From 463f72a123ba69ee03f375602a67ed11737c8203 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Thu, 12 Feb 2026 20:12:11 +0000 Subject: [PATCH] hard reseet --- Cargo.lock | 86 +- Cargo.toml | 24 +- crates/compiler/languages/.gitkeep | 0 crates/compiler/prometeu-abi/src/lib.rs | 9 - .../prometeu-abi/src/vm_init_error.rs | 8 - crates/compiler/prometeu-analysis/Cargo.toml | 13 - crates/compiler/prometeu-analysis/src/lib.rs | 11 - .../prometeu-analysis/src/text_index.rs | 112 - .../tests/text_index_tests.rs | 57 - .../Cargo.toml | 9 +- .../prometeu-build-pipeline/src/cli.rs | 88 + .../prometeu-build-pipeline/src/lib.rs | 3 + .../src/main.rs | 2 +- crates/compiler/prometeu-bytecode/src/lib.rs | 3 + .../src/program_image.rs} | 6 +- .../src/value.rs | 0 .../prometeu-compiler/src/analysis/mod.rs | 3 - .../src/analysis/symbols/mod.rs | 279 -- .../prometeu-compiler/src/analysis/types.rs | 235 -- .../src/backend/artifacts.rs | 158 - .../src/backend/emit_bytecode.rs | 542 ---- .../prometeu-compiler/src/backend/mod.rs | 6 - .../prometeu-compiler/src/building/linker.rs | 725 ----- .../prometeu-compiler/src/building/mod.rs | 13 - .../src/building/orchestrator.rs | 302 -- .../prometeu-compiler/src/building/output.rs | 498 --- .../prometeu-compiler/src/building/plan.rs | 249 -- .../prometeu-compiler/src/common/config.rs | 55 - .../src/common/diagnostics.rs | 155 - .../prometeu-compiler/src/common/files.rs | 106 - .../prometeu-compiler/src/common/mod.rs | 5 - .../prometeu-compiler/src/common/spans.rs | 3 - .../prometeu-compiler/src/common/symbols.rs | 245 -- .../prometeu-compiler/src/compiler.rs | 521 --- .../prometeu-compiler/src/deps/cache.rs | 61 - .../prometeu-compiler/src/deps/fetch.rs | 192 -- .../prometeu-compiler/src/deps/mod.rs | 3 - .../prometeu-compiler/src/deps/resolver.rs | 750 ----- .../prometeu-compiler/src/frontends/mod.rs | 17 - .../src/frontends/pbs/adapter.rs | 184 -- .../src/frontends/pbs/ast.rs | 305 -- .../src/frontends/pbs/collector.rs | 242 -- .../src/frontends/pbs/contracts.rs | 294 -- .../src/frontends/pbs/frontend.rs | 37 - .../src/frontends/pbs/lexer.rs | 442 --- .../src/frontends/pbs/lowering.rs | 2876 ----------------- .../src/frontends/pbs/mod.rs | 108 - .../src/frontends/pbs/parser.rs | 1469 --------- .../src/frontends/pbs/resolve.rs | 568 ---- .../src/frontends/pbs/resolver.rs | 1389 -------- .../src/frontends/pbs/symbols.rs | 101 - .../src/frontends/pbs/token.rs | 95 - .../src/frontends/pbs/typecheck.rs | 1464 --------- .../src/frontends/pbs/types.rs | 53 - .../prometeu-compiler/src/ir_core/block.rs | 12 - .../src/ir_core/const_pool.rs | 98 - .../prometeu-compiler/src/ir_core/function.rs | 30 - .../prometeu-compiler/src/ir_core/ids.rs | 31 - .../prometeu-compiler/src/ir_core/instr.rs | 84 - .../prometeu-compiler/src/ir_core/mod.rs | 128 - .../prometeu-compiler/src/ir_core/module.rs | 9 - .../prometeu-compiler/src/ir_core/program.rs | 16 - .../src/ir_core/signature.rs | 366 --- .../src/ir_core/terminator.rs | 16 - .../prometeu-compiler/src/ir_core/types.rs | 53 - .../prometeu-compiler/src/ir_core/validate.rs | 362 --- .../prometeu-compiler/src/ir_lang/instr.rs | 418 --- .../prometeu-compiler/src/ir_lang/mod.rs | 177 - .../prometeu-compiler/src/ir_lang/module.rs | 82 - .../prometeu-compiler/src/ir_lang/types.rs | 88 - .../prometeu-compiler/src/ir_lang/validate.rs | 10 - crates/compiler/prometeu-compiler/src/lib.rs | 156 - .../src/lowering/core_to_vm.rs | 760 ----- .../prometeu-compiler/src/lowering/mod.rs | 3 - .../prometeu-compiler/src/manifest.rs | 404 --- .../src/semantics/export_surface.rs | 44 - .../prometeu-compiler/src/semantics/mod.rs | 1 - .../compiler/prometeu-compiler/src/sources.rs | 216 -- .../tests/be_no_pbs_imports.rs | 46 - .../tests/diagnostics_span.rs | 26 - .../tests/export_conflicts.rs | 292 -- .../tests/generate_canonical_goldens.rs | 67 - .../tests/hip_conformance.rs | 107 - .../tests/link_integration.rs | 83 - .../Cargo.toml | 2 +- crates/compiler/prometeu-core/src/lib.rs | 3 + .../prometeu-core/src/source/diagnostics.rs | 72 + .../src/source}/file_db.rs | 43 +- .../src => prometeu-core/src/source}/ids.rs | 3 - .../prometeu-core/src/source/line_index.rs | 40 + .../compiler/prometeu-core/src/source/mod.rs | 13 + .../src/source/name_interner.rs} | 2 +- .../src => prometeu-core/src/source}/span.rs | 2 +- .../tests/source}/file_db_line_index.rs | 2 +- .../tests/source}/span_tests.rs | 2 +- crates/compiler/prometeu-deps/Cargo.toml | 13 + crates/compiler/prometeu-deps/src/lib.rs | 1 + .../src}/project_registry.rs | 3 +- .../prometeu-language-api}/Cargo.toml | 2 +- .../compiler/prometeu-language-api/src/lib.rs | 1 + crates/compiler/prometeu-lowering/Cargo.toml | 19 + crates/compiler/prometeu-lowering/src/lib.rs | 0 crates/console/prometeu-drivers/Cargo.toml | 2 +- .../prometeu-drivers/tests/heartbeat.rs | 2 +- crates/console/prometeu-firmware/Cargo.toml | 2 +- crates/console/prometeu-hal/Cargo.toml | 2 +- .../prometeu-hal/src/debugger_protocol.rs | 2 +- .../console/prometeu-hal/src/host_context.rs | 2 +- .../console/prometeu-hal/src/host_return.rs | 4 +- crates/console/prometeu-hal/src/lib.rs | 1 + .../prometeu-hal/src/native_helpers.rs | 4 +- .../prometeu-hal/src/native_interface.rs | 3 +- .../prometeu-hal}/src/vm_fault.rs | 0 crates/console/prometeu-system/Cargo.toml | 2 +- .../src/virtual_machine_runtime.rs | 5 +- crates/console/prometeu-vm/Cargo.toml | 2 +- crates/console/prometeu-vm/src/lib.rs | 5 +- .../prometeu-vm/src/virtual_machine.rs | 26 +- .../console/prometeu-vm/src/vm_init_error.rs | 10 + .../prometeu-host-desktop-winit/Cargo.toml | 2 +- crates/language-api/src/lib.rs | 11 - crates/language-api/src/traits.rs | 23 - crates/language-api/src/types.rs | 341 -- crates/tools/prometeu-cli/Cargo.toml | 2 +- .../tools/prometeu-cli/src/bin/prometeuc.rs | 2 +- crates/tools/prometeu-cli/src/main.rs | 2 +- crates/tools/prometeu-lsp/Cargo.toml | 4 +- crates/tools/prometeu-lsp/src/analysis_db.rs | 103 +- crates/tools/prometeu-lsp/src/main.rs | 368 ++- crates/tools/prometeu-lsp/src/rebuild.rs | 372 +-- crates/tools/prometeu-lsp/tests/adapters.rs | 33 - crates/tools/prometeu-packer/.gitkeep | 0 docs/phase-03-frontend-api.md | 2 +- 133 files changed, 769 insertions(+), 20159 deletions(-) create mode 100644 crates/compiler/languages/.gitkeep delete mode 100644 crates/compiler/prometeu-abi/src/lib.rs delete mode 100644 crates/compiler/prometeu-abi/src/vm_init_error.rs delete mode 100644 crates/compiler/prometeu-analysis/Cargo.toml delete mode 100644 crates/compiler/prometeu-analysis/src/lib.rs delete mode 100644 crates/compiler/prometeu-analysis/src/text_index.rs delete mode 100644 crates/compiler/prometeu-analysis/tests/text_index_tests.rs rename crates/compiler/{prometeu-compiler => prometeu-build-pipeline}/Cargo.toml (70%) create mode 100644 crates/compiler/prometeu-build-pipeline/src/cli.rs create mode 100644 crates/compiler/prometeu-build-pipeline/src/lib.rs rename crates/compiler/{prometeu-compiler => prometeu-build-pipeline}/src/main.rs (82%) rename crates/compiler/{prometeu-abi/src/program.rs => prometeu-bytecode/src/program_image.rs} (96%) rename crates/compiler/{prometeu-abi => prometeu-bytecode}/src/value.rs (100%) delete mode 100644 crates/compiler/prometeu-compiler/src/analysis/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/analysis/symbols/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/analysis/types.rs delete mode 100644 crates/compiler/prometeu-compiler/src/backend/artifacts.rs delete mode 100644 crates/compiler/prometeu-compiler/src/backend/emit_bytecode.rs delete mode 100644 crates/compiler/prometeu-compiler/src/backend/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/building/linker.rs delete mode 100644 crates/compiler/prometeu-compiler/src/building/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/building/orchestrator.rs delete mode 100644 crates/compiler/prometeu-compiler/src/building/output.rs delete mode 100644 crates/compiler/prometeu-compiler/src/building/plan.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/config.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/diagnostics.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/files.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/spans.rs delete mode 100644 crates/compiler/prometeu-compiler/src/common/symbols.rs delete mode 100644 crates/compiler/prometeu-compiler/src/compiler.rs delete mode 100644 crates/compiler/prometeu-compiler/src/deps/cache.rs delete mode 100644 crates/compiler/prometeu-compiler/src/deps/fetch.rs delete mode 100644 crates/compiler/prometeu-compiler/src/deps/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/deps/resolver.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/adapter.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/ast.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/collector.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/contracts.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/frontend.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/lexer.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/lowering.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/parser.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/resolve.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/resolver.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/symbols.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/token.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/typecheck.rs delete mode 100644 crates/compiler/prometeu-compiler/src/frontends/pbs/types.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/block.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/const_pool.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/function.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/ids.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/instr.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/module.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/program.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/signature.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/terminator.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/types.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_core/validate.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_lang/instr.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_lang/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_lang/module.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_lang/types.rs delete mode 100644 crates/compiler/prometeu-compiler/src/ir_lang/validate.rs delete mode 100644 crates/compiler/prometeu-compiler/src/lib.rs delete mode 100644 crates/compiler/prometeu-compiler/src/lowering/core_to_vm.rs delete mode 100644 crates/compiler/prometeu-compiler/src/lowering/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/manifest.rs delete mode 100644 crates/compiler/prometeu-compiler/src/semantics/export_surface.rs delete mode 100644 crates/compiler/prometeu-compiler/src/semantics/mod.rs delete mode 100644 crates/compiler/prometeu-compiler/src/sources.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/be_no_pbs_imports.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/diagnostics_span.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/export_conflicts.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/generate_canonical_goldens.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/hip_conformance.rs delete mode 100644 crates/compiler/prometeu-compiler/tests/link_integration.rs rename crates/compiler/{prometeu-abi => prometeu-core}/Cargo.toml (90%) create mode 100644 crates/compiler/prometeu-core/src/lib.rs create mode 100644 crates/compiler/prometeu-core/src/source/diagnostics.rs rename crates/compiler/{prometeu-analysis/src => prometeu-core/src/source}/file_db.rs (57%) rename crates/compiler/{prometeu-analysis/src => prometeu-core/src/source}/ids.rs (92%) create mode 100644 crates/compiler/prometeu-core/src/source/line_index.rs create mode 100644 crates/compiler/prometeu-core/src/source/mod.rs rename crates/compiler/{prometeu-analysis/src/interner.rs => prometeu-core/src/source/name_interner.rs} (98%) rename crates/compiler/{prometeu-analysis/src => prometeu-core/src/source}/span.rs (95%) rename crates/compiler/{prometeu-analysis/tests => prometeu-core/tests/source}/file_db_line_index.rs (98%) rename crates/compiler/{prometeu-analysis/tests => prometeu-core/tests/source}/span_tests.rs (85%) create mode 100644 crates/compiler/prometeu-deps/Cargo.toml create mode 100644 crates/compiler/prometeu-deps/src/lib.rs rename crates/compiler/{prometeu-compiler/src/analysis => prometeu-deps/src}/project_registry.rs (98%) rename crates/{language-api => compiler/prometeu-language-api}/Cargo.toml (92%) create mode 100644 crates/compiler/prometeu-language-api/src/lib.rs create mode 100644 crates/compiler/prometeu-lowering/Cargo.toml create mode 100644 crates/compiler/prometeu-lowering/src/lib.rs rename crates/{compiler/prometeu-abi => console/prometeu-hal}/src/vm_fault.rs (100%) create mode 100644 crates/console/prometeu-vm/src/vm_init_error.rs delete mode 100644 crates/language-api/src/lib.rs delete mode 100644 crates/language-api/src/traits.rs delete mode 100644 crates/language-api/src/types.rs delete mode 100644 crates/tools/prometeu-lsp/tests/adapters.rs create mode 100644 crates/tools/prometeu-packer/.gitkeep diff --git a/Cargo.lock b/Cargo.lock index f9deaddb..ff07f72e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,14 +1156,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "language-api" -version = "0.1.0" -dependencies = [ - "serde", - "thiserror", -] - [[package]] name = "libc" version = "0.2.180" @@ -1881,20 +1873,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] -name = "prometeu-abi" +name = "prometeu-build-pipeline" version = "0.1.0" dependencies = [ + "anyhow", + "clap", + "pathdiff", "prometeu-bytecode", + "prometeu-core", + "prometeu-language-api", "serde", "serde_json", -] - -[[package]] -name = "prometeu-analysis" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", + "tempfile", ] [[package]] @@ -1910,31 +1900,32 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "prometeu-compiler", + "prometeu-build-pipeline", "prometeu-host-desktop-winit", ] [[package]] -name = "prometeu-compiler" +name = "prometeu-core" version = "0.1.0" dependencies = [ - "anyhow", - "clap", - "language-api", - "pathdiff", - "prometeu-abi", - "prometeu-analysis", "prometeu-bytecode", "serde", "serde_json", - "tempfile", +] + +[[package]] +name = "prometeu-deps" +version = "0.1.0" +dependencies = [ + "prometeu-core", + "serde", ] [[package]] name = "prometeu-drivers" version = "0.1.0" dependencies = [ - "prometeu-abi", + "prometeu-core", "prometeu-hal", "prometeu-vm", "serde_json", @@ -1944,8 +1935,8 @@ dependencies = [ name = "prometeu-firmware" version = "0.1.0" dependencies = [ - "prometeu-abi", "prometeu-bytecode", + "prometeu-core", "prometeu-drivers", "prometeu-hal", "prometeu-system", @@ -1958,8 +1949,8 @@ dependencies = [ name = "prometeu-hal" version = "0.1.0" dependencies = [ - "prometeu-abi", "prometeu-bytecode", + "prometeu-core", "serde", "serde_json", ] @@ -1971,7 +1962,7 @@ dependencies = [ "clap", "cpal", "pixels", - "prometeu-abi", + "prometeu-core", "prometeu-drivers", "prometeu-firmware", "prometeu-hal", @@ -1981,12 +1972,35 @@ dependencies = [ "winit", ] +[[package]] +name = "prometeu-language-api" +version = "0.1.0" +dependencies = [ + "serde", + "thiserror", +] + +[[package]] +name = "prometeu-lowering" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "pathdiff", + "prometeu-bytecode", + "prometeu-core", + "prometeu-language-api", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "prometeu-lsp" version = "0.1.0" dependencies = [ - "prometeu-analysis", - "prometeu-compiler", + "prometeu-build-pipeline", + "prometeu-core", "tokio", "tokio-util", "tower-lsp", @@ -1996,8 +2010,8 @@ dependencies = [ name = "prometeu-system" version = "0.1.0" dependencies = [ - "prometeu-abi", "prometeu-bytecode", + "prometeu-core", "prometeu-drivers", "prometeu-hal", "prometeu-vm", @@ -2008,8 +2022,8 @@ dependencies = [ name = "prometeu-vm" version = "0.1.0" dependencies = [ - "prometeu-abi", "prometeu-bytecode", + "prometeu-core", "prometeu-hal", "serde", ] @@ -2413,9 +2427,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", "getrandom", diff --git a/Cargo.toml b/Cargo.toml index 13bc3f0e..ea0f5f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,22 @@ [workspace] members = [ - "crates/compiler/prometeu-abi", - "crates/console/prometeu-vm", - "crates/console/prometeu-system", - "crates/console/prometeu-drivers", - "crates/host/prometeu-host-desktop-winit", - "crates/tools/prometeu-cli", + "crates/compiler/prometeu-build-pipeline", "crates/compiler/prometeu-bytecode", - "crates/compiler/prometeu-compiler", + "crates/compiler/prometeu-core", + "crates/compiler/prometeu-deps", + "crates/compiler/prometeu-language-api", + "crates/compiler/prometeu-lowering", + + "crates/console/prometeu-drivers", "crates/console/prometeu-firmware", - "crates/compiler/prometeu-analysis", - "crates/tools/prometeu-lsp", "crates/console/prometeu-hal", - "crates/language-api" + "crates/console/prometeu-system", + "crates/console/prometeu-vm", + + "crates/host/prometeu-host-desktop-winit", + + "crates/tools/prometeu-cli", + "crates/tools/prometeu-lsp", ] resolver = "2" diff --git a/crates/compiler/languages/.gitkeep b/crates/compiler/languages/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/crates/compiler/prometeu-abi/src/lib.rs b/crates/compiler/prometeu-abi/src/lib.rs deleted file mode 100644 index 19160f23..00000000 --- a/crates/compiler/prometeu-abi/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod value; -mod vm_init_error; -mod program; -mod vm_fault; - -pub use vm_fault::VmFault; -pub use program::ProgramImage; -pub use vm_init_error::VmInitError; -pub use value::Value; diff --git a/crates/compiler/prometeu-abi/src/vm_init_error.rs b/crates/compiler/prometeu-abi/src/vm_init_error.rs deleted file mode 100644 index 30b3bf04..00000000 --- a/crates/compiler/prometeu-abi/src/vm_init_error.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VmInitError { - InvalidFormat, - UnsupportedFormat, - PbsV0LoadFailed(prometeu_bytecode::LoadError), - EntrypointNotFound, - VerificationFailed(E), -} diff --git a/crates/compiler/prometeu-analysis/Cargo.toml b/crates/compiler/prometeu-analysis/Cargo.toml deleted file mode 100644 index 95c1de81..00000000 --- a/crates/compiler/prometeu-analysis/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "prometeu-analysis" -version = "0.1.0" -edition = "2021" -license = "MIT" - -[lib] -name = "prometeu_analysis" -path = "src/lib.rs" - -[dependencies] -serde = { version = "1", features = ["derive"] } -serde_json = "1" diff --git a/crates/compiler/prometeu-analysis/src/lib.rs b/crates/compiler/prometeu-analysis/src/lib.rs deleted file mode 100644 index 932fdf08..00000000 --- a/crates/compiler/prometeu-analysis/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod ids; -pub mod span; -pub mod file_db; -pub mod interner; -pub mod text_index; - -pub use ids::*; -pub use span::Span; -pub use file_db::{FileDB, LineIndex}; -pub use interner::NameInterner; -pub use text_index::TextIndex; diff --git a/crates/compiler/prometeu-analysis/src/text_index.rs b/crates/compiler/prometeu-analysis/src/text_index.rs deleted file mode 100644 index 1c2a190a..00000000 --- a/crates/compiler/prometeu-analysis/src/text_index.rs +++ /dev/null @@ -1,112 +0,0 @@ -/// TextIndex provides conversions between byte offsets (used in the core) -/// and LSP positions (line, column in UTF-16 units). -/// -/// Notes: -/// - `line_starts` stores byte offsets for the start of each line. -/// - We keep a copy of the text to allow conversions without external dependencies. -/// - The LSP column is counted in UTF-16 units, excluding the end-of-line `\n`. -#[derive(Clone, Debug)] -pub struct TextIndex { - text: String, - line_starts: Vec, -} - -impl TextIndex { - /// Builds the index from the file's current textual content. - pub fn new(text: &str) -> Self { - let mut line_starts = Vec::with_capacity(128); - line_starts.push(0); - for (byte, ch) in text.char_indices() { - if ch == '\n' { - // the start of the next line is the byte after the '\n' - line_starts.push((byte + 1) as u32); - } - } - Self { - text: text.to_string(), - line_starts, - } - } - - /// Number of lines (0-based; empty lines count). - #[inline] - pub fn line_count(&self) -> u32 { - self.line_starts.len() as u32 - } - - /// Converts a byte offset (within the file) to (line, UTF-16 column) in LSP format. - /// - /// For offsets exactly at end-of-line, the column will be the line's UTF-16 length. - pub fn byte_to_lsp(&self, byte: u32) -> (u32, u32) { - let byte = byte.min(self.text.len() as u32); - let line = match self.line_starts.binary_search(&byte) { - Ok(i) => i as u32, - Err(i) => (i.saturating_sub(1)) as u32, - }; - - let (line_start, line_end) = self.line_bounds(line); - let rel = byte.saturating_sub(line_start as u32) as usize; - let slice = &self.text[line_start..line_end]; - - let mut utf16_col: u32 = 0; - for (i, ch) in slice.char_indices() { - if i >= rel { break; } - utf16_col += ch.len_utf16() as u32; - } - (line, utf16_col) - } - - /// Converts (line, UTF-16 column) to a byte offset. - /// - /// - Lines outside the range are clamped to [0, last]. - /// - Columns larger than the line's UTF-16 length return end-of-line. - pub fn lsp_to_byte(&self, line: u32, utf16_col: u32) -> u32 { - let line = line.min(self.line_count().saturating_sub(1)); - let (line_start, line_end) = self.line_bounds(line); - let slice = &self.text[line_start..line_end]; - - let mut acc: u32 = 0; - for (i, ch) in slice.char_indices() { - if acc >= utf16_col { - return (line_start + i) as u32; - } - acc += ch.len_utf16() as u32; - } - // If the target column is after the last character, return end-of-line. - line_end as u32 - } - - #[inline] - fn line_bounds(&self, line: u32) -> (usize, usize) { - let start = *self - .line_starts - .get(line as usize) - .unwrap_or(self.line_starts.last().unwrap()); - let next = self.line_starts.get(line as usize + 1).copied(); - // If there is a next line, `next` points to the byte after the current line's '\n', - // so the content ends at `next - 1`. Otherwise (last line), the content ends at `text.len()`. - let end = match next { - Some(next_start) => next_start.saturating_sub(1), - None => self.text.len() as u32, - }; - (start as usize, end as usize) - } -} - -#[cfg(test)] -mod tests_internal { - use super::*; - - #[test] - fn line_bounds_basic() { - let s = "ab\ncd\n"; - let idx = TextIndex::new(s); - assert_eq!(idx.line_count(), 3); - // line 0: "ab" - assert_eq!(idx.line_bounds(0), (0, 2)); - // line 1: "cd" - assert_eq!(idx.line_bounds(1), (3, 5)); - // line 2: final empty line - assert_eq!(idx.line_bounds(2), (6, 6)); - } -} diff --git a/crates/compiler/prometeu-analysis/tests/text_index_tests.rs b/crates/compiler/prometeu-analysis/tests/text_index_tests.rs deleted file mode 100644 index ada55d54..00000000 --- a/crates/compiler/prometeu-analysis/tests/text_index_tests.rs +++ /dev/null @@ -1,57 +0,0 @@ -use prometeu_analysis::TextIndex; - -#[test] -fn text_index_ascii_roundtrip() { - let text = "hello\nworld\nthis is ascii"; - let idx = TextIndex::new(text); - - // Verify round-trip on all character boundaries - let mut boundaries: Vec = text.char_indices().map(|(i, _)| i).collect(); - boundaries.push(text.len()); - - for &b in &boundaries { - let (line, col16) = idx.byte_to_lsp(b as u32); - let b2 = idx.lsp_to_byte(line, col16); - assert_eq!(b2, b as u32, "roundtrip failed for byte {} -> (l={},c16={})", b, line, col16); - } - - // Some direct checks - // start: (0,0) - assert_eq!(idx.byte_to_lsp(0), (0, 0)); - // after "hello" (5), before '\n': line 0, col=5 - assert_eq!(idx.byte_to_lsp(5), (0, 5)); - // after '\n' (6): line 1, col=0 - assert_eq!(idx.byte_to_lsp(6), (1, 0)); -} - -#[test] -fn text_index_unicode_roundtrip_utf16() { - // "a" (1B, 1u16), "é" (2B, 1u16), "🙂" (4B, 2u16), "b" (1B, 1u16) - let text = "aé🙂b"; - let idx = TextIndex::new(text); - - // character boundaries + end - let mut boundaries: Vec = text.char_indices().map(|(i, _)| i).collect(); - boundaries.push(text.len()); - - for &b in &boundaries { - let (line, col16) = idx.byte_to_lsp(b as u32); - let b2 = idx.lsp_to_byte(line, col16); - assert_eq!(b2, b as u32, "unicode roundtrip failed for byte {} -> (l={},c16={})", b, line, col16); - } - - // Expected columns on line 0 - // bytes: [0:'a'][1..2:'é'][3..6:'🙂'][7:'b'][8:end] - assert_eq!(idx.byte_to_lsp(0), (0, 0)); // before 'a' - assert_eq!(idx.byte_to_lsp(1), (0, 1)); // after 'a' - assert_eq!(idx.byte_to_lsp(3), (0, 2)); // after 'a' + 'é' (1+1 utf16) - assert_eq!(idx.byte_to_lsp(7), (0, 4)); // after '🙂' (2 utf16) => 1+1+2=4 - assert_eq!(idx.byte_to_lsp(8), (0, 5)); // after 'b' - - // and inverse, specific columns - assert_eq!(idx.lsp_to_byte(0, 0), 0); - assert_eq!(idx.lsp_to_byte(0, 1), 1); - assert_eq!(idx.lsp_to_byte(0, 2), 3); - assert_eq!(idx.lsp_to_byte(0, 4), 7); - assert_eq!(idx.lsp_to_byte(0, 5), 8); -} diff --git a/crates/compiler/prometeu-compiler/Cargo.toml b/crates/compiler/prometeu-build-pipeline/Cargo.toml similarity index 70% rename from crates/compiler/prometeu-compiler/Cargo.toml rename to crates/compiler/prometeu-build-pipeline/Cargo.toml index 2b3b638d..5544d90f 100644 --- a/crates/compiler/prometeu-compiler/Cargo.toml +++ b/crates/compiler/prometeu-build-pipeline/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "prometeu-compiler" +name = "prometeu-build-pipeline" version = "0.1.0" edition = "2021" license.workspace = true repository.workspace = true [[bin]] -name = "prometeu-compiler" +name = "prometeu-build-pipeline" path = "src/main.rs" [package.metadata.dist] @@ -15,9 +15,8 @@ include = ["../../VERSION.txt"] [dependencies] prometeu-bytecode = { path = "../prometeu-bytecode" } -prometeu-abi = { path = "../prometeu-abi" } -prometeu-analysis = { path = "../prometeu-analysis" } -language-api = { path = "../../language-api" } +prometeu-core = { path = "../prometeu-core" } +prometeu-language-api = { path = "../prometeu-language-api" } clap = { version = "4.5.54", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/crates/compiler/prometeu-build-pipeline/src/cli.rs b/crates/compiler/prometeu-build-pipeline/src/cli.rs new file mode 100644 index 00000000..bdccb643 --- /dev/null +++ b/crates/compiler/prometeu-build-pipeline/src/cli.rs @@ -0,0 +1,88 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +/// Command line interface for the Prometeu Compiler. +#[derive(Parser)] +#[command(name = "prometeu-build-pipeline")] +#[command(version, about = "Official compiler for the PROMETEU Virtual Machine", long_about = None)] +pub struct Cli { + /// The action to perform (build or verify). + #[command(subcommand)] + pub command: Commands, +} + +/// Available subcommands for the compiler. +#[derive(Subcommand)] +pub enum Commands { + /// Builds a Prometeu project by compiling source code into a PBC file. + Build { + /// Path to the project root directory. + project_dir: PathBuf, + + /// Explicit path to the entry file (defaults to src/main.pbs). + #[arg(short, long)] + entry: Option, + + /// Path to save the compiled .pbc file. + #[arg(short, long)] + out: Option, + + /// Whether to generate a .json symbols file for source mapping. + #[arg(long, default_value_t = true)] + emit_symbols: bool, + + /// Disable symbol generation. + #[arg(long)] + no_symbols: bool, + + /// Whether to generate a .disasm file for debugging. + #[arg(long, default_value_t = true)] + emit_disasm: bool, + + /// Disable disassembly generation. + #[arg(long)] + no_disasm: bool, + + /// Whether to explain the dependency resolution process. + #[arg(long)] + explain_deps: bool, + }, + /// Verifies if a Prometeu project is syntactically and semantically valid without emitting code. + Verify { + /// Path to the project root directory. + project_dir: PathBuf, + + /// Whether to explain the dependency resolution process. + #[arg(long)] + explain_deps: bool, + }, +} + +/// Main entry point for the compiler library's execution logic. +/// Parses CLI arguments and dispatches to the appropriate compiler functions. +pub fn run() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Build { + project_dir, + out, + emit_disasm, + no_disasm, + emit_symbols, + no_symbols, + explain_deps, + .. + } => { + + } + Commands::Verify { + project_dir, + explain_deps + } => { + } + } + + Ok(()) +} \ No newline at end of file diff --git a/crates/compiler/prometeu-build-pipeline/src/lib.rs b/crates/compiler/prometeu-build-pipeline/src/lib.rs new file mode 100644 index 00000000..cb9c883e --- /dev/null +++ b/crates/compiler/prometeu-build-pipeline/src/lib.rs @@ -0,0 +1,3 @@ +mod cli; + +pub use cli::run; diff --git a/crates/compiler/prometeu-compiler/src/main.rs b/crates/compiler/prometeu-build-pipeline/src/main.rs similarity index 82% rename from crates/compiler/prometeu-compiler/src/main.rs rename to crates/compiler/prometeu-build-pipeline/src/main.rs index a57baf4b..b8db3c41 100644 --- a/crates/compiler/prometeu-compiler/src/main.rs +++ b/crates/compiler/prometeu-build-pipeline/src/main.rs @@ -3,5 +3,5 @@ use anyhow::Result; /// Main entry point for the Prometeu Compiler binary. /// It delegates execution to the library's `run` function. fn main() -> Result<()> { - prometeu_compiler::run() + prometeu_build_pipeline::run() } diff --git a/crates/compiler/prometeu-bytecode/src/lib.rs b/crates/compiler/prometeu-bytecode/src/lib.rs index cc4ede51..a6444993 100644 --- a/crates/compiler/prometeu-bytecode/src/lib.rs +++ b/crates/compiler/prometeu-bytecode/src/lib.rs @@ -24,5 +24,8 @@ pub mod decoder; mod model; pub mod io; +pub mod value; +pub mod program_image; pub use model::*; +pub use value::Value; diff --git a/crates/compiler/prometeu-abi/src/program.rs b/crates/compiler/prometeu-bytecode/src/program_image.rs similarity index 96% rename from crates/compiler/prometeu-abi/src/program.rs rename to crates/compiler/prometeu-bytecode/src/program_image.rs index e9e45395..6fbc02c5 100644 --- a/crates/compiler/prometeu-abi/src/program.rs +++ b/crates/compiler/prometeu-bytecode/src/program_image.rs @@ -1,8 +1,8 @@ -use prometeu_bytecode::abi::TrapInfo; -use prometeu_bytecode::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; +use crate::abi::TrapInfo; use std::collections::HashMap; use std::sync::Arc; -use crate::Value; +use crate::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; +use crate::value::Value; /// Represents a fully linked, executable PBS program image. /// diff --git a/crates/compiler/prometeu-abi/src/value.rs b/crates/compiler/prometeu-bytecode/src/value.rs similarity index 100% rename from crates/compiler/prometeu-abi/src/value.rs rename to crates/compiler/prometeu-bytecode/src/value.rs diff --git a/crates/compiler/prometeu-compiler/src/analysis/mod.rs b/crates/compiler/prometeu-compiler/src/analysis/mod.rs deleted file mode 100644 index 79db470b..00000000 --- a/crates/compiler/prometeu-compiler/src/analysis/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod symbols; -pub mod types; -pub mod project_registry; \ No newline at end of file diff --git a/crates/compiler/prometeu-compiler/src/analysis/symbols/mod.rs b/crates/compiler/prometeu-compiler/src/analysis/symbols/mod.rs deleted file mode 100644 index 9120eb83..00000000 --- a/crates/compiler/prometeu-compiler/src/analysis/symbols/mod.rs +++ /dev/null @@ -1,279 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, Severity}; -use crate::common::spans::{Span, FileId}; -use crate::frontends::pbs::ast::AstArena; -use prometeu_analysis::NodeId; -use prometeu_analysis::{NameId, SymbolId, ModuleId}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub enum SymbolKind { - Type, - Value, - Service, - Function, - Struct, - Contract, - ErrorType, - Local, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] -pub enum Namespace { - Type, - Value, - Service, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Symbol { - pub name: NameId, - pub kind: SymbolKind, - pub exported: bool, - pub module: ModuleId, - pub decl_span: Span, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct SymbolArena { - pub symbols: Vec, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct DefKey { - pub module: ModuleId, - pub name: NameId, - pub namespace: Namespace, -} - -#[derive(Debug, Default, Clone)] -pub struct DefIndex { - symbols: HashMap, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct RefIndex { - refs: Vec>, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct NodeToSymbol { - map: Vec>, -} - -impl SymbolArena { - pub fn new() -> Self { - Self { symbols: Vec::new() } - } - - pub fn insert(&mut self, symbol: Symbol) -> SymbolId { - let id = SymbolId(self.symbols.len() as u32); - self.symbols.push(symbol); - id - } - - pub fn get(&self, id: SymbolId) -> &Symbol { - &self.symbols[id.0 as usize] - } -} - -impl DefIndex { - pub fn new() -> Self { - Self { - symbols: HashMap::new(), - } - } - - pub fn insert_symbol(&mut self, key: DefKey, symbol_id: SymbolId) -> Result<(), Diagnostic> { - if self.symbols.contains_key(&key) { - return Err(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(), - message: "Duplicate symbol in the same module and namespace".to_string(), - // Placeholder span; callers should overwrite with accurate span when known. - span: Span::new(FileId(0), 0, 0), - related: Vec::new(), - }); - } - - self.symbols.insert(key, symbol_id); - Ok(()) - } - - pub fn get(&self, key: DefKey) -> Option { - self.symbols.get(&key).copied() - } - - /// Lookup by name/namespace ignoring module. Returns the first match with its module id. - pub fn get_by_name_any_module(&self, name: NameId, namespace: Namespace) -> Option<(ModuleId, SymbolId)> { - for (k, v) in &self.symbols { - if k.name == name && k.namespace == namespace { - return Some((k.module, *v)); - } - } - None - } -} - -impl RefIndex { - pub fn new() -> Self { - Self { refs: Vec::new() } - } - - pub fn ensure_symbol(&mut self, symbol_id: SymbolId) { - let index = symbol_id.0 as usize; - if index >= self.refs.len() { - self.refs.resize_with(index + 1, Vec::new); - } - } - - pub fn record_ref(&mut self, symbol_id: SymbolId, span: Span) { - self.ensure_symbol(symbol_id); - self.refs[symbol_id.0 as usize].push(span); - } - - pub fn refs_of(&self, symbol_id: SymbolId) -> &[Span] { - const EMPTY: [Span; 0] = []; - self.refs - .get(symbol_id.0 as usize) - .map(|refs| refs.as_slice()) - .unwrap_or(&EMPTY) - } -} - -impl NodeToSymbol { - pub fn new() -> Self { - Self { map: Vec::new() } - } - - pub fn bind_node(&mut self, node_id: NodeId, symbol_id: SymbolId) { - self.ensure(node_id); - self.map[node_id.0 as usize] = Some(symbol_id); - } - - pub fn get(&self, node_id: NodeId) -> Option { - self.map.get(node_id.0 as usize).and_then(|opt| *opt) - } - - pub fn ensure(&mut self, node_id: NodeId) { - let index = node_id.0 as usize; - if index >= self.map.len() { - self.map.resize(index + 1, None); - } - } - - pub fn resize_to_fit(&mut self, arena: &AstArena) { - self.map.resize(arena.nodes.len(), None); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_symbol(name: NameId, module: ModuleId) -> Symbol { - Symbol { - name, - kind: SymbolKind::Function, - exported: false, - module, - decl_span: Span::new(FileId(0), 1, 2), - } - } - - #[test] - fn insert_returns_incremental_ids() { - let mut arena = SymbolArena::new(); - let id0 = arena.insert(sample_symbol(NameId(0), ModuleId(0))); - let id1 = arena.insert(sample_symbol(NameId(1), ModuleId(0))); - - assert_eq!(id0, SymbolId(0)); - assert_eq!(id1, SymbolId(1)); - } - - #[test] - fn get_returns_correct_symbol() { - let mut arena = SymbolArena::new(); - let symbol = sample_symbol(NameId(7), ModuleId(3)); - let id = arena.insert(symbol.clone()); - - assert_eq!(arena.get(id).name, symbol.name); - assert_eq!(arena.get(id).kind, symbol.kind); - assert_eq!(arena.get(id).module, symbol.module); - assert_eq!(arena.get(id).decl_span, symbol.decl_span); - } - - #[test] - fn def_index_duplicate_in_same_namespace_errors() { - let mut index = DefIndex::new(); - let key = DefKey { - module: ModuleId(1), - name: NameId(10), - namespace: Namespace::Type, - }; - - assert!(index.insert_symbol(key, SymbolId(0)).is_ok()); - let err = index.insert_symbol(key, SymbolId(1)).unwrap_err(); - - assert_eq!(err.code, "E_RESOLVE_DUPLICATE_SYMBOL"); - } - - #[test] - fn def_index_allows_same_name_in_different_namespace() { - let mut index = DefIndex::new(); - let name = NameId(11); - let type_key = DefKey { - module: ModuleId(2), - name, - namespace: Namespace::Type, - }; - let value_key = DefKey { - module: ModuleId(2), - name, - namespace: Namespace::Value, - }; - - assert!(index.insert_symbol(type_key, SymbolId(0)).is_ok()); - assert!(index.insert_symbol(value_key, SymbolId(1)).is_ok()); - assert_eq!(index.get(type_key), Some(SymbolId(0))); - assert_eq!(index.get(value_key), Some(SymbolId(1))); - } - - #[test] - fn ref_index_records_refs_per_symbol() { - let mut index = RefIndex::new(); - let span_a1 = Span::new(FileId(0), 1, 2); - let span_a2 = Span::new(FileId(0), 3, 4); - let span_b1 = Span::new(FileId(1), 10, 12); - - index.record_ref(SymbolId(2), span_a1.clone()); - index.record_ref(SymbolId(2), span_a2.clone()); - index.record_ref(SymbolId(5), span_b1.clone()); - - assert_eq!(index.refs_of(SymbolId(2)), &[span_a1.clone(), span_a2.clone()]); - assert_eq!(index.refs_of(SymbolId(5)), &[span_b1.clone()]); - assert!(index.refs_of(SymbolId(9)).is_empty()); - } - - #[test] - fn node_to_symbol_bind_and_get() { - let mut map = NodeToSymbol::new(); - let nid = NodeId(10); - let sid = SymbolId(5); - - map.bind_node(nid, sid); - assert_eq!(map.get(nid), Some(sid)); - assert_eq!(map.get(NodeId(0)), None); - } - - #[test] - fn node_to_symbol_expands_automatically() { - let mut map = NodeToSymbol::new(); - let nid_high = NodeId(100); - let sid = SymbolId(1); - - map.bind_node(nid_high, sid); - assert_eq!(map.get(nid_high), Some(sid)); - assert!(map.map.len() > 100); - } -} \ No newline at end of file diff --git a/crates/compiler/prometeu-compiler/src/analysis/types.rs b/crates/compiler/prometeu-compiler/src/analysis/types.rs deleted file mode 100644 index 818cd214..00000000 --- a/crates/compiler/prometeu-compiler/src/analysis/types.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::analysis::symbols::{SymbolArena}; -use prometeu_analysis::{NameId, NameInterner, TypeId, SymbolId, NodeId}; -use serde::{Deserialize, Serialize}; - -// Use canonical TypeId from prometeu-analysis - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum TypeKind { - Primitive { name: NameId }, - Optional { inner: TypeId }, - Result { ok: TypeId, err: TypeId }, - Array { inner: TypeId, len: Option }, - Struct { sym: SymbolId }, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct TypeArena { - pub types: Vec, -} - -impl TypeArena { - pub fn new() -> Self { - Self { types: Vec::new() } - } - - /// Interna um tipo na arena. Atualmente apenas adiciona ao final (append-only), - /// sem realizar de-duplicação. - pub fn intern_type(&mut self, kind: TypeKind) -> TypeId { - let id = TypeId(self.types.len() as u32); - self.types.push(kind); - id - } - - pub fn kind(&self, id: TypeId) -> &TypeKind { - &self.types[id.0 as usize] - } -} - -pub fn format_type( - type_id: TypeId, - arena: &TypeArena, - interner: &NameInterner, - symbols: Option<&SymbolArena>, -) -> String { - let kind = arena.kind(type_id); - match kind { - TypeKind::Primitive { name } => interner.resolve(*name).to_string(), - TypeKind::Optional { inner } => { - let inner_str = format_type(*inner, arena, interner, symbols); - format!("optional<{}>", inner_str) - } - TypeKind::Result { ok, err } => { - let ok_str = format_type(*ok, arena, interner, symbols); - let err_str = format_type(*err, arena, interner, symbols); - format!("result<{}, {}>", ok_str, err_str) - } - TypeKind::Array { inner, len } => { - let inner_str = format_type(*inner, arena, interner, symbols); - if let Some(n) = len { - format!("array<{}>[{}]", inner_str, n) - } else { - format!("array<{}>", inner_str) - } - } - TypeKind::Struct { sym } => { - if let Some(symbol_arena) = symbols { - let symbol = symbol_arena.get(*sym); - interner.resolve(symbol.name).to_string() - } else { - format!("struct#{}", sym.0) - } - } - } -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct TypeFacts { - pub node_type: Vec>, - pub symbol_type: Vec>, -} - -impl TypeFacts { - pub fn new() -> Self { - Self::default() - } - - pub fn set_node_type(&mut self, node_id: NodeId, type_id: TypeId) { - let idx = node_id.0 as usize; - if idx >= self.node_type.len() { - self.node_type.resize_with(idx + 1, || None); - } - self.node_type[idx] = Some(type_id); - } - - pub fn get_node_type(&self, node_id: NodeId) -> Option { - self.node_type.get(node_id.0 as usize).and_then(|t| *t) - } - - pub fn set_symbol_type(&mut self, symbol_id: SymbolId, type_id: TypeId) { - let idx = symbol_id.0 as usize; - if idx >= self.symbol_type.len() { - self.symbol_type.resize_with(idx + 1, || None); - } - self.symbol_type[idx] = Some(type_id); - } - - pub fn get_symbol_type(&self, symbol_id: SymbolId) -> Option { - self.symbol_type.get(symbol_id.0 as usize).and_then(|t| *t) - } -} - -#[cfg(test)] -mod tests { - use prometeu_analysis::{FileId, ModuleId}; - use super::*; - // Mock NameId and SymbolId for simplified tests if necessary, - // or use real values. - - #[test] - fn type_arena_push_is_append_only() { - let mut arena = TypeArena::new(); - let name = NameId(0); - let t1 = arena.intern_type(TypeKind::Primitive { name }); - let t2 = arena.intern_type(TypeKind::Optional { inner: t1 }); - - assert_eq!(t1.0, 0); - assert_eq!(t2.0, 1); - assert_eq!(arena.types.len(), 2); - } - - #[test] - fn type_arena_index_is_stable() { - let mut arena = TypeArena::new(); - let name = NameId(0); - let t1 = arena.intern_type(TypeKind::Primitive { name }); - let t2 = arena.intern_type(TypeKind::Optional { inner: t1 }); - - assert!(matches!(arena.kind(t1), TypeKind::Primitive { .. })); - assert!(matches!(arena.kind(t2), TypeKind::Optional { .. })); - - // Adding more types should not change the previous ones - let t3 = arena.intern_type(TypeKind::Array { inner: t1, len: None }); - assert!(matches!(arena.kind(t1), TypeKind::Primitive { .. })); - assert!(matches!(arena.kind(t2), TypeKind::Optional { .. })); - assert!(matches!(arena.kind(t3), TypeKind::Array { .. })); - } - - #[test] - fn type_facts_auto_grows_for_node_ids() { - let mut facts = TypeFacts::new(); - let nid = NodeId(10); - let tid = TypeId(1); - - assert_eq!(facts.get_node_type(nid), None); - facts.set_node_type(nid, tid); - assert_eq!(facts.get_node_type(nid), Some(tid)); - assert!(facts.node_type.len() > 10); - } - - #[test] - fn format_type_optional() { - let mut arena = TypeArena::new(); - let mut interner = NameInterner::new(); - let int_name = interner.intern("int"); - let int_t = arena.intern_type(TypeKind::Primitive { name: int_name }); - let opt_t = arena.intern_type(TypeKind::Optional { inner: int_t }); - - let formatted = format_type(opt_t, &arena, &interner, None); - assert_eq!(formatted, "optional"); - } - - #[test] - fn format_type_result() { - let mut arena = TypeArena::new(); - let mut interner = NameInterner::new(); - let int_name = interner.intern("int"); - let string_name = interner.intern("string"); - let int_t = arena.intern_type(TypeKind::Primitive { name: int_name }); - let string_t = arena.intern_type(TypeKind::Primitive { name: string_name }); - let res_t = arena.intern_type(TypeKind::Result { ok: int_t, err: string_t }); - - let formatted = format_type(res_t, &arena, &interner, None); - assert_eq!(formatted, "result"); - } - - #[test] - fn format_type_array_len() { - let mut arena = TypeArena::new(); - let mut interner = NameInterner::new(); - let bool_name = interner.intern("bool"); - let bool_t = arena.intern_type(TypeKind::Primitive { name: bool_name }); - - let arr_dynamic = arena.intern_type(TypeKind::Array { inner: bool_t, len: None }); - let arr_fixed = arena.intern_type(TypeKind::Array { inner: bool_t, len: Some(10) }); - - assert_eq!(format_type(arr_dynamic, &arena, &interner, None), "array"); - assert_eq!(format_type(arr_fixed, &arena, &interner, None), "array[10]"); - } - - #[test] - fn format_type_struct_with_arena() { - use crate::analysis::symbols::{Symbol, SymbolKind}; - use crate::common::spans::Span; - - let mut arena = TypeArena::new(); - let mut interner = NameInterner::new(); - let mut symbols = SymbolArena::new(); - - let my_struct_name = interner.intern("MyStruct"); - let sym_id = symbols.insert(Symbol { - name: my_struct_name, - kind: SymbolKind::Struct, - exported: false, - module: ModuleId(0), - decl_span: Span::new(FileId(0), 0, 0), - }); - - let struct_t = arena.intern_type(TypeKind::Struct { sym: sym_id }); - - let formatted = format_type(struct_t, &arena, &interner, Some(&symbols)); - assert_eq!(formatted, "MyStruct"); - } - - #[test] - fn format_type_struct_fallback() { - let mut arena = TypeArena::new(); - let interner = NameInterner::new(); - let sym_id = SymbolId(42); - - let struct_t = arena.intern_type(TypeKind::Struct { sym: sym_id }); - - let formatted = format_type(struct_t, &arena, &interner, None); - assert_eq!(formatted, "struct#42"); - } -} diff --git a/crates/compiler/prometeu-compiler/src/backend/artifacts.rs b/crates/compiler/prometeu-compiler/src/backend/artifacts.rs deleted file mode 100644 index c67f998c..00000000 --- a/crates/compiler/prometeu-compiler/src/backend/artifacts.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::common::symbols::{ - AnalysisFileTableEntry, AnalysisFileV0, AnalysisModuleEntry, AnalysisNameEntry, AnalysisSymbolEntry, - DebugSymbol, SymbolsFile, -}; -use anyhow::{Context, Result}; -use prometeu_bytecode::disasm::disasm; -use prometeu_bytecode::BytecodeLoader; -use std::fs; -use std::path::Path; - -pub struct Artifacts { - pub rom: Vec, - pub debug_symbols: Vec, - pub lsp_symbols: SymbolsFile, -} - -impl Artifacts { - pub fn new(rom: Vec, debug_symbols: Vec, lsp_symbols: SymbolsFile) -> Self { - Self { rom, debug_symbols, lsp_symbols } - } - - pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { - // 1. Save the main binary - fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?; - - // 2. Export symbols for LSP - if emit_symbols { - let symbols_path = out.with_file_name("symbols.json"); - let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?; - fs::write(&symbols_path, symbols_json)?; - - // Also export analysis.json v0 - let analysis = build_analysis_v0(&self.lsp_symbols); - let analysis_path = out.with_file_name("analysis.json"); - let analysis_json = serde_json::to_string_pretty(&analysis)?; - fs::write(&analysis_path, analysis_json)?; - } - - // 3. Export human-readable disassembly for developer inspection - if emit_disasm { - let disasm_path = out.with_extension("disasm.txt"); - - // Extract the actual bytecode (stripping the industrial PBS\0 header) - let rom_to_disasm = if let Ok(module) = BytecodeLoader::load(&self.rom) { - module.code - } else { - self.rom.clone() - }; - - let instructions = disasm(&rom_to_disasm).map_err(|e| anyhow::anyhow!("Disassembly failed: {}", e))?; - - let mut disasm_text = String::new(); - for instr in instructions { - // Find a matching symbol to show which source line generated this instruction - let symbol = self.debug_symbols.iter().find(|s| s.pc == instr.pc); - let comment = if let Some(s) = symbol { - format!(" ; {}:{}", s.file, s.line) - } else { - "".to_string() - }; - - let operands_str = instr.operands.iter() - .map(|o| format!("{:?}", o)) - .collect::>() - .join(" "); - - disasm_text.push_str(&format!("{:08X} {:?} {}{}\n", instr.pc, instr.opcode, operands_str, comment)); - } - fs::write(disasm_path, disasm_text)?; - } - - Ok(()) - } -} - -fn build_analysis_v0(symbols_file: &SymbolsFile) -> AnalysisFileV0 { - // Gather uniques with deterministic ordering - use std::collections::BTreeMap; - let mut file_ids: BTreeMap = BTreeMap::new(); - let mut name_ids: BTreeMap = BTreeMap::new(); - let mut module_ids: BTreeMap = BTreeMap::new(); - - // First pass: collect all unique entries - for project in &symbols_file.projects { - for s in &project.symbols { - file_ids.entry(s.decl_span.file_uri.clone()).or_insert(0); - name_ids.entry(s.name.clone()).or_insert(0); - module_ids.entry(s.module_path.clone()).or_insert(0); - } - } - - // Assign incremental IDs in key order (deterministic) - let mut next = 0u32; - for v in file_ids.values_mut() { *v = next; next += 1; } - next = 0; for v in name_ids.values_mut() { *v = next; next += 1; } - next = 0; for v in module_ids.values_mut() { *v = next; next += 1; } - - // Build tables - let mut file_table = Vec::with_capacity(file_ids.len()); - for (uri, file_id) in &file_ids { - file_table.push(AnalysisFileTableEntry { file_id: *file_id, uri: uri.clone() }); - } - let mut name_table = Vec::with_capacity(name_ids.len()); - for (name, name_id) in &name_ids { - name_table.push(AnalysisNameEntry { name_id: *name_id, name: name.clone() }); - } - let mut module_table = Vec::with_capacity(module_ids.len()); - for (module_path, module_id) in &module_ids { - module_table.push(AnalysisModuleEntry { module_id: *module_id, module_path: module_path.clone() }); - } - - // Second pass: assign symbol ids in a deterministic global order - let mut flat_symbols: Vec<(&str, &crate::common::symbols::Symbol)> = Vec::new(); - for project in &symbols_file.projects { - for s in &project.symbols { - flat_symbols.push((project.project.as_str(), s)); - } - } - // Deterministic order: by file, start line/col, then module, name, kind - flat_symbols.sort_by(|a, b| { - let sa = a.1; let sb = b.1; - sa.decl_span.file_uri.cmp(&sb.decl_span.file_uri) - .then(sa.decl_span.start.line.cmp(&sb.decl_span.start.line)) - .then(sa.decl_span.start.col.cmp(&sb.decl_span.start.col)) - .then(sa.module_path.cmp(&sb.module_path)) - .then(sa.name.cmp(&sb.name)) - .then(sa.kind.cmp(&sb.kind)) - }); - - let mut symbols = Vec::with_capacity(flat_symbols.len()); - for (i, (_proj, s)) in flat_symbols.iter().enumerate() { - let name_id = *name_ids.get(&s.name).unwrap(); - let module_id = *module_ids.get(&s.module_path).unwrap(); - let _file_id = *file_ids.get(&s.decl_span.file_uri).unwrap(); - symbols.push(AnalysisSymbolEntry { - symbol_id: i as u32, - name_id, - kind: s.kind.clone(), - exported: s.exported, - module_id, - decl_span: s.decl_span.clone(), - }); - } - - AnalysisFileV0 { - schema_version: 0, - compiler_version: symbols_file.compiler_version.clone(), - root_project: symbols_file.root_project.clone(), - file_table, - name_table, - module_table, - symbols, - refs: Vec::new(), - types: Vec::new(), - facts: Default::default(), - diagnostics: Vec::new(), - } -} diff --git a/crates/compiler/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/compiler/prometeu-compiler/src/backend/emit_bytecode.rs deleted file mode 100644 index f6e893fd..00000000 --- a/crates/compiler/prometeu-compiler/src/backend/emit_bytecode.rs +++ /dev/null @@ -1,542 +0,0 @@ -//! # Bytecode Emitter -//! -//! This module is responsible for the final stage of the compilation process: -//! converting the Intermediate Representation (IR) into the binary Prometeu ByteCode (PBC) format. -//! -//! It performs two main tasks: -//! 1. **Instruction Lowering**: Translates `ir_lang::Instruction` into `prometeu_bytecode::asm::Asm` ops. -//! 2. **DebugSymbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations. - -use crate::ir_core::ConstantValue; -use crate::ir_lang; -use crate::ir_lang::instr::InstrKind; -use anyhow::{anyhow, Result}; -use prometeu_bytecode::abi::SourceSpan; -use prometeu_bytecode::asm::{update_pc_by_operand, Asm, Operand}; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta}; - -/// The final output of the code generation phase. -pub struct EmitResult { - /// The serialized binary data of the PBC file. - pub rom: Vec, -} - -pub struct EmitFragments { - pub const_pool: Vec, - pub functions: Vec, - pub code: Vec, - pub debug_info: Option, - pub unresolved_labels: std::collections::HashMap>, -} - -/// Entry point for emitting a bytecode module from the IR. -pub fn emit_module(module: &ir_lang::Module) -> Result { - let fragments = emit_fragments(module)?; - - let exports: Vec<_> = module.functions.iter().enumerate().map(|(i, f)| { - prometeu_bytecode::Export { - symbol: f.name.clone(), - func_idx: i as u32, - } - }).collect(); - - let bytecode_module = BytecodeModule { - version: 0, - const_pool: fragments.const_pool, - functions: fragments.functions, - code: fragments.code, - debug_info: fragments.debug_info, - exports, - }; - - Ok(EmitResult { - rom: bytecode_module.serialize(), - }) -} - -pub fn emit_fragments(module: &ir_lang::Module) -> Result { - let mut emitter = BytecodeEmitter::new(); - - let mut asm_instrs = Vec::new(); - let mut ir_instr_map = Vec::new(); - let function_ranges = emitter.lower_instrs(module, &mut asm_instrs, &mut ir_instr_map)?; - - let pcs = BytecodeEmitter::calculate_pcs(&asm_instrs); - let assemble_res = prometeu_bytecode::asm::assemble_with_unresolved(&asm_instrs).map_err(|e| anyhow!(e))?; - let bytecode = assemble_res.code; - - let mut functions = Vec::new(); - let mut function_names = Vec::new(); - for (i, function) in module.functions.iter().enumerate() { - let (start_idx, last_op_idx) = function_ranges[i]; - let start_pc = pcs[start_idx]; - // `last_op_idx` aponta para o último Asm::Op pertencente à função. O PC de término canônico - // é o PC da próxima entrada em `pcs` (exclusivo). Labels subsequentes não alteram o PC. - let end_pc = if (last_op_idx + 1) < pcs.len() { pcs[last_op_idx + 1] } else { bytecode.len() as u32 }; - - // Nome enriquecido para tooling/analysis: "name@offset+len" - let enriched_name = format!("{}@{}+{}", function.name, start_pc, end_pc - start_pc); - - functions.push(FunctionMeta { - code_offset: start_pc, - code_len: end_pc - start_pc, - param_slots: function.param_slots, - local_slots: function.local_slots, - return_slots: function.return_slots, - max_stack_slots: 0, // Will be filled by verifier - }); - function_names.push((i as u32, enriched_name)); - } - - let mut pc_to_span = Vec::new(); - for (i, instr_opt) in ir_instr_map.iter().enumerate() { - let current_pc = pcs[i]; - if let Some(instr) = instr_opt { - if let Some(span) = &instr.span { - pc_to_span.push((current_pc, SourceSpan { - file_id: span.file.as_u32(), - start: span.start, - end: span.end, - })); - } - } - } - pc_to_span.sort_by_key(|(pc, _)| *pc); - pc_to_span.dedup_by_key(|(pc, _)| *pc); - - Ok(EmitFragments { - const_pool: emitter.constant_pool, - functions, - code: bytecode, - debug_info: Some(DebugInfo { - pc_to_span, - function_names, - }), - unresolved_labels: assemble_res.unresolved_labels, - }) -} - -/// Internal helper for managing the bytecode emission state. -struct BytecodeEmitter { - /// Stores constant values (like strings) that are referenced by instructions. - constant_pool: Vec, -} - -impl BytecodeEmitter { - fn new() -> Self { - Self { - // Index 0 is traditionally reserved for Null in many VMs - constant_pool: vec![ConstantPoolEntry::Null], - } - } - - /// Adds a value to the constant pool if it doesn't exist, returning its unique index. - fn add_constant(&mut self, entry: ConstantPoolEntry) -> u32 { - if let Some(pos) = self.constant_pool.iter().position(|e| e == &entry) { - pos as u32 - } else { - let id = self.constant_pool.len() as u32; - self.constant_pool.push(entry); - id - } - } - - fn add_ir_constant(&mut self, val: &ConstantValue) -> u32 { - let entry = match val { - ConstantValue::Int(v) => ConstantPoolEntry::Int64(*v), - ConstantValue::Float(v) => ConstantPoolEntry::Float64(*v), - ConstantValue::String(s) => ConstantPoolEntry::String(s.clone()), - }; - self.add_constant(entry) - } - - fn lower_instrs<'b>( - &mut self, - module: &'b ir_lang::Module, - asm_instrs: &mut Vec, - ir_instr_map: &mut Vec> - ) -> Result> { - // Cache to map VM IR const ids to emitted constant pool ids - let mut const_id_map: std::collections::HashMap = std::collections::HashMap::new(); - // Build a mapping from VM function id -> index into module.functions - let mut id_to_index = std::collections::HashMap::new(); - let mut func_names = std::collections::HashMap::new(); - for (idx, func) in module.functions.iter().enumerate() { - id_to_index.insert(func.id, idx as u32); - func_names.insert(func.id, func.name.clone()); - } - - let mut ranges = Vec::new(); - - for function in &module.functions { - let start_idx = asm_instrs.len(); - // Each function starts with a label for its entry point. - asm_instrs.push(Asm::Label(function.name.clone())); - ir_instr_map.push(None); - // Track an approximate stack height for this function - let mut stack_height: i32 = 0; - // Nome canônico para o label de término desta função - let end_label = format!("{}::__end", function.name); - // Track last opcode index for this function (to exclude trailing padding/labels) - let mut last_op_idx_in_func: Option = None; - - for instr in &function.body { - let op_start_idx = asm_instrs.len(); - - // Translate each IR instruction to its equivalent Bytecode OpCode. - match &instr.kind { - InstrKind::Nop => asm_instrs.push(Asm::Op(OpCode::Nop, vec![])), - InstrKind::Halt => asm_instrs.push(Asm::Op(OpCode::Halt, vec![])), - InstrKind::PushConst(id) => { - // Map VM const id to emitted const pool id on-demand - let mapped_id = if let Some(mid) = const_id_map.get(id) { - *mid - } else { - let idx = id.0 as usize; - let val = module - .const_pool - .constants - .get(idx) - .ok_or_else(|| anyhow!("Invalid const id {} (pool len {})", id.0, module.const_pool.constants.len()))?; - let mid = self.add_ir_constant(val); - const_id_map.insert(*id, mid); - mid - }; - asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(mapped_id)])); - stack_height += 1; - } - InstrKind::PushBounded(val) => { - asm_instrs.push(Asm::Op(OpCode::PushBounded, vec![Operand::U32(*val)])); - stack_height += 1; - } - InstrKind::PushBool(v) => { - asm_instrs.push(Asm::Op(OpCode::PushBool, vec![Operand::Bool(*v)])); - stack_height += 1; - } - InstrKind::PushNull => { - asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(0)])); - stack_height += 1; - } - InstrKind::Pop => { - asm_instrs.push(Asm::Op(OpCode::Pop, vec![])); - stack_height = (stack_height - 1).max(0); - } - InstrKind::Dup => { - asm_instrs.push(Asm::Op(OpCode::Dup, vec![])); - stack_height += 1; - } - InstrKind::Swap => asm_instrs.push(Asm::Op(OpCode::Swap, vec![])), - InstrKind::Add => { asm_instrs.push(Asm::Op(OpCode::Add, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Sub => { asm_instrs.push(Asm::Op(OpCode::Sub, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Mul => { asm_instrs.push(Asm::Op(OpCode::Mul, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Div => { asm_instrs.push(Asm::Op(OpCode::Div, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Neg => { asm_instrs.push(Asm::Op(OpCode::Neg, vec![])); /* unary */ } - InstrKind::Eq => { asm_instrs.push(Asm::Op(OpCode::Eq, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Neq => { asm_instrs.push(Asm::Op(OpCode::Neq, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Lt => { asm_instrs.push(Asm::Op(OpCode::Lt, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Gt => { asm_instrs.push(Asm::Op(OpCode::Gt, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Lte => { asm_instrs.push(Asm::Op(OpCode::Lte, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::Gte => { asm_instrs.push(Asm::Op(OpCode::Gte, vec![])); stack_height = (stack_height - 1).max(0); } - InstrKind::And => asm_instrs.push(Asm::Op(OpCode::And, vec![])), - InstrKind::Or => asm_instrs.push(Asm::Op(OpCode::Or, vec![])), - InstrKind::Not => asm_instrs.push(Asm::Op(OpCode::Not, vec![])), - InstrKind::BitAnd => asm_instrs.push(Asm::Op(OpCode::BitAnd, vec![])), - InstrKind::BitOr => asm_instrs.push(Asm::Op(OpCode::BitOr, vec![])), - InstrKind::BitXor => asm_instrs.push(Asm::Op(OpCode::BitXor, vec![])), - InstrKind::Shl => asm_instrs.push(Asm::Op(OpCode::Shl, vec![])), - InstrKind::Shr => asm_instrs.push(Asm::Op(OpCode::Shr, vec![])), - InstrKind::LocalLoad { slot } => { - asm_instrs.push(Asm::Op(OpCode::GetLocal, vec![Operand::U32(*slot)])); - stack_height += 1; - } - InstrKind::LocalStore { slot } => { - asm_instrs.push(Asm::Op(OpCode::SetLocal, vec![Operand::U32(*slot)])); - stack_height = (stack_height - 1).max(0); - } - InstrKind::GetGlobal(slot) => { - asm_instrs.push(Asm::Op(OpCode::GetGlobal, vec![Operand::U32(*slot)])); - stack_height += 1; - } - InstrKind::SetGlobal(slot) => { - asm_instrs.push(Asm::Op(OpCode::SetGlobal, vec![Operand::U32(*slot)])); - stack_height = (stack_height - 1).max(0); - } - InstrKind::Jmp(label) => { - let target = if label.0 == "end" { end_label.clone() } else { label.0.clone() }; - asm_instrs.push(Asm::Op(OpCode::Jmp, vec![Operand::RelLabel(target, function.name.clone())])); - } - InstrKind::JmpIfFalse(label) => { - let target = if label.0 == "end" { end_label.clone() } else { label.0.clone() }; - asm_instrs.push(Asm::Op(OpCode::JmpIfFalse, vec![Operand::RelLabel(target, function.name.clone())])); - // VM consumes the condition for JmpIfFalse - stack_height = (stack_height - 1).max(0); - } - InstrKind::Label(label) => { - asm_instrs.push(Asm::Label(label.0.clone())); - // Each labeled block in VM code is a fresh basic block. - // Our IR lowering (core_to_vm) assumes empty evaluation stack at - // block boundaries. Ensure the emitter's internal accounting - // matches that assumption to avoid inserting balancing Pops - // carried over from previous fallthrough paths. - stack_height = 0; - } - InstrKind::Call { func_id, arg_count } => { - // Translate call by function index within this module - if let Some(idx) = id_to_index.get(func_id) { - asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::U32(*idx)])); - stack_height = (stack_height - (*arg_count as i32)).max(0); - } else { - // As a fallback, if we can resolve by name (cross-module import label) - let name = func_names.get(func_id).ok_or_else(|| anyhow!("Undefined function ID: {:?}", func_id))?; - asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(name.clone())])); - stack_height = (stack_height - (*arg_count as i32)).max(0); - } - } - InstrKind::ImportCall { dep_alias, module_path, owner, base_name, sig, arg_count } => { - let display_name = if let Some(o) = owner { - format!("{}.{}", o, base_name) - } else { - base_name.clone() - }; - let label = format!("@{}::{}:{}#sig{}", dep_alias, module_path, display_name, sig.0); - asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(label)])); - stack_height = (stack_height - (*arg_count as i32)).max(0); - } - InstrKind::Ret => { - // Do not emit balancing Pops here. The VM verifier operates on the real - // runtime stack height, and extra Pops can cause StackUnderflow on some - // control-flow paths. Ret should appear when the stack is already in the - // correct state. - asm_instrs.push(Asm::Op(OpCode::Ret, vec![])); - } - InstrKind::Syscall(id) => { - asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)])); - } - InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])), - InstrKind::Alloc { type_id, slots } => { - asm_instrs.push(Asm::Op(OpCode::Alloc, vec![Operand::U32(type_id.0), Operand::U32(*slots)])); - stack_height += *slots as i32; - } - InstrKind::GateLoad { offset } => { - asm_instrs.push(Asm::Op(OpCode::GateLoad, vec![Operand::U32(*offset)])); - stack_height += 1; - } - InstrKind::GateStore { offset } => { - asm_instrs.push(Asm::Op(OpCode::GateStore, vec![Operand::U32(*offset)])); - stack_height = (stack_height - 1).max(0); - } - InstrKind::GateBeginPeek => asm_instrs.push(Asm::Op(OpCode::GateBeginPeek, vec![])), - InstrKind::GateEndPeek => asm_instrs.push(Asm::Op(OpCode::GateEndPeek, vec![])), - InstrKind::GateBeginBorrow => asm_instrs.push(Asm::Op(OpCode::GateBeginBorrow, vec![])), - InstrKind::GateEndBorrow => asm_instrs.push(Asm::Op(OpCode::GateEndBorrow, vec![])), - InstrKind::GateBeginMutate => asm_instrs.push(Asm::Op(OpCode::GateBeginMutate, vec![])), - InstrKind::GateEndMutate => asm_instrs.push(Asm::Op(OpCode::GateEndMutate, vec![])), - InstrKind::GateRetain => asm_instrs.push(Asm::Op(OpCode::GateRetain, vec![])), - InstrKind::GateRelease => asm_instrs.push(Asm::Op(OpCode::GateRelease, vec![])), - } - - let op_end_idx = asm_instrs.len(); - // If we just pushed an Op, record its index as last_op_idx_in_func - if op_end_idx > 0 { - if let Asm::Op(_, _) = &asm_instrs[op_end_idx - 1] { - last_op_idx_in_func = Some(op_end_idx - 1); - } - } - for _ in op_start_idx..op_end_idx { - ir_instr_map.push(Some(instr)); - } - } - // Emite label canônico de término no fim real do corpo - asm_instrs.push(Asm::Label(end_label)); - ir_instr_map.push(None); - // Determine last op index; if function had no ops, fallback to the padding NOP we just injected - let last_op_idx = last_op_idx_in_func.unwrap_or(start_idx); - ranges.push((start_idx, last_op_idx)); - } - Ok(ranges) - } - - fn calculate_pcs(asm_instrs: &[Asm]) -> Vec { - let mut pcs = Vec::with_capacity(asm_instrs.len()); - let mut current_pc = 0u32; - for instr in asm_instrs { - pcs.push(current_pc); - match instr { - Asm::Label(_) => {} - Asm::Op(_opcode, operands) => { - current_pc += 2; - current_pc = update_pc_by_operand(current_pc, operands); - } - } - } - pcs - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_core::const_pool::ConstantValue; - use crate::ir_core::ids::FunctionId; - use crate::ir_lang::instr::{InstrKind, Instruction}; - use crate::ir_lang::module::{Function, Module}; - use crate::ir_lang::types::Type; - use prometeu_bytecode::{BytecodeLoader, ConstantPoolEntry}; - use prometeu_bytecode::disasm::disasm; - - #[test] - fn test_emit_module_with_const_pool() { - let mut module = Module::new("test".to_string()); - - let id_int = module.const_pool.insert(ConstantValue::Int(12345)); - let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string())); - - let function = Function { - id: FunctionId(0), - name: "main".to_string(), - sig: crate::ir_core::SigId(0), - params: vec![], - return_type: Type::Void, - body: vec![ - Instruction::new(InstrKind::PushConst(ir_lang::ConstId(id_int.0)), None), - Instruction::new(InstrKind::PushConst(ir_lang::ConstId(id_str.0)), None), - Instruction::new(InstrKind::Ret, None), - ], - param_slots: 0, - local_slots: 0, - return_slots: 0, - }; - - module.functions.push(function); - - let result = emit_module(&module).expect("Failed to emit module"); - - let pbc = BytecodeLoader::load(&result.rom).expect("Failed to parse emitted PBC"); - - assert_eq!(pbc.const_pool.len(), 3); - assert_eq!(pbc.const_pool[0], ConstantPoolEntry::Null); - assert_eq!(pbc.const_pool[1], ConstantPoolEntry::Int64(12345)); - assert_eq!(pbc.const_pool[2], ConstantPoolEntry::String("hello".to_string())); - } - - // #[test] - // fn test_stack_is_reset_on_label_boundaries() { - // // This builds a function with control flow that would leave a value - // // on the stack before a jump. The target block starts with a Label - // // and immediately returns. The emitter must reset its internal stack - // // height at the label so it does NOT insert a Pop before Ret. - // - // let mut module = Module::new("label_stack_reset".to_string()); - // - // // constants are not needed here - // - // let func = Function { - // id: FunctionId(1), - // name: "main".to_string(), - // sig: crate::ir_core::SigId(0), - // params: vec![], - // return_type: Type::Void, - // body: vec![ - // // entry block (block_0) - // Instruction::new(InstrKind::PushConst(ir_lang::ConstId(module.const_pool.insert(ConstantValue::Int(1)).0)), None), - // // jump to else, leaving one value on the emitter's stack accounting - // Instruction::new(InstrKind::Jmp(ir_lang::Label("else".to_string())), None), - // // then block (unreachable, but included for shape) - // Instruction::new(InstrKind::Label(ir_lang::Label("then".to_string())), None), - // Instruction::new(InstrKind::Ret, None), - // // else block: must not start with an extra Pop - // Instruction::new(InstrKind::Label(ir_lang::Label("else".to_string())), None), - // Instruction::new(InstrKind::Ret, None), - // ], - // param_slots: 0, - // local_slots: 0, - // return_slots: 0, - // }; - // - // module.functions.push(func); - // - // let result = emit_module(&module).expect("emit failed"); - // let pbc = BytecodeLoader::load(&result.rom).expect("pbc load failed"); - // let instrs = disasm(&pbc.code).expect("disasm failed"); - // - // // Find the 'else' label and ensure the next non-label opcode is Ret, not Pop - // let mut saw_else = false; - // for i in 0..instrs.len() - 1 { - // if let prometeu_bytecode::disasm::DisasmOp { opcode, .. } = &instrs[i] { - // if format!("{:?}", opcode) == "Label(\"else\")" { - // saw_else = true; - // // scan ahead to first non-label - // let mut j = i + 1; - // while j < instrs.len() && format!("{:?}", instrs[j].opcode).starts_with("Label(") { - // j += 1; - // } - // assert!(j < instrs.len(), "No instruction after else label"); - // let next = format!("{:?}", instrs[j].opcode); - // assert_ne!(next.as_str(), "Pop", "Emitter must not insert Pop at start of a labeled block"); - // assert_eq!(next.as_str(), "Ret", "Expected Ret directly after label when function is void"); - // break; - // } - // } - // } - // assert!(saw_else, "Expected to find else label in emitted bytecode"); - // } - - #[test] - fn test_jmp_if_false_does_not_emit_pop() { - // Build a tiny VM IR module with a conditional jump. - // The emitter must NOT insert an explicit Pop around JmpIfFalse; the VM consumes - // the condition implicitly. - - let mut module = Module::new("test_jif".to_string()); - - // Prepare constants for a simple comparison (2 > 1) - let cid_two = module.const_pool.insert(ConstantValue::Int(2)); - let cid_one = module.const_pool.insert(ConstantValue::Int(1)); - - let func = Function { - id: FunctionId(1), - name: "main".to_string(), - sig: crate::ir_core::SigId(0), - params: vec![], - return_type: Type::Void, - body: vec![ - // cond: 2 > 1 - Instruction::new(InstrKind::PushConst(ir_lang::ConstId(cid_two.0)), None), - Instruction::new(InstrKind::PushConst(ir_lang::ConstId(cid_one.0)), None), - Instruction::new(InstrKind::Gt, None), - // if !cond -> else - Instruction::new(InstrKind::JmpIfFalse(ir_lang::Label("else".to_string())), None), - // then: jump to merge - Instruction::new(InstrKind::Jmp(ir_lang::Label("then".to_string())), None), - // else block - Instruction::new(InstrKind::Label(ir_lang::Label("else".to_string())), None), - Instruction::new(InstrKind::Ret, None), - // then block - Instruction::new(InstrKind::Label(ir_lang::Label("then".to_string())), None), - Instruction::new(InstrKind::Ret, None), - ], - param_slots: 0, - local_slots: 0, - return_slots: 0, - }; - - module.functions.push(func); - - let result = emit_module(&module).expect("Failed to emit module"); - let pbc = BytecodeLoader::load(&result.rom).expect("Failed to parse emitted PBC"); - let instrs = disasm(&pbc.code).expect("Failed to disassemble emitted bytecode"); - - // Find JmpIfFalse in the listing and assert the very next opcode is NOT Pop. - let mut found_jif = false; - for i in 0..instrs.len().saturating_sub(1) { - if format!("{:?}", instrs[i].opcode) == "JmpIfFalse" { - found_jif = true; - let next_op = format!("{:?}", instrs[i + 1].opcode); - assert_ne!(next_op.as_str(), "Pop", "Emitter must not insert Pop after JmpIfFalse"); - break; - } - } - assert!(found_jif, "Expected JmpIfFalse in emitted code but none was found"); - } -} diff --git a/crates/compiler/prometeu-compiler/src/backend/mod.rs b/crates/compiler/prometeu-compiler/src/backend/mod.rs deleted file mode 100644 index 94e217ed..00000000 --- a/crates/compiler/prometeu-compiler/src/backend/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod emit_bytecode; -pub mod artifacts; - -pub use artifacts::Artifacts; -pub use emit_bytecode::EmitResult; -pub use emit_bytecode::{emit_fragments, emit_module, EmitFragments}; diff --git a/crates/compiler/prometeu-compiler/src/building/linker.rs b/crates/compiler/prometeu-compiler/src/building/linker.rs deleted file mode 100644 index 0b7de7b5..00000000 --- a/crates/compiler/prometeu-compiler/src/building/linker.rs +++ /dev/null @@ -1,725 +0,0 @@ -use crate::building::output::CompiledModule; -use crate::building::plan::BuildStep; -use prometeu_abi::{ProgramImage, Value}; -use prometeu_analysis::ids::ProjectId; -use prometeu_bytecode::decoder::decode_next; -use prometeu_bytecode::layout; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::{ConstantPoolEntry, DebugInfo}; -use std::collections::HashMap; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum LinkError { - OutOfBounds(usize, usize), - UnresolvedSymbol(String), - DuplicateExport(String), - IncompatibleSymbolSignature(String), -} - -impl std::fmt::Display for LinkError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LinkError::OutOfBounds(pos, len) => write!(f, "Out of bounds: pos={} len={}", pos, len), - LinkError::UnresolvedSymbol(s) => write!(f, "Unresolved symbol: {}", s), - LinkError::DuplicateExport(s) => write!(f, "Duplicate export: {}", s), - LinkError::IncompatibleSymbolSignature(s) => write!(f, "Incompatible symbol signature: {}", s), - } - } -} - -impl std::error::Error for LinkError {} - -pub struct Linker; - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct ConstantPoolBitKey(Vec); - -impl ConstantPoolBitKey { - fn from_entry(entry: &ConstantPoolEntry) -> Self { - match entry { - ConstantPoolEntry::Null => Self(vec![0]), - ConstantPoolEntry::Int64(v) => { - let mut b = vec![1]; - b.extend_from_slice(&v.to_le_bytes()); - Self(b) - } - ConstantPoolEntry::Float64(v) => { - let mut b = vec![2]; - b.extend_from_slice(&v.to_bits().to_le_bytes()); - Self(b) - } - ConstantPoolEntry::Boolean(v) => { - Self(vec![3, if *v { 1 } else { 0 }]) - } - ConstantPoolEntry::String(v) => { - let mut b = vec![4]; - b.extend_from_slice(v.as_bytes()); - Self(b) - } - ConstantPoolEntry::Int32(v) => { - let mut b = vec![5]; - b.extend_from_slice(&v.to_le_bytes()); - Self(b) - } - } - } -} - -impl Linker { - - pub fn link(modules: Vec, steps: Vec) -> Result { - if modules.len() != steps.len() { - return Err(LinkError::IncompatibleSymbolSignature(format!("Module count ({}) does not match build steps count ({})", modules.len(), steps.len()))); - } - - let mut combined_code = Vec::new(); - let mut combined_functions = Vec::new(); - let mut combined_constants = Vec::new(); - let mut constant_map: HashMap = HashMap::new(); - - // Debug info merging - let mut combined_pc_to_span = Vec::new(); - let mut combined_function_names = Vec::new(); - - // 1. DebugSymbol resolution maps: - // - canonical: (ProjectKey, module_path, ExportItem) -> func_idx - // - compatibility (Phase 03 transitional): (ProjectKey, module_path, short_name) -> func_idx - let mut global_symbols = HashMap::new(); - let mut global_symbols_str = HashMap::new(); - - let mut module_code_offsets = Vec::with_capacity(modules.len()); - let mut module_function_offsets = Vec::with_capacity(modules.len()); - - // Map ProjectKey to index - let _project_to_idx: HashMap<_, _> = modules.iter().enumerate().map(|(i, m)| (m.project_id.clone(), i)).collect(); - - // PASS 1: Collect exports and calculate offsets - for (_i, module) in modules.iter().enumerate() { - let code_offset = combined_code.len() as u32; - let function_offset = combined_functions.len() as u32; - - module_code_offsets.push(code_offset); - module_function_offsets.push(function_offset); - - for (key, meta) in &module.exports { - if let Some(local_func_idx) = meta.func_idx { - let global_func_idx = function_offset + local_func_idx; - // Note: Use a tuple as key for clarity - let symbol_id = (module.project_id.clone(), key.module_path.clone(), key.item.clone()); - - if global_symbols.contains_key(&symbol_id) { - return Err(LinkError::DuplicateExport(format!( - "Project {:?} export {}:{:?} already defined", - symbol_id.0, symbol_id.1, symbol_id.2 - ))); - } - // Canonical mapping - global_symbols.insert(symbol_id, global_func_idx); - - // Compatibility string mapping (short name only) - let short_name = match &key.item { - language_api::types::ExportItem::Function { fn_key } => fn_key.name.as_str().to_string(), - language_api::types::ExportItem::Service { name } => name.as_str().to_string(), - language_api::types::ExportItem::Type { name } => name.as_str().to_string(), - }; - let symbol_id_str = (module.project_id.clone(), key.module_path.clone(), short_name); - global_symbols_str.insert(symbol_id_str, global_func_idx); - } - } - - combined_code.extend_from_slice(&module.code); - for func in &module.function_metas { - let mut relocated = func.clone(); - relocated.code_offset += code_offset; - combined_functions.push(relocated); - } - - if let Some(debug) = &module.debug_info { - for (pc, span) in &debug.pc_to_span { - combined_pc_to_span.push((code_offset + pc, span.clone())); - } - for (func_idx, name) in &debug.function_names { - combined_function_names.push((function_offset + func_idx, name.clone())); - } - } - } - - // PASS 2: Relocate constants and patch CALLs - for (i, module) in modules.iter().enumerate() { - let step = &steps[i]; - let code_offset = module_code_offsets[i] as usize; - - // Map local constant indices to global constant indices - let mut local_to_global_const = Vec::with_capacity(module.const_pool.len()); - for entry in &module.const_pool { - let bit_key = ConstantPoolBitKey::from_entry(entry); - if let Some(&global_idx) = constant_map.get(&bit_key) { - local_to_global_const.push(global_idx); - } else { - let global_idx = combined_constants.len() as u32; - combined_constants.push(match entry { - ConstantPoolEntry::Null => Value::Null, - ConstantPoolEntry::Int64(v) => Value::Int64(*v), - ConstantPoolEntry::Float64(v) => Value::Float(*v), - ConstantPoolEntry::Boolean(v) => Value::Boolean(*v), - ConstantPoolEntry::String(v) => Value::String(v.clone()), - ConstantPoolEntry::Int32(v) => Value::Int32(*v), - }); - constant_map.insert(bit_key, global_idx); - local_to_global_const.push(global_idx); - } - } - - // Patch imports - for import in &module.imports { - // Resolve the dependency project id. If alias is missing/self, try all deps as fallback. - let mut candidate_projects: Vec<&ProjectId> = Vec::new(); - if import.key.dep_alias == "self" || import.key.dep_alias.is_empty() { - candidate_projects.push(&module.project_id); - for (_alias, pid) in &step.deps { candidate_projects.push(pid); } - } else { - let pid = step.deps.get(&import.key.dep_alias) - .ok_or_else(|| LinkError::UnresolvedSymbol(format!("Dependency alias '{}' not found in project {:?}", import.key.dep_alias, module.project_id)))?; - candidate_projects.push(pid); - } - - let mut resolved_idx: Option = None; - for pid in candidate_projects { - let pid_val: ProjectId = (*pid).clone(); - let key = (pid_val, import.key.module_path.clone(), import.key.symbol_name.clone()); - if let Some(&idx) = global_symbols_str.get(&key) { - resolved_idx = Some(idx); - break; - } - } - let target_func_idx = resolved_idx.ok_or_else(|| { - LinkError::UnresolvedSymbol(format!( - "DebugSymbol '{}:{}' not found in any candidate project (self={:?}, deps={:?})", - import.key.module_path, - import.key.symbol_name, - module.project_id, - step.deps - )) - })?; - - for &reloc_pc in &import.relocation_pcs { - // `reloc_pc` aponta para o INÍCIO do operando (após os 2 bytes do opcode), - // conforme `assemble_with_unresolved` grava `pc` antes de escrever o U32. - // Portanto, devemos escrever exatamente em `absolute_pc`. - let absolute_pc = code_offset + reloc_pc as usize; - if absolute_pc + 4 <= combined_code.len() { - combined_code[absolute_pc..absolute_pc+4] - .copy_from_slice(&target_func_idx.to_le_bytes()); - } - } - } - - let mut pc = code_offset; - let end = code_offset + module.code.len(); - while pc < end { - // Scope the immutable borrow from decode_next so we can mutate combined_code afterwards - let (opcode, next_pc, imm_start, imm_u32_opt) = { - match decode_next(pc, &combined_code) { - Ok(instr) => { - let opcode = instr.opcode; - let next_pc = instr.next_pc; - let imm_start = instr.pc + 2; // start of immediate payload - let imm_u32_opt = match opcode { - OpCode::PushConst | OpCode::Call => { - match instr.imm_u32() { - Ok(v) => Some(v), - Err(_) => None, - } - } - _ => None, - }; - (opcode, next_pc, imm_start, imm_u32_opt) - } - Err(e) => { - return Err(LinkError::IncompatibleSymbolSignature(format!( - "Bytecode decode error at pc {}: {:?}", - pc - code_offset, e - ))); - } - } - }; - - match opcode { - OpCode::PushConst => { - let local_idx = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!( - "Invalid PUSH_CONST immediate at pc {}", - pc - code_offset - )))? as usize; - if let Some(&global_idx) = local_to_global_const.get(local_idx) { - patch_u32_at(&mut combined_code, imm_start, &|_| global_idx); - } - } - OpCode::Call => { - let local_func_idx = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!( - "Invalid CALL immediate at pc {}", - pc - code_offset - )))?; - // Determine if this CALL site corresponds to an import relocation. - let reloc_rel_pc = (imm_start - code_offset) as u32; - let is_import = module - .imports - .iter() - .any(|imp| imp.relocation_pcs.contains(&reloc_rel_pc)); - if !is_import { - let global_func_idx = module_function_offsets[i] + local_func_idx; - patch_u32_at(&mut combined_code, imm_start, &|_| global_func_idx); - } - } - // Branches are strictly function-relative. Do NOT relocate or inspect immediates. - // The emitter encodes `target_rel = label - func_start` and the verifier enforces it. - OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => { /* no-op */ } - _ => {} - } - - pc = next_pc; - } - } - - // Final Exports map for ProgramImage (String -> func_idx) - // Only including exports from the ROOT project (the last one in build plan usually) - // In PBS v0, exports are name -> func_id. - let mut final_exports = HashMap::new(); - if let Some(root_module) = modules.last() { - for (key, meta) in &root_module.exports { - if let Some(local_func_idx) = meta.func_idx { - let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx; - final_exports.insert(format!("{}:{:?}", key.module_path, key.item), global_func_idx); - // Also provide short name for root module exports to facilitate entrypoint resolution. - // For canonical items, we fall back to the `Debug` representation without the module path. - let short = format!("{:?}", key.item); - if !final_exports.contains_key(&short) { - final_exports.insert(short, global_func_idx); - } - } - } - } - - // v0: Fallback export for entrypoint `frame` (root module) - if !final_exports.iter().any(|(name, _)| name.ends_with(":frame") || name == "frame") { - if let Some(&root_offset) = module_function_offsets.last() { - if let Some((idx, _)) = combined_function_names.iter().find(|(i, name)| *i >= root_offset && name == "frame") { - final_exports.insert("frame".to_string(), *idx); - final_exports.insert("src/main/modules:frame".to_string(), *idx); - } - } - } - - // Ajuste final: se os nomes de função no DebugInfo estiverem enriquecidos no formato - // "name@offset+len", alinhar apenas o `code_len` de `combined_functions[idx]` a esses - // valores (os offsets do DebugInfo são locais ao módulo antes do link). Mantemos o - // `code_offset` já realocado durante o PASS 1. - // Track which function metas received a precise code_len from DebugInfo - let mut has_precise_len: Vec = vec![false; combined_functions.len()]; - - for (idx, name) in &combined_function_names { - if let Some((base, rest)) = name.split_once('@') { - let mut parts = rest.split('+'); - if let (Some(off_str), Some(len_str)) = (parts.next(), parts.next()) { - if let (Ok(_off), Ok(len)) = (off_str.parse::(), len_str.parse::()) { - if let Some(meta) = combined_functions.get_mut(*idx as usize) { - let old_off = meta.code_offset; - let old_len = meta.code_len; - meta.code_len = len; - has_precise_len[*idx as usize] = true; - eprintln!( - "[Linker][debug] Align len idx={} name={} -> code_offset {} (kept) | code_len {} -> {}", - idx, base, old_off, old_len, len - ); - } - } - } - } - } - - // Ensure DebugInfo also contains plain base names alongside enriched names for easy lookup. - // For any entry of form "name@off+len", also add (idx, "name") if missing. - let mut plain_names_to_add: Vec<(u32, String)> = Vec::new(); - for (idx, name) in &combined_function_names { - if let Some((base, _)) = name.split_once('@') { - let already_has_plain = combined_function_names.iter().any(|(i, n)| i == idx && n == base); - if !already_has_plain { - plain_names_to_add.push((*idx, base.to_string())); - } - } - } - combined_function_names.extend(plain_names_to_add); - - // Recompute code_len ONLY for functions that did NOT receive a precise length from DebugInfo. - // This preserves exact ends emitted by the compiler while still filling lengths for functions - // that lack enriched annotations. - let total_len = combined_code.len(); - let layouts = layout::compute_function_layouts(&combined_functions, total_len); - for i in 0..combined_functions.len() { - if !has_precise_len.get(i).copied().unwrap_or(false) { - let start = layouts[i].start; - let end = layouts[i].end; - combined_functions[i].code_len = end.saturating_sub(start) as u32; - } - } - - // Removido padding específico de `frame`; o emissor passou a garantir que o label de término - // esteja no ponto exato do fim do corpo, e, quando necessário, insere NOPs reais antes do fim. - - // Garantir export do entry point 'frame' mesmo com nomes enriquecidos no DebugInfo. - if !final_exports.contains_key("frame") { - if let Some((idx, _name)) = combined_function_names.iter().find(|(i, name)| { - let base = name.split('@').next().unwrap_or(name.as_str()); - let i_usize = *i as usize; - (base == "frame" || base.ends_with(":frame")) - && combined_functions.get(i_usize).map(|m| m.param_slots == 0 && m.return_slots == 0).unwrap_or(false) - }) { - final_exports.insert("frame".to_string(), *idx); - final_exports.insert("src/main/modules:frame".to_string(), *idx); - } - } - - let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() { - None - } else { - // Ensure entry-point name mapping is present for easy lookup in DebugInfo - if let Some(frame_idx) = final_exports.get("frame") { - if !combined_function_names.iter().any(|(i, n)| i == frame_idx && n == "frame") { - combined_function_names.push((*frame_idx, "frame".to_string())); - } - } - Some(DebugInfo { - pc_to_span: combined_pc_to_span, - function_names: combined_function_names, - }) - }; - - Ok(ProgramImage::new( - combined_code, - combined_constants, - combined_functions, - combined_debug_info, - final_exports, - )) - } -} - -fn patch_u32_at( - buf: &mut [u8], - pos: usize, - f: impl FnOnce(u32) -> u32, -) -> Result<(), LinkError> { - let current = prometeu_bytecode::io::read_u32_le(buf, pos).ok_or(LinkError::OutOfBounds(pos, buf.len()))?; - let next = f(current); - prometeu_bytecode::io::write_u32_le(buf, pos, next).ok_or(LinkError::OutOfBounds(pos, buf.len()))?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata}; - use crate::building::plan::BuildTarget; - use crate::deps::resolver::ProjectKey; - use prometeu_analysis::ids::ProjectId; - use prometeu_bytecode::opcode::OpCode; - use prometeu_bytecode::FunctionMeta; - use std::collections::BTreeMap; - - #[test] - fn test_link_root_and_lib() { - let lib_key = ProjectKey { name: "lib".into(), version: "1.0.0".into() }; - let root_key = ProjectKey { name: "root".into(), version: "1.0.0".into() }; - let lib_id = ProjectId(0); - let root_id = ProjectId(1); - - // Lib module: exports 'add' - let mut lib_code = Vec::new(); - lib_code.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); - lib_code.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); - - let mut lib_exports = BTreeMap::new(); - use language_api::types::{CanonicalFnKey, ExportItem, ItemName, SignatureRef}; - // NOTE: ItemName validation may enforce capitalized identifiers; for test purposes use a canonical valid name. - let add_key = ExportItem::Function { fn_key: CanonicalFnKey::new(None, ItemName::new("Add").unwrap(), SignatureRef(0)) }; - lib_exports.insert(ExportKey { module_path: "math".into(), item: add_key }, ExportMetadata { func_idx: Some(0), is_host: false, ty: None }); - - let lib_module = CompiledModule { - project_id: lib_id, - project_key: lib_key.clone(), - target: BuildTarget::Main, - exports: lib_exports, - imports: vec![], - const_pool: vec![], - code: lib_code, - function_metas: vec![FunctionMeta { - code_offset: 0, - code_len: 4, - ..Default::default() - }], - debug_info: None, - symbols: vec![], - }; - - // Root module: calls 'lib::math:add' - let mut root_code = Vec::new(); - root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - root_code.extend_from_slice(&10i32.to_le_bytes()); - root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - root_code.extend_from_slice(&20i32.to_le_bytes()); - // Call lib:math:add - let call_pc = root_code.len() as u32; - root_code.extend_from_slice(&(OpCode::Call as u16).to_le_bytes()); - root_code.extend_from_slice(&0u32.to_le_bytes()); // placeholder - root_code.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - - let root_imports = vec![ImportMetadata { - key: ImportKey { - dep_alias: "mylib".into(), - module_path: "math".into(), - symbol_name: "Add".into(), - }, - relocation_pcs: vec![call_pc], - }]; - - let root_module = CompiledModule { - project_id: root_id, - project_key: root_key.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: root_imports, - const_pool: vec![], - code: root_code, - function_metas: vec![FunctionMeta { - code_offset: 0, - code_len: 20, - ..Default::default() - }], - debug_info: None, - symbols: vec![], - }; - - let lib_step = BuildStep { - project_id: lib_id, - project_key: lib_key.clone(), - project_dir: "".into(), - target: BuildTarget::Main, - sources: vec![], - deps: BTreeMap::new(), - }; - - let mut root_deps: BTreeMap = BTreeMap::new(); - root_deps.insert("mylib".into(), lib_id); - - let root_step = BuildStep { - project_id: root_id, - project_key: root_key.clone(), - project_dir: "".into(), - target: BuildTarget::Main, - sources: vec![], - deps: root_deps, - }; - - let result = Linker::link(vec![lib_module, root_module], vec![lib_step, root_step]).unwrap(); - - assert_eq!(result.functions.len(), 2); - // lib:add is func 0 - // root:main is func 1 - - // lib_code length is 4. - // Root code starts at 4. - // CALL was at root_code offset 12. - // Absolute PC of CALL: 4 + 12 = 16. - // Immediate is at 16 + 2 = 18. - let patched_func_idx = u32::from_le_bytes(result.rom[18..22].try_into().unwrap()); - assert_eq!(patched_func_idx, 0); // Points to lib:add - } - - #[test] - fn test_link_const_deduplication() { - let key = ProjectKey { name: "test".into(), version: "1.0.0".into() }; - let id = ProjectId(0); - let step = BuildStep { project_id: id, project_key: key.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; - - let m1 = CompiledModule { - project_id: id, - project_key: key.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![ConstantPoolEntry::Int32(42), ConstantPoolEntry::String("hello".into())], - code: vec![], - function_metas: vec![], - debug_info: None, - symbols: vec![], - }; - - let m2 = CompiledModule { - project_id: id, - project_key: key.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![ConstantPoolEntry::String("hello".into()), ConstantPoolEntry::Int32(99)], - code: vec![], - function_metas: vec![], - debug_info: None, - symbols: vec![], - }; - - let result = Linker::link(vec![m1, m2], vec![step.clone(), step]).unwrap(); - - // Constants should be: 42, "hello", 99 - assert_eq!(result.constant_pool.len(), 3); - assert_eq!(result.constant_pool[0], Value::Int32(42)); - assert_eq!(result.constant_pool[1], Value::String("hello".into())); - assert_eq!(result.constant_pool[2], Value::Int32(99)); - } - - #[test] - fn test_jump_relocation_across_modules() { - // Module 1: small stub to create a non-zero code offset for module 2 - let key1 = ProjectKey { name: "m1".into(), version: "1.0.0".into() }; - let id1 = ProjectId(0); - let step1 = BuildStep { project_id: id1, project_key: key1.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; - - let mut code1 = Vec::new(); - code1.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); - code1.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); - let m1 = CompiledModule { - project_id: id1, - project_key: key1.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![], - code: code1.clone(), - function_metas: vec![FunctionMeta { code_offset: 0, code_len: code1.len() as u32, ..Default::default() }], - debug_info: None, - symbols: vec![], - }; - - // Module 2: contains an unconditional JMP and a conditional JMP_IF_TRUE with local targets - let key2 = ProjectKey { name: "m2".into(), version: "1.0.0".into() }; - let id2 = ProjectId(1); - let step2 = BuildStep { project_id: id2, project_key: key2.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; - - let mut code2 = Vec::new(); - // Unconditional JMP to local target 0 (module-local start) - let jmp_pc = code2.len() as u32; // where opcode will be placed - code2.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); - code2.extend_from_slice(&0u32.to_le_bytes()); - - // PushBool true; then conditional jump to local target 0 - code2.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); - code2.push(1u8); - let cjmp_pc = code2.len() as u32; - code2.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes()); - code2.extend_from_slice(&0u32.to_le_bytes()); - - // End with HALT so VM would stop if executed - code2.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - - let m2 = CompiledModule { - project_id: id2, - project_key: key2.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![], - code: code2.clone(), - function_metas: vec![FunctionMeta { code_offset: 0, code_len: code2.len() as u32, ..Default::default() }], - debug_info: None, - symbols: vec![], - }; - - // Link with order [m1, m2] - let result = Linker::link(vec![m1, m2], vec![step1, step2]).unwrap(); - - // Module 2's code starts after module 1's code - let module2_offset = code1.len() as u32; - - // Verify that the JMP immediate remains function-relative (0), no relocation applied - let jmp_abs_pc = module2_offset as usize + jmp_pc as usize; - let jmp_imm_off = jmp_abs_pc + 2; // skip opcode - let jmp_patched = u32::from_le_bytes(result.rom[jmp_imm_off..jmp_imm_off+4].try_into().unwrap()); - assert_eq!(jmp_patched, 0); - - // Verify that the conditional JMP immediate also remains function-relative (0) - let cjmp_abs_pc = module2_offset as usize + cjmp_pc as usize; - let cjmp_imm_off = cjmp_abs_pc + 2; - let cjmp_patched = u32::from_le_bytes(result.rom[cjmp_imm_off..cjmp_imm_off+4].try_into().unwrap()); - assert_eq!(cjmp_patched, 0); - } - - #[test] - fn test_jump_link_order_invariance() { - // Same setup as previous test, but link order is [m2, m1] - let key1 = ProjectKey { name: "m1".into(), version: "1.0.0".into() }; - let id1 = ProjectId(0); - let step1 = BuildStep { project_id: id1, project_key: key1.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; - - let mut code1 = Vec::new(); - code1.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); - code1.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); - let m1 = CompiledModule { - project_id: id1, - project_key: key1.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![], - code: code1.clone(), - function_metas: vec![FunctionMeta { code_offset: 0, code_len: code1.len() as u32, ..Default::default() }], - debug_info: None, - symbols: vec![], - }; - - let key2 = ProjectKey { name: "m2".into(), version: "1.0.0".into() }; - let id2 = ProjectId(1); - let step2 = BuildStep { project_id: id2, project_key: key2.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; - - let mut code2 = Vec::new(); - let jmp_pc = code2.len() as u32; // where opcode will be placed - code2.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); - code2.extend_from_slice(&0u32.to_le_bytes()); - - code2.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); - code2.push(1u8); - let cjmp_pc = code2.len() as u32; - code2.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes()); - code2.extend_from_slice(&0u32.to_le_bytes()); - - code2.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - - let m2 = CompiledModule { - project_id: id2, - project_key: key2.clone(), - target: BuildTarget::Main, - exports: BTreeMap::new(), - imports: vec![], - const_pool: vec![], - code: code2.clone(), - function_metas: vec![FunctionMeta { code_offset: 0, code_len: code2.len() as u32, ..Default::default() }], - debug_info: None, - symbols: vec![], - }; - - // Link with order [m2, m1] - let result = Linker::link(vec![m2, m1], vec![step2, step1]).unwrap(); - - // Module 2 is now at offset 0 - let module2_offset = 0usize; - - // Verify that the JMP immediate remains function-relative (0), no relocation applied - let jmp_abs_pc = module2_offset + jmp_pc as usize; - let jmp_imm_off = jmp_abs_pc + 2; // skip opcode - let jmp_patched = u32::from_le_bytes(result.rom[jmp_imm_off..jmp_imm_off+4].try_into().unwrap()); - assert_eq!(jmp_patched, 0); - - // Verify that the conditional JMP immediate also remains function-relative (0) - let cjmp_abs_pc = module2_offset + cjmp_pc as usize; - let cjmp_imm_off = cjmp_abs_pc + 2; - let cjmp_patched = u32::from_le_bytes(result.rom[cjmp_imm_off..cjmp_imm_off+4].try_into().unwrap()); - assert_eq!(cjmp_patched, 0); - } -} diff --git a/crates/compiler/prometeu-compiler/src/building/mod.rs b/crates/compiler/prometeu-compiler/src/building/mod.rs deleted file mode 100644 index 4c013cec..00000000 --- a/crates/compiler/prometeu-compiler/src/building/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod plan; -pub mod output; -pub mod linker; -pub mod orchestrator; - -// Compile-time boundary guard: Backend modules must not import PBS directly. -// This doctest will fail to compile if someone tries to `use crate::frontends::pbs` from here. -// It is lightweight and runs with `cargo test`. -/// ```compile_fail -/// use crate::frontends::pbs; // Backend must not depend on PBS directly -/// # let _ = &pbs; // ensure the import is actually used so the check is meaningful -/// ``` -mod __backend_boundary_guard {} diff --git a/crates/compiler/prometeu-compiler/src/building/orchestrator.rs b/crates/compiler/prometeu-compiler/src/building/orchestrator.rs deleted file mode 100644 index 0be06704..00000000 --- a/crates/compiler/prometeu-compiler/src/building/orchestrator.rs +++ /dev/null @@ -1,302 +0,0 @@ -use crate::building::linker::{LinkError, Linker}; -use crate::building::output::{compile_project, CompileError}; -use language_api::traits::Frontend as CanonFrontend; -use crate::building::plan::{BuildPlan, BuildTarget}; -use crate::common::diagnostics::DiagnosticBundle; -use crate::common::files::FileManager; -use crate::deps::resolver::ResolvedGraph; -use prometeu_abi::ProgramImage; -use std::collections::HashMap; - -#[derive(Debug)] -pub enum BuildError { - Compile(CompileError), - Link(LinkError), -} - -#[derive(Debug, Clone)] -pub struct BuildResult { - pub image: ProgramImage, - pub file_manager: FileManager, - pub symbols: Vec, -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BuildError::Compile(e) => write!(f, "Compile error: {}", e), - BuildError::Link(e) => write!(f, "Link error: {}", e), - } - } -} - -impl std::error::Error for BuildError {} - -impl From for BuildError { - fn from(e: CompileError) -> Self { - BuildError::Compile(e) - } -} - -impl From for BuildError { - fn from(e: LinkError) -> Self { - BuildError::Link(e) - } -} - -pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget, fe: &dyn CanonFrontend) -> Result { - let plan = BuildPlan::from_graph(graph, target); - let mut compiled_modules = HashMap::new(); - let mut modules_in_order = Vec::new(); - let mut file_manager = FileManager::new(); - - for step in &plan.steps { - let compiled = compile_project(step.clone(), &compiled_modules, fe, &mut file_manager)?; - compiled_modules.insert(step.project_id.clone(), compiled.clone()); - modules_in_order.push(compiled); - } - - // Validate PBS entry point only for the root project (last step in plan order) - if let Some(root_step) = plan.steps.last() { - // 1) Ensure the root project contains file src/main/modules/main.pbs - let main_pbs_path = root_step.project_dir.join("src/main/modules/main.pbs"); - let has_main_pbs = main_pbs_path.exists(); - if !has_main_pbs { - return Err(BuildError::Compile(CompileError::Frontend( - DiagnosticBundle::error( - "E_MISSING_ENTRY_POINT_FILE", - "Root project must contain src/main/modules/main.pbs".to_string(), - crate::common::spans::Span::new(crate::common::spans::FileId::INVALID, 0, 0), - ), - ))); - } - - // 2) Ensure that file declares fn frame(): void (no params) - // We validate at the bytecode metadata level using function names and signature slots. - if let Some(root_compiled) = compiled_modules.get(&root_step.project_id) { - // Instrumentação: listar nomes de funções e metas do módulo root - if let Some(di) = &root_compiled.debug_info { - // Suprimir logs verbosos na versão final. - let _ = di; // no-op - } - - // Find function index by name "frame" (tolerate qualified names ending with ":frame") - let mut found_valid = false; - if let Some(di) = &root_compiled.debug_info { - for (idx, name) in &di.function_names { - // Names in debug_info may be enriched as "name@offset+len". Strip the annotation for comparison. - let base_name = name.split('@').next().unwrap_or(name.as_str()); - let is_frame_name = base_name == "frame" || base_name.ends_with(":frame"); - if is_frame_name { - // Check signature: 0 params, 0 return slots - if let Some(meta) = root_compiled.function_metas.get(*idx as usize) { - if meta.param_slots == 0 && meta.return_slots == 0 { - found_valid = true; - break; - } - } - } - } - } - - if !found_valid { - return Err(BuildError::Compile(CompileError::Frontend( - DiagnosticBundle::error( - "E_MISSING_ENTRY_POINT_FN", - "Missing entry point fn frame(): void in src/main/modules/main.pbs".to_string(), - crate::common::spans::Span::new(crate::common::spans::FileId::INVALID, 0, 0), - ), - ))); - } - } - } - - let program_image = Linker::link(modules_in_order.clone(), plan.steps.clone())?; - - let mut all_project_symbols = Vec::new(); - for (i, module) in modules_in_order.into_iter().enumerate() { - let project_dir = &plan.steps[i].project_dir; - let project_dir_norm = project_dir.canonicalize().unwrap_or_else(|_| project_dir.clone()); - - // Relativize file URIs for this project's symbols - let mut rel_symbols = Vec::with_capacity(module.symbols.len()); - for mut s in module.symbols { - // Try to relativize decl span's file_uri - let original = std::path::PathBuf::from(&s.decl_span.file_uri); - let rel = if original.is_absolute() { - pathdiff::diff_paths(&original, &project_dir_norm).unwrap_or(original.clone()) - } else { - original.clone() - }; - let rel_str = rel.to_string_lossy().replace('\\', "/"); - s.decl_span.file_uri = rel_str; - rel_symbols.push(s); - } - - all_project_symbols.push(crate::common::symbols::ProjectSymbols { - project: module.project_key.name.clone(), - project_dir: project_dir.to_string_lossy().to_string(), - symbols: rel_symbols, - }); - } - - Ok(BuildResult { - image: program_image, - file_manager, - symbols: all_project_symbols, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::deps::resolver::{ProjectKey, ResolvedGraph, ResolvedNode}; - use crate::manifest::{Manifest, ManifestKind}; - use crate::sources::discover; - use prometeu_analysis::ids::ProjectId; - use std::collections::BTreeMap; - use crate::frontends::pbs::adapter::PbsFrontendAdapter; - use std::fs; - use std::path::PathBuf; - use tempfile::tempdir; - - fn make_minimal_manifest(dir: &std::path::Path) { - // Minimal Prometeu JSON manifest for an App project - // See crates/prometeu-compiler/src/manifest.rs::load_manifest (expects prometeu.json) - let manifest_json = r#"{ - "name": "root", - "version": "0.1.0", - "kind": "app", - "dependencies": {} -}"#; - fs::write(dir.join("prometeu.json"), manifest_json).unwrap(); - } - - fn build_single_node_graph(project_dir: PathBuf) -> ResolvedGraph { - // Discover sources as the normal pipeline would - let sources = discover(&project_dir).unwrap_or_else(|_| crate::sources::ProjectSources { - main: None, - files: vec![], - test_files: vec![], - }); - - let id = ProjectId(0); - let node = ResolvedNode { - id, - key: ProjectKey { name: "root".to_string(), version: "0.1.0".to_string() }, - path: project_dir, - manifest: Manifest { name: "root".into(), version: "0.1.0".into(), kind: ManifestKind::App, dependencies: BTreeMap::new() }, - sources, - }; - - let mut g = ResolvedGraph::default(); - g.root_id = Some(id); - g.nodes.insert(id, node); - g - } - - #[test] - fn test_missing_main_pbs_errors() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - make_minimal_manifest(&project_dir); - - let graph = build_single_node_graph(project_dir); - let fe = PbsFrontendAdapter; - let res = build_from_graph(&graph, BuildTarget::Main, &fe); - assert!(res.is_err()); - let err = res.err().unwrap(); - match err { - BuildError::Compile(CompileError::Frontend(bundle)) => { - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_MISSING_ENTRY_POINT_FILE")); - } - BuildError::Compile(other) => { - // Accept any compile error here; presence check should have fired earlier. - // This keeps the test resilient across internal pipeline changes. - eprintln!("Got non-frontend compile error: {}", other); - } - _ => panic!("expected compile error for missing entry point file"), - } - } - - #[test] - fn test_wrong_signature_frame_errors() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - make_minimal_manifest(&project_dir); - - // Create main.pbs but with wrong signature for frame (has a parameter) - let code = r#" - fn frame(a: int): void { - return; - } - "#; - fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap(); - - let graph = build_single_node_graph(project_dir); - let fe = PbsFrontendAdapter; - let res = build_from_graph(&graph, BuildTarget::Main, &fe); - assert!(res.is_err()); - let err = res.err().unwrap(); - match err { - BuildError::Compile(CompileError::Frontend(bundle)) => { - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_MISSING_ENTRY_POINT_FN")); - } - _ => panic!("expected frontend error for wrong frame signature"), - } - } - - #[test] - fn test_framesync_injected_end_to_end() { - use prometeu_bytecode::opcode::OpCode; - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - make_minimal_manifest(&project_dir); - - // Valid entry point - let code = r#" - fn frame(): void { - let x = 1 + 1; - return; - } - "#; - fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap(); - - let graph = build_single_node_graph(project_dir); - let fe = PbsFrontendAdapter; - let res = build_from_graph(&graph, BuildTarget::Main, &fe).expect("should compile"); - - // Locate function by name -> function index - let di = res.image.debug_info.as_ref().expect("debug info"); - let (func_idx, _) = di - .function_names - .iter() - .find(|(_, name)| name == "frame") - .cloned() - .expect("frame function should exist"); - - let meta = &res.image.functions[func_idx as usize]; - let start = meta.code_offset as usize; - let end = (meta.code_offset + meta.code_len) as usize; - let code = &res.image.rom[start..end]; - - // Decode sequentially using the canonical decoder; record opcode stream. - let mut pcs = Vec::new(); - let mut i = 0usize; - while i < code.len() { - let instr = prometeu_bytecode::decoder::decode_next(i, code).expect("decoder should succeed"); - pcs.push(instr.opcode as u16); - i = instr.next_pc; - } - assert_eq!(i, code.len(), "decoder must end exactly at function end"); - - assert!(pcs.len() >= 2); - let last = *pcs.last().unwrap(); - let prev = pcs[pcs.len() - 2]; - assert_eq!(last, OpCode::Ret as u16, "last opcode must be RET"); - assert_eq!(prev, OpCode::FrameSync as u16, "prev opcode must be FRAME_SYNC"); - } -} diff --git a/crates/compiler/prometeu-compiler/src/building/output.rs b/crates/compiler/prometeu-compiler/src/building/output.rs deleted file mode 100644 index aa9a7840..00000000 --- a/crates/compiler/prometeu-compiler/src/building/output.rs +++ /dev/null @@ -1,498 +0,0 @@ -use crate::backend::emit_fragments; -use crate::building::plan::{BuildStep, BuildTarget}; -use crate::common::diagnostics::DiagnosticBundle; -use crate::common::files::FileManager; -use crate::deps::resolver::ProjectKey; -use language_api::traits::Frontend as CanonFrontend; -use language_api::types::{ExportItem, TypeRef}; -use prometeu_analysis::ids::ProjectId; -use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta}; -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; - -// Simple stable 32-bit FNV-1a hash for synthesizing opaque TypeRef tokens from names. -fn symbol_name_hash(name: &str) -> u32 { - let mut hash: u32 = 0x811C9DC5; // FNV offset basis - for &b in name.as_bytes() { - hash ^= b as u32; - hash = hash.wrapping_mul(0x01000193); // FNV prime - } - hash -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct ExportKey { - pub module_path: String, - pub item: ExportItem, -} - -#[derive(Debug, Clone)] -pub struct ExportMetadata { - pub func_idx: Option, - pub is_host: bool, - pub ty: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] -pub struct ImportKey { - pub dep_alias: String, - pub module_path: String, - pub symbol_name: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ImportMetadata { - pub key: ImportKey, - pub relocation_pcs: Vec, -} - -#[derive(Debug, Clone)] -pub struct CompiledModule { - pub project_id: ProjectId, - pub project_key: ProjectKey, - pub target: BuildTarget, - pub exports: BTreeMap, - pub imports: Vec, - pub const_pool: Vec, - pub code: Vec, - pub function_metas: Vec, - pub debug_info: Option, - pub symbols: Vec, -} - -#[derive(Debug)] -pub enum CompileError { - Frontend(DiagnosticBundle), - DuplicateExport { - symbol: String, - first_dep: String, - second_dep: String, - }, - Io(std::io::Error), - Internal(String), -} - -impl std::fmt::Display for CompileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d), - CompileError::DuplicateExport { - symbol, - first_dep, - second_dep, - } => write!( - f, - "duplicate export: symbol `{}`\n first defined in dependency `{}`\n again defined in dependency `{}`", - symbol, first_dep, second_dep - ), - CompileError::Io(e) => write!(f, "IO error: {}", e), - CompileError::Internal(s) => write!(f, "Internal error: {}", s), - } - } -} - -impl std::error::Error for CompileError {} - -impl From for CompileError { - fn from(e: std::io::Error) -> Self { - CompileError::Io(e) - } -} - -impl From for CompileError { - fn from(d: crate::common::diagnostics::DiagnosticBundle) -> Self { - CompileError::Frontend(d) - } -} - -// Note: PBS ModuleProvider/ModuleSymbols are no longer used at the backend boundary. - -pub fn compile_project( - step: BuildStep, - dep_modules: &HashMap, - fe: &dyn CanonFrontend, - _file_manager: &mut FileManager, -) -> Result { - // 1) FE-driven analysis per source → gather VM IR modules - let mut combined_vm = crate::ir_lang::Module::new(step.project_key.name.clone()); - combined_vm.const_pool = crate::ir_core::ConstPool::new(); - - // Origin module_path per appended function - let mut combined_func_origins: Vec = Vec::new(); - - let insert_const = - |pool: &mut crate::ir_core::ConstPool, val: &crate::ir_core::ConstantValue| -> crate::ir_lang::types::ConstId { - let new_id = pool.insert(val.clone()); - crate::ir_lang::types::ConstId(new_id.0) - }; - - // Map: module_path → FE exports for that module - let mut fe_exports_per_module: HashMap> = HashMap::new(); - - // Build dependency synthetic export keys and detect cross-dependency duplicates upfront - use std::collections::HashSet; - #[derive(Hash, Eq, PartialEq)] - struct DepKey(String, ExportItem); // (module_path, item) - - fn display_export_item(item: &ExportItem) -> String { - match item { - ExportItem::Type { name } | ExportItem::Service { name } => name.as_str().to_string(), - ExportItem::Function { fn_key } => { - let base = fn_key.debug_name(); - format!("{}#sig{}", base, fn_key.sig.0) - } - } - } - let mut dep_seen: HashSet = HashSet::new(); - for (alias, project_id) in &step.deps { - if let Some(compiled) = dep_modules.get(project_id) { - for (key, _meta) in &compiled.exports { - // Track using canonical item keyed by module path; alias variations only for display/conflict reporting. - let synthetic_paths = [ - format!("{}/{}", alias, key.module_path), - format!("@{}:{}", alias, key.module_path), - ]; - for sp in synthetic_paths { - let k = DepKey(sp.clone(), key.item.clone()); - if !dep_seen.insert(k) { - return Err(CompileError::DuplicateExport { - symbol: display_export_item(&key.item), - first_dep: alias.clone(), - second_dep: alias.clone(), - }); - } - } - } - } - } - - for source_rel in &step.sources { - let source_abs = step.project_dir.join(source_rel); - let full_path = source_rel.to_string_lossy().replace('\\', "/"); - let logical_module_path = if let Some(stripped) = full_path.strip_prefix("src/main/modules/") { - stripped - } else if let Some(stripped) = full_path.strip_prefix("src/test/modules/") { - stripped - } else { - &full_path - }; - let module_path = std::path::Path::new(logical_module_path) - .parent() - .map(|p| p.to_string_lossy().replace('\\', "/")) - .unwrap_or_else(|| "".to_string()); - - let unit = fe.parse_and_analyze(&source_abs.to_string_lossy()); - // Deserialize VM IR from canonical payload - let vm_module: crate::ir_lang::Module = if unit.lowered_ir.format == "vm-ir-json" { - match serde_json::from_slice(&unit.lowered_ir.bytes) { - Ok(m) => m, - Err(e) => return Err(CompileError::Internal(format!("Invalid FE VM-IR payload: {}", e))), - } - } else { - return Err(CompileError::Internal(format!("Unsupported lowered IR format: {}", unit.lowered_ir.format))); - }; - - // Aggregate FE exports per module, detecting duplicates and dep conflicts - let entry = fe_exports_per_module.entry(module_path.clone()).or_insert_with(Vec::new); - for it in unit.exports { - // Conflict with dependency synthetic exports? - let dep_key = DepKey(module_path.clone(), it.clone()); - if dep_seen.contains(&dep_key) { - return Err(CompileError::DuplicateExport { - symbol: display_export_item(&it), - first_dep: "dependency".to_string(), - second_dep: "local".to_string(), - }); - } - - // Local duplicate within same module? - let already = entry.iter().any(|e| e == &it); - if already { - return Err(CompileError::Frontend( - DiagnosticBundle::error( - "E_RESOLVE_DUPLICATE_SYMBOL", - format!("Duplicate symbol '{:?}' in module '{}'", it, module_path), - crate::common::spans::Span::new(crate::common::spans::FileId::INVALID, 0, 0), - ) - )); - } - entry.push(it); - } - - // Remap this module's const pool into the combined pool - let mut const_map: Vec = Vec::with_capacity(vm_module.const_pool.constants.len()); - for c in &vm_module.const_pool.constants { - const_map.push(insert_const(&mut combined_vm.const_pool, c)); - } - - // Append functions; remap PushConst ids safely - for mut f in vm_module.functions.into_iter() { - for instr in &mut f.body { - let kind_clone = instr.kind.clone(); - if let crate::ir_lang::instr::InstrKind::PushConst(old_id) = kind_clone { - let mapped = const_map.get(old_id.0 as usize).cloned().unwrap_or(old_id); - instr.kind = crate::ir_lang::instr::InstrKind::PushConst(mapped); - } - } - - combined_func_origins.push(module_path.clone()); - combined_vm.functions.push(f); - } - } - - let fragments = emit_fragments(&combined_vm) - .map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?; - - // Ensure function metas reflect final slots info - let mut fixed_function_metas = fragments.functions.clone(); - for (i, fm) in fixed_function_metas.iter_mut().enumerate() { - if let Some(vm_func) = combined_vm.functions.get(i) { - fm.param_slots = vm_func.param_slots; - fm.local_slots = vm_func.local_slots; - fm.return_slots = vm_func.return_slots; - } - } - - // 2) Collect exports from FE contract, map to VM function indices - let mut exports = BTreeMap::new(); - - for (module_path, items) in &fe_exports_per_module { - for item in items { - match item { - ExportItem::Type { name } => { - exports.insert( - ExportKey { module_path: module_path.clone(), item: item.clone() }, - ExportMetadata { func_idx: None, is_host: false, ty: Some(TypeRef(symbol_name_hash(name.as_str()))) }, - ); - } - ExportItem::Service { name: _ } => { - // Service owner export (no functions synthesized here) - exports.insert( - ExportKey { module_path: module_path.clone(), item: item.clone() }, - ExportMetadata { func_idx: None, is_host: false, ty: None }, - ); - } - ExportItem::Function { fn_key } => { - // Map function to VM function index by name and overwrite FE-provided sig with actual VM sig id - for (i, f) in combined_vm.functions.iter().enumerate() { - if combined_func_origins.get(i).map(|s| s.as_str()) != Some(module_path.as_str()) { continue; } - if f.name != fn_key.name.as_str() { continue; } - // Rebuild canonical key with authoritative BE signature id - let fixed_key = ExportItem::Function { fn_key: language_api::types::CanonicalFnKey::new( - fn_key.owner.clone(), - fn_key.name.clone(), - language_api::types::SignatureRef(f.sig.0 as u32), - )}; - exports.insert( - ExportKey { module_path: module_path.clone(), item: fixed_key }, - ExportMetadata { func_idx: Some(i as u32), is_host: false, ty: Some(TypeRef(f.sig.0 as u32)) }, - ); - } - } - } - } - } - - // 3) Collect symbols for analysis (LSP, etc.) — minimal fallback from debug_info - let mut project_symbols = Vec::new(); - if let Some(di) = &fragments.debug_info { - // Create at least a symbol for entry point or first function - if let Some((_, name)) = di.function_names.first() { - let name = name.split('@').next().unwrap_or(name.as_str()).to_string(); - let span = crate::common::symbols::SpanRange { - file_uri: step.project_dir.join("src/main/modules/main.pbs").to_string_lossy().to_string(), - start: crate::common::symbols::Pos { line: 0, col: 0 }, - end: crate::common::symbols::Pos { line: 0, col: 1 }, - }; - project_symbols.push(crate::common::symbols::Symbol { - id: format!("{}:{}:{}:{}:{:016x}", step.project_key.name, "function", "", name.clone(), 0), - name, - kind: "function".to_string(), - exported: false, - module_path: "".to_string(), - decl_span: span, - }); - } - } - - // 4) Enrich debug_info (only if present). Avoid requiring Default on DebugInfo. - let mut debug_info = fragments.debug_info.clone(); - if let Some(dbg) = debug_info.as_mut() { - // annotate function names with "@offset+len" - // NOTE: assumes dbg.function_names aligns with functions order. - let mut enriched = Vec::new(); - for (i, (fid, name)) in dbg.function_names.clone().into_iter().enumerate() { - if let Some(meta) = fixed_function_metas.get(i) { - enriched.push((fid, format!("{}@{}+{}", name, meta.code_offset, meta.code_len))); - } else { - enriched.push((fid, name)); - } - } - if !enriched.is_empty() { - dbg.function_names = enriched; - } - } - - // 5) Collect imports from unresolved labels - let mut imports = Vec::new(); - for (label, pcs) in fragments.unresolved_labels { - if !label.starts_with('@') { - continue; - } - - // Format: @dep_alias::module_path:symbol_name - let parts: Vec<&str> = label[1..].splitn(2, "::").collect(); - if parts.len() != 2 { - continue; - } - - let dep_alias = parts[0].to_string(); - let rest = parts[1]; - - // Split from the right once: "...:" - let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect(); - if sub_parts.len() != 2 { - continue; - } - - let symbol_name = sub_parts[0].to_string(); - let module_path = sub_parts[1].to_string(); - - imports.push(ImportMetadata { - key: ImportKey { - dep_alias, - module_path, - symbol_name, - }, - relocation_pcs: pcs, - }); - } - - Ok(CompiledModule { - project_id: step.project_id, - project_key: step.project_key, - target: step.target, - exports, - imports, - const_pool: fragments.const_pool, - code: fragments.code, - function_metas: fixed_function_metas, - debug_info, - symbols: project_symbols, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::frontends::pbs::adapter::PbsFrontendAdapter; - use language_api::types::{ExportItem, ItemName}; - use std::fs; - use std::path::PathBuf; - use tempfile::tempdir; - - #[test] - fn test_compile_root_only_project() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - - // NOTE: ajuste de sintaxe: seu PBS entrypoint atual é `fn frame(): void` dentro de main.pbs - // e "mod fn frame" pode não ser válido. Mantive o essencial. - let main_code = r#" - pub declare struct Vec2(x: int, y: int) - - fn add(a: int, b: int): int { - return a + b; - } - - fn frame(): void { - let x = add(1, 2); - } - "#; - - fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap(); - - let project_key = ProjectKey { - name: "root".to_string(), - version: "0.1.0".to_string(), - }; - let project_id = ProjectId(0); - - let step = BuildStep { - project_id, - project_key: project_key.clone(), - project_dir: project_dir.clone(), - target: BuildTarget::Main, - sources: vec![PathBuf::from("src/main/modules/main.pbs")], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let compiled = - compile_project(step, &HashMap::new(), &fe, &mut file_manager).expect("Failed to compile project"); - - assert_eq!(compiled.project_id, project_id); - assert_eq!(compiled.target, BuildTarget::Main); - - // Vec2 should be exported (canonical) - let vec2_key = ExportKey { - module_path: "".to_string(), - item: ExportItem::Type { name: ItemName::new("Vec2").unwrap() }, - }; - assert!(compiled.exports.contains_key(&vec2_key)); - } - - #[test] - fn test_service_method_export_qualified() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - - let main_code = r#" - pub service Log { - fn debug(msg: string): void { - } - } - "#; - - fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap(); - - let project_key = ProjectKey { - name: "root".to_string(), - version: "0.1.0".to_string(), - }; - let project_id = ProjectId(0); - - let step = BuildStep { - project_id, - project_key: project_key.clone(), - project_dir: project_dir.clone(), - target: BuildTarget::Main, - sources: vec![PathBuf::from("src/main/modules/main.pbs")], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager) - .expect("Failed to compile project"); - - // Find a function export with canonical fn key for method `debug`. - // Owner is optional at this stage; canonical owner propagation will be added later. - let mut found = false; - for (key, _meta) in &compiled.exports { - if let ExportItem::Function { fn_key } = &key.item { - if fn_key.name.as_str() == "debug" { - found = true; - break; - } - } - } - - assert!(found, "Expected an export with canonical fn key name=debug but not found. Exports: {:?}", compiled.exports.keys().collect::>()); - } -} diff --git a/crates/compiler/prometeu-compiler/src/building/plan.rs b/crates/compiler/prometeu-compiler/src/building/plan.rs deleted file mode 100644 index 5d63c1d9..00000000 --- a/crates/compiler/prometeu-compiler/src/building/plan.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::deps::resolver::{ProjectKey, ResolvedGraph}; -use prometeu_analysis::ids::ProjectId; -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum BuildTarget { - Main, - Test, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BuildStep { - pub project_id: ProjectId, - pub project_key: ProjectKey, - pub project_dir: PathBuf, - pub target: BuildTarget, - pub sources: Vec, - pub deps: BTreeMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BuildPlan { - pub steps: Vec, -} - -impl BuildPlan { - pub fn from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Self { - let mut steps = Vec::new(); - let sorted_ids = topological_sort(graph); - - for id in sorted_ids { - if let Some(node) = graph.nodes.get(&id) { - let sources_list: Vec = match target { - BuildTarget::Main => node.sources.files.clone(), - BuildTarget::Test => node.sources.test_files.clone(), - }; - - // Normalize to relative paths and sort lexicographically - let mut sources: Vec = sources_list - .into_iter() - .map(|p| { - p.strip_prefix(&node.path) - .map(|rp| rp.to_path_buf()) - .unwrap_or(p) - }) - .collect(); - sources.sort(); - - let mut deps: BTreeMap = BTreeMap::new(); - if let Some(edges) = graph.edges.get(&id) { - for edge in edges { - deps.insert(edge.alias.clone(), edge.to); - } - } - - steps.push(BuildStep { - project_id: id, - project_key: node.key.clone(), - project_dir: node.path.clone(), - target, - sources, - deps, - }); - } - } - - Self { steps } - } -} - -fn topological_sort(graph: &ResolvedGraph) -> Vec { - let mut in_degree = HashMap::new(); - let mut adj = HashMap::new(); - - for id in graph.nodes.keys() { - in_degree.insert(id.clone(), 0); - adj.insert(id.clone(), Vec::new()); - } - - for (from, edges) in &graph.edges { - for edge in edges { - // from depends on edge.to - // so edge.to must be built BEFORE from - // edge.to -> from - adj.get_mut(&edge.to).unwrap().push(from.clone()); - *in_degree.get_mut(from).unwrap() += 1; - } - } - - let mut ready: std::collections::BinaryHeap = graph.nodes.keys() - .filter(|id| *in_degree.get(id).unwrap() == 0) - .map(|id| ReverseProjectId(*id)) - .collect(); - - let mut result = Vec::new(); - while let Some(ReverseProjectId(u)) = ready.pop() { - result.push(u); - - if let Some(neighbors) = adj.get(&u) { - for v in neighbors { - let degree = in_degree.get_mut(v).unwrap(); - *degree -= 1; - if *degree == 0 { - ready.push(ReverseProjectId(*v)); - } - } - } - } - - result -} - -#[derive(Eq, PartialEq, Copy, Clone)] -struct ReverseProjectId(ProjectId); - -impl Ord for ReverseProjectId { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - // BinaryHeap is a max-heap. We want min-heap with stable numeric order. - // So we reverse the comparison on the numeric id. - other.0.as_u32().cmp(&self.0.as_u32()) - } -} - -impl PartialOrd for ReverseProjectId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::deps::resolver::{ProjectKey, ResolvedEdge, ResolvedGraph, ResolvedNode}; - use crate::manifest::Manifest; - use crate::sources::ProjectSources; - use std::collections::BTreeMap; - - fn mock_node(id: ProjectId, name: &str, version: &str) -> (ProjectId, ResolvedNode) { - let node = ResolvedNode { - id, - key: ProjectKey { name: name.to_string(), version: version.to_string() }, - path: PathBuf::from(format!("/{}", name)), - manifest: Manifest { - name: name.to_string(), - version: version.to_string(), - kind: crate::manifest::ManifestKind::Lib, - dependencies: BTreeMap::new(), - }, - sources: ProjectSources { - main: None, - files: vec![PathBuf::from("b.pbs"), PathBuf::from("a.pbs")], - test_files: vec![PathBuf::from("test_b.pbs"), PathBuf::from("test_a.pbs")], - }, - }; - (id, node) - } - - #[test] - fn test_topo_sort_stability() { - let mut graph = ResolvedGraph::default(); - - let (a_id, a) = mock_node(ProjectId(0), "a", "1.0.0"); - let (b_id, b) = mock_node(ProjectId(1), "b", "1.0.0"); - let (c_id, c) = mock_node(ProjectId(2), "c", "1.0.0"); - - graph.nodes.insert(a_id, a); - graph.nodes.insert(b_id, b); - graph.nodes.insert(c_id, c); - - // No edges, order by numeric id: a(0), b(1), c(2) - let plan = BuildPlan::from_graph(&graph, BuildTarget::Main); - assert_eq!(plan.steps[0].project_key.name, "a"); - assert_eq!(plan.steps[1].project_key.name, "b"); - assert_eq!(plan.steps[2].project_key.name, "c"); - } - - #[test] - fn test_topo_sort_dependencies() { - let mut graph = ResolvedGraph::default(); - - let (a_id, a) = mock_node(ProjectId(0), "a", "1.0.0"); - let (b_id, b) = mock_node(ProjectId(1), "b", "1.0.0"); - let (c_id, c) = mock_node(ProjectId(2), "c", "1.0.0"); - - graph.nodes.insert(a_id, a.clone()); - graph.nodes.insert(b_id, b.clone()); - graph.nodes.insert(c_id, c.clone()); - - // c depends on b, b depends on a - // Sort should be: a, b, c - graph.edges.insert(c_id, vec![ResolvedEdge { alias: "b_alias".to_string(), to: b_id }]); - graph.edges.insert(b_id, vec![ResolvedEdge { alias: "a_alias".to_string(), to: a_id }]); - - let plan = BuildPlan::from_graph(&graph, BuildTarget::Main); - assert_eq!(plan.steps.len(), 3); - assert_eq!(plan.steps[0].project_key.name, "a"); - assert_eq!(plan.steps[1].project_key.name, "b"); - assert_eq!(plan.steps[2].project_key.name, "c"); - - assert_eq!(plan.steps[2].deps.get("b_alias").copied(), Some(b_id)); - } - - #[test] - fn test_topo_sort_complex() { - let mut graph = ResolvedGraph::default(); - - // d -> b, c - // b -> a - // c -> a - // a - - let (a_id, a) = mock_node(ProjectId(0), "a", "1.0.0"); - let (b_id, b) = mock_node(ProjectId(1), "b", "1.0.0"); - let (c_id, c) = mock_node(ProjectId(2), "c", "1.0.0"); - let (d_id, d) = mock_node(ProjectId(3), "d", "1.0.0"); - - graph.nodes.insert(a_id, a.clone()); - graph.nodes.insert(b_id, b.clone()); - graph.nodes.insert(c_id, c.clone()); - graph.nodes.insert(d_id, d.clone()); - - graph.edges.insert(d_id, vec![ - ResolvedEdge { alias: "b".to_string(), to: b_id }, - ResolvedEdge { alias: "c".to_string(), to: c_id }, - ]); - graph.edges.insert(b_id, vec![ResolvedEdge { alias: "a".to_string(), to: a_id }]); - graph.edges.insert(c_id, vec![ResolvedEdge { alias: "a".to_string(), to: a_id }]); - - let plan = BuildPlan::from_graph(&graph, BuildTarget::Main); - let names: Vec<_> = plan.steps.iter().map(|s| s.project_key.name.as_str()).collect(); - assert_eq!(names, vec!["a", "b", "c", "d"]); - } - - #[test] - fn test_sources_sorting() { - let mut graph = ResolvedGraph::default(); - let (a_id, a) = mock_node(ProjectId(0), "a", "1.0.0"); - graph.nodes.insert(a_id, a); - - let plan = BuildPlan::from_graph(&graph, BuildTarget::Main); - assert_eq!(plan.steps[0].sources, vec![PathBuf::from("a.pbs"), PathBuf::from("b.pbs")]); - - let plan_test = BuildPlan::from_graph(&graph, BuildTarget::Test); - assert_eq!(plan_test.steps[0].sources, vec![PathBuf::from("test_a.pbs"), PathBuf::from("test_b.pbs")]); - } -} diff --git a/crates/compiler/prometeu-compiler/src/common/config.rs b/crates/compiler/prometeu-compiler/src/common/config.rs deleted file mode 100644 index a62e802d..00000000 --- a/crates/compiler/prometeu-compiler/src/common/config.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::manifest::Manifest; -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ProjectConfig { - #[serde(flatten)] - pub manifest: Manifest, - pub script_fe: String, - pub entry: PathBuf, -} - -impl ProjectConfig { - pub fn load(project_dir: &Path) -> Result { - let config_path = project_dir.join("prometeu.json"); - let content = std::fs::read_to_string(&config_path)?; - let config: ProjectConfig = serde_json::from_str(&content) - .map_err(|e| anyhow::anyhow!("JSON error in {:?}: {}", config_path, e))?; - - // Use manifest validation - crate::manifest::load_manifest(project_dir) - .map_err(|e| anyhow::anyhow!("{}", e))?; - - Ok(config) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_load_valid_config() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("prometeu.json"); - fs::write( - config_path, - r#"{ - "name": "test_project", - "version": "0.1.0", - "script_fe": "pbs", - "entry": "main.pbs" - }"#, - ) - .unwrap(); - - let config = ProjectConfig::load(dir.path()).unwrap(); - assert_eq!(config.manifest.name, "test_project"); - assert_eq!(config.script_fe, "pbs"); - assert_eq!(config.entry, PathBuf::from("main.pbs")); - } -} diff --git a/crates/compiler/prometeu-compiler/src/common/diagnostics.rs b/crates/compiler/prometeu-compiler/src/common/diagnostics.rs deleted file mode 100644 index 1e954baf..00000000 --- a/crates/compiler/prometeu-compiler/src/common/diagnostics.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::common::files::FileManager; -use crate::common::spans::{FileId, Span}; -use serde::{Serialize, Serializer}; - -#[derive(Debug, Clone, PartialEq)] -pub enum Severity { - Error, - Warning, -} - -impl Serialize for Severity { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Severity::Error => serializer.serialize_str("error"), - Severity::Warning => serializer.serialize_str("warning"), - } - } -} - -#[derive(Debug, Clone, Serialize)] -pub struct Diagnostic { - pub severity: Severity, - pub code: String, - pub message: String, - pub span: Span, - pub related: Vec<(String, Span)>, -} - -#[derive(Debug, Clone, Serialize)] -pub struct DiagnosticBundle { - pub diagnostics: Vec, -} - -impl DiagnosticBundle { - pub fn new() -> Self { - Self { - diagnostics: Vec::new(), - } - } - - pub fn push(&mut self, diagnostic: Diagnostic) { - self.diagnostics.push(diagnostic); - } - - pub fn error(code: &str, message: String, span: Span) -> Self { - let mut bundle = Self::new(); - bundle.push(Diagnostic { - severity: Severity::Error, - code: code.to_string(), - message, - span, - related: Vec::new(), - }); - bundle - } - - pub fn has_errors(&self) -> bool { - self.diagnostics - .iter() - .any(|d| matches!(d.severity, Severity::Error)) - } - - /// Serializes the diagnostic bundle to canonical JSON, resolving file IDs via FileManager. - /// The output is deterministic: diagnostics are sorted by (file_id, start, end, code). - pub fn to_json(&self, file_manager: &FileManager) -> String { - #[derive(Serialize)] - struct CanonicalSpan { - file: String, - start: u32, - end: u32, - } - - #[derive(Serialize)] - struct CanonicalDiag { - severity: Severity, - code: String, - message: String, - span: CanonicalSpan, - related: Vec<(String, CanonicalSpan)>, - } - - let mut diags = self.diagnostics.clone(); - diags.sort_by(|a, b| { - ( - a.span.file.as_usize(), - a.span.start, - a.span.end, - &a.code, - ) - .cmp(&(b.span.file.as_usize(), b.span.start, b.span.end, &b.code)) - }); - - let canonical_diags: Vec = diags - .iter() - .map(|d| { - let s = &d.span; - let file = if s.file == FileId::INVALID { - "".to_string() - } else { - file_manager - .get_path(s.file.as_usize()) - .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string())) - .unwrap_or_else(|| format!("file_{}", s.file.as_usize())) - }; - let canonical_span = CanonicalSpan { - file, - start: s.start, - end: s.end, - }; - - let related = d - .related - .iter() - .map(|(msg, sp)| { - let file = if sp.file == FileId::INVALID { - "".to_string() - } else { - file_manager - .get_path(sp.file.as_usize()) - .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string())) - .unwrap_or_else(|| format!("file_{}", sp.file.as_usize())) - }; - let rsp = CanonicalSpan { - file, - start: sp.start, - end: sp.end, - }; - (msg.clone(), rsp) - }) - .collect(); - - CanonicalDiag { - severity: d.severity.clone(), - code: d.code.clone(), - message: d.message.clone(), - span: canonical_span, - related, - } - }) - .collect(); - - serde_json::to_string_pretty(&canonical_diags).unwrap() - } -} - -impl From for DiagnosticBundle { - fn from(diagnostic: Diagnostic) -> Self { - let mut bundle = Self::new(); - bundle.push(diagnostic); - bundle - } -} diff --git a/crates/compiler/prometeu-compiler/src/common/files.rs b/crates/compiler/prometeu-compiler/src/common/files.rs deleted file mode 100644 index d0c7dca9..00000000 --- a/crates/compiler/prometeu-compiler/src/common/files.rs +++ /dev/null @@ -1,106 +0,0 @@ -use prometeu_analysis::interner::NameInterner; -use std::path::PathBuf; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct SourceFile { - pub id: usize, - pub path: PathBuf, - pub source: Arc, -} - -#[derive(Debug, Clone)] -pub struct FileManager { - files: Vec, - interner: NameInterner, -} - -impl FileManager { - pub fn new() -> Self { - Self { - files: Vec::new(), - interner: NameInterner::new(), - } - } - - pub fn with_interner(interner: NameInterner) -> Self { - Self { - files: Vec::new(), - interner, - } - } - - pub fn interner(&self) -> &NameInterner { - &self.interner - } - - pub fn interner_mut(&mut self) -> &mut NameInterner { - &mut self.interner - } - - pub fn add(&mut self, path: PathBuf, source: String) -> usize { - let id = self.files.len(); - self.files.push(SourceFile { - id, - path, - source: Arc::from(source), - }); - id - } - - pub fn get_file(&self, id: usize) -> Option<&SourceFile> { - self.files.get(id) - } - - pub fn get_path(&self, id: usize) -> Option { - self.files.get(id).map(|f| f.path.clone()) - } - - pub fn lookup_pos(&self, file_id: usize, pos: u32) -> (usize, usize) { - let file = if let Some(f) = self.files.get(file_id) { - f - } else { - return (0, 0); - }; - - let mut line = 1; - let mut col = 1; - for (i, c) in file.source.char_indices() { - if i as u32 == pos { - break; - } - if c == '\n' { - line += 1; - col = 1; - } else { - col += 1; - } - } - (line, col) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_lookup_pos() { - let mut fm = FileManager::new(); - let source = "line1\nline2\n line3".to_string(); - let file_id = fm.add(PathBuf::from("test.pbs"), source); - - // "l" in line 1 - assert_eq!(fm.lookup_pos(file_id, 0), (1, 1)); - // "e" in line 1 - assert_eq!(fm.lookup_pos(file_id, 3), (1, 4)); - // "\n" after line 1 - assert_eq!(fm.lookup_pos(file_id, 5), (1, 6)); - // "l" in line 2 - assert_eq!(fm.lookup_pos(file_id, 6), (2, 1)); - // first space in line 3 - assert_eq!(fm.lookup_pos(file_id, 12), (3, 1)); - // "l" in line 3 - assert_eq!(fm.lookup_pos(file_id, 14), (3, 3)); - } -} diff --git a/crates/compiler/prometeu-compiler/src/common/mod.rs b/crates/compiler/prometeu-compiler/src/common/mod.rs deleted file mode 100644 index 350a49e7..00000000 --- a/crates/compiler/prometeu-compiler/src/common/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod diagnostics; -pub mod spans; -pub mod files; -pub mod symbols; -pub mod config; diff --git a/crates/compiler/prometeu-compiler/src/common/spans.rs b/crates/compiler/prometeu-compiler/src/common/spans.rs deleted file mode 100644 index 10e9e17e..00000000 --- a/crates/compiler/prometeu-compiler/src/common/spans.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Canonical Span for the whole workspace -pub use prometeu_analysis::span::Span; -pub use prometeu_analysis::ids::FileId; diff --git a/crates/compiler/prometeu-compiler/src/common/symbols.rs b/crates/compiler/prometeu-compiler/src/common/symbols.rs deleted file mode 100644 index a642fdd4..00000000 --- a/crates/compiler/prometeu-compiler/src/common/symbols.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::common::spans::Span; -use prometeu_analysis::NameInterner; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Debug, Clone)] -pub struct RawSymbol { - pub pc: u32, - pub span: Span, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct DebugSymbol { - pub pc: u32, - pub file: String, - pub line: usize, - pub col: usize, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Symbol { - pub id: String, - pub name: String, - pub kind: String, - pub exported: bool, - pub module_path: String, - pub decl_span: SpanRange, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SpanRange { - pub file_uri: String, - pub start: Pos, - pub end: Pos, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Pos { - pub line: u32, - pub col: u32, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ProjectSymbols { - pub project: String, - pub project_dir: String, - pub symbols: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct SymbolsFile { - pub schema_version: u32, - pub compiler_version: String, - pub root_project: String, - pub projects: Vec, -} - -pub type SymbolInfo = Symbol; - -// ======================== -// analysis.json v0 structs -// ======================== - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnalysisFileTableEntry { - pub file_id: u32, - pub uri: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnalysisNameEntry { - pub name_id: u32, - pub name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnalysisModuleEntry { - pub module_id: u32, - pub module_path: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnalysisSymbolEntry { - pub symbol_id: u32, - pub name_id: u32, - pub kind: String, - pub exported: bool, - pub module_id: u32, - pub decl_span: SpanRange, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct AnalysisFacts { - pub node_type: Vec, - pub symbol_type: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnalysisFileV0 { - pub schema_version: u32, - pub compiler_version: String, - pub root_project: String, - pub file_table: Vec, - pub name_table: Vec, - pub module_table: Vec, - pub symbols: Vec, - pub refs: Vec, - pub types: Vec, - pub facts: AnalysisFacts, - pub diagnostics: Vec, -} - -pub fn collect_symbols( - project_id: &str, - module_symbols: &HashMap, - file_manager: &crate::common::files::FileManager, - interner: &NameInterner, -) -> Vec { - let mut result = Vec::new(); - - for (module_path, ms) in module_symbols { - // Collect from type_symbols - for list in ms.type_symbols.symbols.values() { - for sym in list { - if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { - result.push(s); - } - } - } - // Collect from value_symbols - for list in ms.value_symbols.symbols.values() { - for sym in list { - if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { - result.push(s); - } - } - } - } - - // Deterministic ordering: by file, then start pos, then name - result.sort_by(|a, b| { - a.decl_span.file_uri.cmp(&b.decl_span.file_uri) - .then(a.decl_span.start.line.cmp(&b.decl_span.start.line)) - .then(a.decl_span.start.col.cmp(&b.decl_span.start.col)) - .then(a.name.cmp(&b.name)) - }); - - result -} - -fn convert_symbol( - project_id: &str, - module_path: &str, - sym: &crate::frontends::pbs::symbols::Symbol, - file_manager: &crate::common::files::FileManager, - interner: &NameInterner, -) -> Option { - use crate::frontends::pbs::symbols::{SymbolKind, Visibility}; - - let kind = match sym.kind { - SymbolKind::Service => "service", - SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => "type", - SymbolKind::Function => "function", - SymbolKind::Local => return None, // Ignore locals for v0 - }; - - let exported = sym.visibility == Visibility::Pub; - - // According to v0 policy, only service and declare are exported. - // Functions are NOT exportable yet. - if exported && sym.kind == SymbolKind::Function { - // This should have been caught by semantic analysis, but we enforce it here too - // for the symbols.json output. - // Actually, we'll just mark it exported=false if it's a function. - } - - let span = sym.span.clone(); - let file_path = file_manager.get_path(span.file.as_usize()) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("unknown_file_{}", span.file.as_usize())); - - // Convert 1-based to 0-based - let (s_line, s_col) = file_manager.lookup_pos(span.file.as_usize(), span.start); - let (e_line, e_col) = file_manager.lookup_pos(span.file.as_usize(), span.end); - - let decl_span = SpanRange { - file_uri: file_path, - start: Pos { line: (s_line - 1) as u32, col: (s_col - 1) as u32 }, - end: Pos { line: (e_line - 1) as u32, col: (e_col - 1) as u32 }, - }; - - let hash = decl_span.compute_hash(); - let name = interner.resolve(sym.name).to_string(); - let id = format!("{}:{}:{}:{}:{:016x}", project_id, kind, module_path, name, hash); - - Some(Symbol { - id, - name, - kind: kind.to_string(), - exported, - module_path: module_path.to_string(), - decl_span, - }) -} - -impl SpanRange { - pub fn compute_hash(&self) -> u64 { - let mut h = 0xcbf29ce484222325u64; - let mut update = |bytes: &[u8]| { - for b in bytes { - h ^= *b as u64; - h = h.wrapping_mul(0x100000001b3u64); - } - }; - - update(self.file_uri.as_bytes()); - update(&self.start.line.to_le_bytes()); - update(&self.start.col.to_le_bytes()); - update(&self.end.line.to_le_bytes()); - update(&self.end.col.to_le_bytes()); - h - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_symbol_id_is_stable() { - let span = SpanRange { - file_uri: "main.pbs".to_string(), - start: Pos { line: 10, col: 5 }, - end: Pos { line: 10, col: 20 }, - }; - - let hash1 = span.compute_hash(); - let hash2 = span.compute_hash(); - - assert_eq!(hash1, hash2); - // Hash constant may change if algorithm or field names change; we only - // assert determinism here. - assert!(hash1 != 0); - } -} diff --git a/crates/compiler/prometeu-compiler/src/compiler.rs b/crates/compiler/prometeu-compiler/src/compiler.rs deleted file mode 100644 index 8e36e859..00000000 --- a/crates/compiler/prometeu-compiler/src/compiler.rs +++ /dev/null @@ -1,521 +0,0 @@ -//! # Compiler Orchestration -//! -//! This module provides the high-level API for triggering the compilation process. -//! It handles the transition between different compiler phases: Frontend -> IR -> Backend. - -use crate::backend; -use crate::common::config::ProjectConfig; -use crate::common::files::FileManager; -use crate::common::spans::{FileId, Span}; -use crate::common::symbols::{DebugSymbol, ProjectSymbols, RawSymbol, SymbolsFile}; -use anyhow::Result; -use prometeu_bytecode::BytecodeModule; -use std::path::Path; - -/// The result of a successful compilation process. -/// It contains the final binary and the metadata needed for debugging. -#[derive(Debug)] -pub struct CompilationUnit { - /// The raw binary data formatted as Prometeu ByteCode (PBC). - /// This is what gets written to a `.pbc` file. - pub rom: Vec, - - /// The list of debug symbols discovered during compilation. - /// These are used to map bytecode offsets back to source code locations. - pub raw_symbols: Vec, - - /// The file manager containing all source files used during compilation. - pub file_manager: FileManager, - - /// The high-level project symbols for LSP and other tools. - pub project_symbols: Vec, - - /// The name of the root project. - pub root_project: String, -} - -impl CompilationUnit { - /// Writes the compilation results (PBC binary, disassembly, and symbols) to the disk. - /// - /// # Arguments - /// * `out` - The base path for the output `.pbc` file. - /// * `emit_disasm` - If true, a `.disasm` file will be created next to the output. - /// * `emit_symbols` - If true, a `.json` symbols file will be created next to the output. - pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { - let mut debug_symbols = Vec::new(); - for raw in &self.raw_symbols { - let path = self.file_manager.get_path(raw.span.file.as_usize()) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("file_{}", raw.span.file.as_usize())); - - let (line, col) = self.file_manager.lookup_pos(raw.span.file.as_usize(), raw.span.start); - - debug_symbols.push(DebugSymbol { - pc: raw.pc, - file: path, - line, - col, - }); - } - - let lsp_symbols = SymbolsFile { - schema_version: 1, - compiler_version: "0.1.0".to_string(), // TODO: use crate version - root_project: self.root_project.clone(), - projects: self.project_symbols.clone(), - }; - - let artifacts = backend::artifacts::Artifacts::new( - self.rom.clone(), - debug_symbols, - lsp_symbols, - ); - artifacts.export(out, emit_disasm, emit_symbols) - } -} - - -pub fn compile(project_dir: &Path) -> Result { - compile_ext(project_dir, false) -} - -pub fn compile_ext(project_dir: &Path, explain_deps: bool) -> Result { - let config = ProjectConfig::load(project_dir)?; - - if config.script_fe == "pbs" { - let graph_res = crate::deps::resolver::resolve_graph(project_dir); - - if explain_deps || graph_res.is_err() { - match &graph_res { - Ok(graph) => { - println!("{}", graph.explain()); - } - Err(crate::deps::resolver::ResolveError::WithTrace { trace, source }) => { - // Create a dummy graph to use its explain logic for the trace - let mut dummy_graph = crate::deps::resolver::ResolvedGraph::default(); - dummy_graph.trace = trace.clone(); - println!("{}", dummy_graph.explain()); - eprintln!("Dependency resolution failed: {}", source); - } - Err(e) => { - eprintln!("Dependency resolution failed: {}", e); - } - } - } - - let graph = graph_res.map_err(|e| anyhow::anyhow!("Dependency resolution failed: {}", e))?; - - // Use PBS Frontend adapter implementing the canonical FE contract - let fe = crate::frontends::pbs::adapter::PbsFrontendAdapter; - let build_result = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main, &fe) - .map_err(|e| anyhow::anyhow!("Build failed: {}", e))?; - - let module = BytecodeModule::from(build_result.image.clone()); - let rom = module.serialize(); - - let mut raw_symbols = Vec::new(); - if let Some(debug) = &build_result.image.debug_info { - for (pc, span) in &debug.pc_to_span { - raw_symbols.push(RawSymbol { - pc: *pc, - span: Span::new(FileId(span.file_id), span.start, span.end), - }); - } - } - - Ok(CompilationUnit { - rom, - raw_symbols, - file_manager: build_result.file_manager, - project_symbols: build_result.symbols, - root_project: config.manifest.name.clone(), - }) - } else { - anyhow::bail!("Invalid frontend: {}", config.script_fe) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_lang; - use prometeu_bytecode::disasm::disasm; - use prometeu_bytecode::opcode::OpCode; - use prometeu_bytecode::BytecodeLoader; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_invalid_frontend() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("prometeu.json"); - fs::write( - config_path, - r#"{ - "name": "invalid_fe", - "version": "0.1.0", - "script_fe": "invalid", - "entry": "main.pbs" - }"#, - ) - .unwrap(); - - let result = compile(dir.path()); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Invalid frontend: invalid")); - } - - #[test] - fn test_compile_hip_program() { - let dir = tempdir().unwrap(); - let project_dir = dir.path(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "hip_test", - "version": "0.1.0", - "script_fe": "pbs", - "entry": "src/main/modules/main.pbs" - }"#, - ).unwrap(); - - let code = " - fn frame(): void { - let x = alloc int; - mutate x as v { - let y = v + 1; - } - } - "; - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap(); - - let unit = compile(project_dir).expect("Failed to compile"); - let pbc = BytecodeLoader::load(&unit.rom).expect("Failed to parse PBC"); - let instrs = disasm(&pbc.code).expect("Failed to disassemble"); - - let opcodes: Vec<_> = instrs.iter().map(|i| i.opcode).collect(); - - assert!(opcodes.contains(&OpCode::Alloc)); - assert!(opcodes.contains(&OpCode::GateLoad)); - // After PR-09, BeginMutate/EndMutate map to their respective opcodes - assert!(opcodes.contains(&OpCode::GateBeginMutate)); - assert!(opcodes.contains(&OpCode::GateEndMutate)); - assert!(opcodes.contains(&OpCode::Add)); - assert!(opcodes.contains(&OpCode::Ret)); - } - - #[test] - fn test_golden_bytecode_snapshot() { - let dir = tempdir().unwrap(); - let project_dir = dir.path(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "golden_test", - "version": "0.1.0", - "script_fe": "pbs", - "entry": "src/main/modules/main.pbs" - }"#, - ).unwrap(); - - let code = r#" - declare contract Gfx host {} - - fn helper(val: int): int { - return val * 2; - } - - fn main() { - Gfx.clear(0); - let x = 10; - if (x > 5) { - let y = helper(x); - } - - let buf = alloc int; - mutate buf as b { - let current = b + 1; - } - } - - // Entry point required by the compiler - fn frame(): void { return; } - "#; - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap(); - - let unit = compile(project_dir).expect("Failed to compile"); - let pbc = BytecodeLoader::load(&unit.rom).expect("Failed to parse PBC"); - let instrs = disasm(&pbc.code).expect("Failed to disassemble"); - - let mut disasm_text = String::new(); - for instr in instrs { - let operands_str = instr.operands.iter() - .map(|o| format!("{:?}", o)) - .collect::>() - .join(" "); - let line = if operands_str.is_empty() { - format!("{:04X} {:?}\n", instr.pc, instr.opcode) - } else { - format!("{:04X} {:?} {}\n", instr.pc, instr.opcode, operands_str.trim()) - }; - disasm_text.push_str(&line); - } - - let expected_disasm = r#"0000 GetLocal U32(0) -0006 PushConst U32(1) -000C Mul -000E Ret -0010 PushConst U32(2) -0016 Syscall U32(4112) -001C PushConst U32(3) -0022 SetLocal U32(0) -0028 GetLocal U32(0) -002E PushConst U32(4) -0034 Gt -0036 JmpIfFalse U32(74) -003C Jmp U32(50) -0042 GetLocal U32(0) -0048 Call U32(0) -004E SetLocal U32(1) -0054 Jmp U32(80) -005A Jmp U32(80) -0060 Alloc U32(2) U32(1) -006A SetLocal U32(1) -0070 GetLocal U32(1) -0076 GateRetain -0078 SetLocal U32(2) -007E GetLocal U32(2) -0084 GateRetain -0086 GateBeginMutate -0088 GetLocal U32(2) -008E GateRetain -0090 GateLoad U32(0) -0096 SetLocal U32(3) -009C GetLocal U32(3) -00A2 PushConst U32(5) -00A8 Add -00AA SetLocal U32(4) -00B0 GateEndMutate -00B2 GateRelease -00B4 GetLocal U32(1) -00BA GateRelease -00BC GetLocal U32(2) -00C2 GateRelease -00C4 Ret -"#; - - // O código pode conter funções adicionais (ex.: frame). Verificamos que o disasm - // começa com o snapshot estável e, adicionalmente, que o entry point contém FRAME_SYNC antes de RET. - assert!(disasm_text.starts_with(expected_disasm), "Golden disassembly prefix mismatch. Got:\n{}", disasm_text); - assert!(disasm_text.contains("FrameSync\n") || disasm_text.contains("FrameSync"), "Expected to find FrameSync for entry point. Got:\n{}", disasm_text); - } - - #[test] - fn test_hip_conformance_v0() { - use crate::ir_core::*; - use crate::lowering::lower_program; - use crate::backend; - use std::collections::HashMap; - - // --- 1. SETUP CORE IR FIXTURE --- - let mut const_pool = ConstPool::new(); - let val_42 = const_pool.add_int(42); - - let mut field_offsets = HashMap::new(); - let f1 = FieldId(0); - field_offsets.insert(f1, 0); - - let mut local_types = HashMap::new(); - local_types.insert(0, Type::Struct("Storage".to_string())); // slot 0: gate handle - local_types.insert(1, Type::Int); // slot 1: value 42 - local_types.insert(2, Type::Int); // slot 2: result of peek - - let program = Program { - const_pool, - modules: vec![Module { - name: "conformance".to_string(), - functions: vec![Function { - id: FunctionId(1), - name: "main".to_string(), - sig: { - let mut i = global_signature_interner().lock().unwrap(); - i.intern(Signature { params: vec![], return_type: Type::Void }) - }, - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - // 1. allocates a storage struct - Instr::from(InstrKind::Alloc { ty: TypeId(1), slots: 2 }), - Instr::from(InstrKind::SetLocal(0)), - - // 2. mutates a field (offset 0) - Instr::from(InstrKind::BeginMutate { gate: ValueId(0) }), - Instr::from(InstrKind::PushConst(val_42)), - Instr::from(InstrKind::SetLocal(1)), - Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) }), - Instr::from(InstrKind::EndMutate), - - // 3. peeks value (offset 0) - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: f1 }), - Instr::from(InstrKind::SetLocal(2)), - Instr::from(InstrKind::EndPeek), - ], - terminator: Terminator::Return, - }], - local_types, - }], - }], - field_offsets, - field_types: HashMap::new(), - }; - - // --- 2. LOWER TO VM IR --- - let vm_module = lower_program(&program).expect("Lowering failed"); - - let func = &vm_module.functions[0]; - let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); - - // Expected sequence of significant instructions: - // Alloc, LocalStore(0), GateBeginMutate, PushConst, LocalStore(1), LocalLoad(0), LocalLoad(1), GateStore(0), GateEndMutate... - - assert!(kinds.iter().any(|k| matches!(k, ir_lang::InstrKind::Alloc { .. })), "Must contain Alloc"); - assert!(kinds.iter().any(|k| matches!(k, ir_lang::InstrKind::GateBeginMutate)), "Must contain GateBeginMutate"); - assert!(kinds.iter().any(|k| matches!(k, ir_lang::InstrKind::GateStore { offset: 0 })), "Must contain GateStore(0)"); - assert!(kinds.iter().any(|k| matches!(k, ir_lang::InstrKind::GateBeginPeek)), "Must contain GateBeginPeek"); - assert!(kinds.iter().any(|k| matches!(k, ir_lang::InstrKind::GateLoad { offset: 0 })), "Must contain GateLoad(0)"); - - // RC assertions: - assert!(kinds.contains(&&ir_lang::InstrKind::GateRetain), "Must contain GateRetain (on LocalLoad of gate)"); - assert!(kinds.contains(&&ir_lang::InstrKind::GateRelease), "Must contain GateRelease (on cleanup or Pop)"); - - // --- 4. EMIT BYTECODE --- - let emit_result = backend::emit_module(&vm_module).expect("Emission failed"); - - let rom = emit_result.rom; - - // --- 5. ASSERT INDUSTRIAL FORMAT --- - use prometeu_bytecode::BytecodeLoader; - let pbc = BytecodeLoader::load(&rom).expect("Failed to parse industrial PBC"); - - assert_eq!(&rom[0..4], b"PBS\0"); - assert_eq!(pbc.const_pool.len(), 2); // Null, 42 - - // ROM Data contains HIP opcodes: - let code = pbc.code; - assert!(code.iter().any(|&b| b == 0x60), "Bytecode must contain Alloc (0x60)"); - assert!(code.iter().any(|&b| b == 0x67), "Bytecode must contain GateBeginMutate (0x67)"); - assert!(code.iter().any(|&b| b == 0x62), "Bytecode must contain GateStore (0x62)"); - assert!(code.iter().any(|&b| b == 0x63), "Bytecode must contain GateBeginPeek (0x63)"); - assert!(code.iter().any(|&b| b == 0x61), "Bytecode must contain GateLoad (0x61)"); - assert!(code.iter().any(|&b| b == 0x69), "Bytecode must contain GateRetain (0x69)"); - assert!(code.iter().any(|&b| b == 0x6A), "Bytecode must contain GateRelease (0x6A)"); - } - - #[test] - fn test_project_root_and_entry_resolution() { - let dir = tempdir().unwrap(); - let project_dir = dir.path(); - - // Create prometeu.json - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "resolution_test", - "version": "0.1.0", - "script_fe": "pbs", - "entry": "src/main/modules/main.pbs" - }"#, - ).unwrap(); - - // Create src directory and main.pbs (must contain entry point frame(): void) - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - fs::write( - project_dir.join("src/main/modules/main.pbs"), - "fn frame(): void { return; }", - ) - .unwrap(); - - // Call compile - let result = compile(project_dir); - - assert!(result.is_ok(), "Failed to compile: {:?}", result.err()); - } - - // #[test] - // fn test_symbols_emission_integration() { - // let dir = tempdir().unwrap(); - // let project_dir = dir.path(); - // - // fs::write( - // project_dir.join("prometeu.json"), - // r#"{ - // "name": "symbols_test", - // "version": "0.1.0", - // "script_fe": "pbs", - // "entry": "src/main/modules/main.pbs" - // }"#, - // ).unwrap(); - // - // let code = r#" - // fn frame(): void { - // let x = 10; - // } - // "#; - // fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - // fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap(); - // - // let unit = compile(project_dir).expect("Failed to compile"); - // let out_pbc = project_dir.join("../../../../build/program.pbc"); - // fs::create_dir_all(out_pbc.parent().unwrap()).unwrap(); - // - // unit.export(&out_pbc, false, true).expect("Failed to export"); - // - // let symbols_path = project_dir.join("../../../../build/symbols.json"); - // assert!(symbols_path.exists(), "symbols.json should exist at {:?}", symbols_path); - // - // let symbols_content = fs::read_to_string(symbols_path).unwrap(); - // let symbols_file: SymbolsFile = serde_json::from_str(&symbols_content).unwrap(); - // - // assert_eq!(symbols_file.schema_version, 1); - // assert!(!symbols_file.projects.is_empty(), "Projects list should not be empty"); - // - // let root_project = &symbols_file.projects[0]; - // assert!(!root_project.symbols.is_empty(), "Symbols list should not be empty"); - // - // // Check for a symbol (v0 schema uses 0-based lines) - // let main_sym = root_project.symbols.iter().find(|s| s.name == "frame"); - // assert!(main_sym.is_some(), "Should find 'frame' symbol"); - // - // let sym = main_sym.unwrap(); - // assert!(sym.decl_span.file_uri.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.decl_span.file_uri); - // - // // Check analysis.json exists and has the basic structure - // let analysis_path = project_dir.join("build/analysis.json"); - // assert!(analysis_path.exists(), "analysis.json should exist at {:?}", analysis_path); - // let analysis_content = fs::read_to_string(analysis_path).unwrap(); - // #[derive(serde::Deserialize)] - // struct MinimalAnalysisV0 { - // schema_version: u32, - // file_table: Vec, - // name_table: Vec, - // module_table: Vec, - // symbols: Vec, - // } - // let analysis: MinimalAnalysisV0 = serde_json::from_str(&analysis_content).unwrap(); - // assert_eq!(analysis.schema_version, 0); - // assert!(!analysis.file_table.is_empty()); - // assert!(!analysis.name_table.is_empty()); - // assert!(!analysis.module_table.is_empty()); - // assert!(!analysis.symbols.is_empty()); - // } -} diff --git a/crates/compiler/prometeu-compiler/src/deps/cache.rs b/crates/compiler/prometeu-compiler/src/deps/cache.rs deleted file mode 100644 index b85b09bf..00000000 --- a/crates/compiler/prometeu-compiler/src/deps/cache.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anyhow::Result; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::path::{Path, PathBuf}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CacheManifest { - #[serde(default)] - pub git: HashMap, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct GitCacheEntry { - pub path: PathBuf, - pub resolved_ref: String, - pub fetched_at: String, -} - -impl CacheManifest { - pub fn load(cache_dir: &Path) -> Result { - let manifest_path = cache_dir.join("cache.json"); - if !manifest_path.exists() { - return Ok(Self { - git: HashMap::new(), - }); - } - let content = fs::read_to_string(&manifest_path)?; - let manifest = serde_json::from_str(&content)?; - Ok(manifest) - } - - pub fn save(&self, cache_dir: &Path) -> Result<()> { - if !cache_dir.exists() { - fs::create_dir_all(cache_dir)?; - } - let manifest_path = cache_dir.join("cache.json"); - let content = serde_json::to_string_pretty(self)?; - fs::write(manifest_path, content)?; - Ok(()) - } -} - -pub fn get_cache_root(project_root: &Path) -> PathBuf { - project_root.join("cache") -} - -pub fn get_git_worktree_path(project_root: &Path, repo_url: &str) -> PathBuf { - let cache_root = get_cache_root(project_root); - let id = normalized_repo_id(repo_url); - cache_root.join("git").join(id).join("worktree") -} - -fn normalized_repo_id(url: &str) -> String { - let mut hash = 0xcbf29ce484222325; - for b in url.as_bytes() { - hash ^= *b as u64; - hash = hash.wrapping_mul(0x100000001b3); - } - format!("{:016x}", hash) -} diff --git a/crates/compiler/prometeu-compiler/src/deps/fetch.rs b/crates/compiler/prometeu-compiler/src/deps/fetch.rs deleted file mode 100644 index 47000525..00000000 --- a/crates/compiler/prometeu-compiler/src/deps/fetch.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::deps::cache::{get_cache_root, get_git_worktree_path, CacheManifest, GitCacheEntry}; -use crate::manifest::DependencySpec; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; - -#[derive(Debug)] -pub enum FetchError { - Io(std::io::Error), - CloneFailed { - url: String, - stderr: String, - }, - MissingManifest(PathBuf), - InvalidPath(PathBuf), - CacheError(String), -} - -impl std::fmt::Display for FetchError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FetchError::Io(e) => write!(f, "IO error: {}", e), - FetchError::CloneFailed { url, stderr } => { - write!(f, "Failed to clone git repository from '{}': {}", url, stderr) - } - FetchError::MissingManifest(path) => { - write!(f, "Missing 'prometeu.json' in fetched project at {}", path.display()) - } - FetchError::InvalidPath(path) => { - write!(f, "Invalid dependency path: {}", path.display()) - } - FetchError::CacheError(msg) => write!(f, "Cache error: {}", msg), - } - } -} - -impl std::error::Error for FetchError {} - -impl From for FetchError { - fn from(e: std::io::Error) -> Self { - FetchError::Io(e) - } -} - -/// Fetches a dependency based on its specification. -pub fn fetch_dependency( - alias: &str, - spec: &DependencySpec, - base_dir: &Path, - root_project_dir: &Path, -) -> Result { - match spec { - DependencySpec::Path(p) => fetch_path(p, base_dir), - DependencySpec::Full(full) => { - if let Some(p) = &full.path { - fetch_path(p, base_dir) - } else if let Some(url) = &full.git { - let version = full.version.as_deref().unwrap_or("latest"); - fetch_git(url, version, root_project_dir) - } else { - Err(FetchError::InvalidPath(PathBuf::from(alias))) - } - } - } -} - -pub fn fetch_path(path_str: &str, base_dir: &Path) -> Result { - let path = base_dir.join(path_str); - if !path.exists() { - return Err(FetchError::InvalidPath(path)); - } - - let canonical = path.canonicalize()?; - if !canonical.join("prometeu.json").exists() { - return Err(FetchError::MissingManifest(canonical)); - } - - Ok(canonical) -} - -pub fn fetch_git(url: &str, version: &str, root_project_dir: &Path) -> Result { - let cache_root = get_cache_root(root_project_dir); - let mut manifest = CacheManifest::load(&cache_root).map_err(|e| FetchError::CacheError(e.to_string()))?; - - let target_dir = get_git_worktree_path(root_project_dir, url); - - if !target_dir.exists() { - fs::create_dir_all(&target_dir)?; - - let output = Command::new("git") - .arg("clone") - .arg(url) - .arg(".") - .current_dir(&target_dir) - .output()?; - - if !output.status.success() { - // Cleanup on failure - let _ = fs::remove_dir_all(&target_dir); - return Err(FetchError::CloneFailed { - url: url.to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - }); - } - - // TODO: Handle version/pinning (v0 pins to HEAD for now) - if version != "latest" { - let output = Command::new("git") - .arg("checkout") - .arg(version) - .current_dir(&target_dir) - .output()?; - - if !output.status.success() { - // We keep the clone but maybe should report error? - // For v0 we just attempt it. - return Err(FetchError::CloneFailed { - url: url.to_string(), - stderr: String::from_utf8_lossy(&output.stderr).to_string(), - }); - } - } - - // Update cache manifest - let rel_path = target_dir.strip_prefix(root_project_dir).map_err(|_| FetchError::CacheError("Path outside of project root".to_string()))?; - manifest.git.insert(url.to_string(), GitCacheEntry { - path: rel_path.to_path_buf(), - resolved_ref: version.to_string(), - fetched_at: "2026-02-02T00:00:00Z".to_string(), // Use a fixed timestamp or actual one? The requirement said "2026-02-02T00:00:00Z" in example - }); - manifest.save(&cache_root).map_err(|e| FetchError::CacheError(e.to_string()))?; - } - - if !target_dir.join("prometeu.json").exists() { - return Err(FetchError::MissingManifest(target_dir)); - } - - Ok(target_dir) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_fetch_path_resolves_relative() { - let tmp = tempdir().unwrap(); - let base = tmp.path().join("base"); - let dep = tmp.path().join("dep"); - fs::create_dir_all(&base).unwrap(); - fs::create_dir_all(&dep).unwrap(); - fs::write(dep.join("prometeu.json"), "{}").unwrap(); - - let fetched = fetch_path("../dep", &base).unwrap(); - assert_eq!(fetched.canonicalize().unwrap(), dep.canonicalize().unwrap()); - } - - #[test] - fn test_fetch_git_local_mock() { - let tmp = tempdir().unwrap(); - let project_root = tmp.path().join("project"); - let remote_dir = tmp.path().join("remote"); - fs::create_dir_all(&project_root).unwrap(); - fs::create_dir_all(&remote_dir).unwrap(); - - // Init remote git repo - let _ = Command::new("git").arg("init").current_dir(&remote_dir).status(); - let _ = Command::new("git").arg("config").arg("user.email").arg("you@example.com").current_dir(&remote_dir).status(); - let _ = Command::new("git").arg("config").arg("user.name").arg("Your Name").current_dir(&remote_dir).status(); - - fs::write(remote_dir.join("prometeu.json"), r#"{"name": "remote", "version": "1.0.0"}"#).unwrap(); - let _ = Command::new("git").arg("add").arg(".").current_dir(&remote_dir).status(); - let _ = Command::new("git").arg("commit").arg("-m").arg("initial").current_dir(&remote_dir).status(); - - let url = format!("file://{}", remote_dir.display()); - let fetched = fetch_git(&url, "latest", &project_root); - - // Only assert if git succeeded (it might not be in all CI envs, though should be here) - if let Ok(path) = fetched { - assert!(path.exists()); - assert!(path.join("prometeu.json").exists()); - - // Check cache manifest - let cache_json = project_root.join("cache/cache.json"); - assert!(cache_json.exists()); - let content = fs::read_to_string(cache_json).unwrap(); - assert!(content.contains(&url)); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/deps/mod.rs b/crates/compiler/prometeu-compiler/src/deps/mod.rs deleted file mode 100644 index 95d05564..00000000 --- a/crates/compiler/prometeu-compiler/src/deps/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod resolver; -pub mod fetch; -pub mod cache; diff --git a/crates/compiler/prometeu-compiler/src/deps/resolver.rs b/crates/compiler/prometeu-compiler/src/deps/resolver.rs deleted file mode 100644 index d2fac8cc..00000000 --- a/crates/compiler/prometeu-compiler/src/deps/resolver.rs +++ /dev/null @@ -1,750 +0,0 @@ -use crate::analysis::project_registry::ProjectRegistry; -use crate::deps::fetch::{fetch_dependency, FetchError}; -use crate::manifest::{load_manifest, Manifest}; -use crate::sources::{discover, ProjectSources, SourceError}; -use prometeu_analysis::ids::ProjectId; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; - -// Re-export for external modules/tests that previously imported from here -pub use crate::analysis::project_registry::ProjectKey; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ResolvedNode { - pub id: ProjectId, - pub key: ProjectKey, - pub path: PathBuf, - pub manifest: Manifest, - pub sources: ProjectSources, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ResolvedEdge { - pub alias: String, - pub to: ProjectId, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ResolutionStep { - TryResolve { - alias: String, - spec: String, - }, - Resolved { - project_id: ProjectKey, - path: PathBuf, - }, - UsingCached { - project_id: ProjectKey, - }, - Conflict { - name: String, - existing_version: String, - new_version: String, - }, - Error { - message: String, - }, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct ResolutionTrace { - pub steps: Vec, -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct ResolvedGraph { - pub nodes: HashMap, - pub edges: HashMap>, - pub root_id: Option, - pub trace: ResolutionTrace, - #[serde(skip)] - pub registry: ProjectRegistry, -} - -impl ResolvedGraph { - pub fn resolve_import_path(&self, from_node: &ProjectId, import_path: &str) -> Option { - if import_path.starts_with('@') { - let parts: Vec<&str> = import_path[1..].splitn(2, ':').collect(); - if parts.len() == 2 { - let alias = parts[0]; - let module_name = parts[1]; - - // Find dependency by alias - if let Some(edges) = self.edges.get(from_node) { - if let Some(edge) = edges.iter().find(|e| e.alias == alias) { - if let Some(node) = self.nodes.get(&edge.to) { - // Found the dependency project. Now find the module inside it. - let module_path = node.path.join("src/main/modules").join(module_name); - return Some(module_path); - } - } - } - } - } else { - // Local import (relative to current project's src/main/modules) - if let Some(node) = self.nodes.get(from_node) { - return Some(node.path.join("src/main/modules").join(import_path)); - } - } - None - } - - pub fn explain(&self) -> String { - let mut out = String::new(); - out.push_str("--- Dependency Resolution Trace ---\n"); - for step in &self.trace.steps { - match step { - ResolutionStep::TryResolve { alias, spec } => { - out.push_str(&format!(" [?] Resolving '{}' (spec: {})\n", alias, spec)); - } - ResolutionStep::Resolved { project_id, path } => { - out.push_str(&format!(" [✓] Resolved '{}' v{} at {:?}\n", project_id.name, project_id.version, path)); - } - ResolutionStep::UsingCached { project_id } => { - out.push_str(&format!(" [.] Using cached '{}' v{}\n", project_id.name, project_id.version)); - } - ResolutionStep::Conflict { name, existing_version, new_version } => { - out.push_str(&format!(" [!] CONFLICT for '{}': {} vs {}\n", name, existing_version, new_version)); - } - ResolutionStep::Error { message } => { - out.push_str(&format!(" [X] ERROR: {}\n", message)); - } - } - } - - if let Some(root_id) = &self.root_id { - out.push_str("\n--- Resolved Dependency Graph ---\n"); - let mut visited = HashSet::new(); - if let Some(meta) = self.registry.meta(*root_id) { - out.push_str(&format!("{} v{}\n", meta.name, meta.version)); - } - self.print_node(root_id, 0, &mut out, &mut visited); - } - - out - } - - fn print_node(&self, id: &ProjectId, indent: usize, out: &mut String, visited: &mut HashSet) { - if let Some(edges) = self.edges.get(id) { - for edge in edges { - let prefix = " ".repeat(indent); - if let Some(meta) = self.registry.meta(edge.to) { - out.push_str(&format!("{}└── {}: {} v{}\n", prefix, edge.alias, meta.name, meta.version)); - } - if !visited.contains(&edge.to) { - visited.insert(edge.to); - self.print_node(&edge.to, indent + 1, out, visited); - } - } - } - } -} - -#[derive(Debug)] -pub enum ResolveError { - CycleDetected(Vec), - MissingDependency(PathBuf), - VersionConflict { - name: String, - v1: String, - v2: String, - }, - NameCollision { - name: String, - p1: PathBuf, - p2: PathBuf, - }, - ManifestError(crate::manifest::ManifestError), - FetchError(FetchError), - SourceError(SourceError), - IoError { - path: PathBuf, - source: std::io::Error, - }, - WithTrace { - trace: ResolutionTrace, - source: Box, - }, -} - -impl std::fmt::Display for ResolveError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ResolveError::CycleDetected(chain) => write!(f, "Cycle detected: {}", chain.join(" -> ")), - ResolveError::MissingDependency(path) => write!(f, "Missing dependency at: {}", path.display()), - ResolveError::VersionConflict { name, v1, v2 } => { - write!(f, "Version conflict for project '{}': {} vs {}", name, v1, v2) - } - ResolveError::NameCollision { name, p1, p2 } => { - write!(f, "Name collision: two distinct projects claiming same name '{}' at {} and {}", name, p1.display(), p2.display()) - } - ResolveError::ManifestError(e) => write!(f, "Manifest error: {}", e), - ResolveError::FetchError(e) => write!(f, "Fetch error: {}", e), - ResolveError::SourceError(e) => write!(f, "Source error: {}", e), - ResolveError::IoError { path, source } => write!(f, "IO error at {}: {}", path.display(), source), - ResolveError::WithTrace { source, .. } => write!(f, "{}", source), - } - } -} - -impl std::error::Error for ResolveError {} - -impl From for ResolveError { - fn from(e: crate::manifest::ManifestError) -> Self { - ResolveError::ManifestError(e) - } -} - -impl From for ResolveError { - fn from(e: FetchError) -> Self { - ResolveError::FetchError(e) - } -} - -impl From for ResolveError { - fn from(e: SourceError) -> Self { - match e { - SourceError::Manifest(me) => ResolveError::ManifestError(me), - SourceError::Io(ioe) => ResolveError::IoError { - path: PathBuf::new(), - source: ioe, - }, - _ => ResolveError::SourceError(e), - } - } -} - -pub fn resolve_graph(root_dir: &Path) -> Result { - let mut graph = ResolvedGraph::default(); - let mut visited: HashSet = HashSet::new(); - let mut stack: Vec = Vec::new(); - - let root_path = root_dir.canonicalize().map_err(|e| ResolveError::IoError { - path: root_dir.to_path_buf(), - source: e, - })?; - - let root_id = match resolve_recursive(&root_path, &root_path, &mut graph, &mut visited, &mut stack) { - Ok(id) => id, - Err(e) => return Err(ResolveError::WithTrace { - trace: graph.trace, - source: Box::new(e), - }), - }; - graph.root_id = Some(root_id); - - Ok(graph) -} - -fn resolve_recursive( - project_path: &Path, - root_project_dir: &Path, - graph: &mut ResolvedGraph, - visited: &mut HashSet, - stack: &mut Vec, -) -> Result { - let manifest = load_manifest(project_path)?; - let sources = discover(project_path)?; - let project_key = ProjectKey { name: manifest.name.clone(), version: manifest.version.clone() }; - let project_id = graph.registry.intern(&project_key); - - // Cycle detection - if let Some(pos) = stack.iter().position(|id| id == &project_id) { - let mut chain: Vec = stack[pos..] - .iter() - .map(|id| graph.registry.meta(*id).map(|m| m.name.clone()).unwrap_or_else(|| format!("#{}", id.0))) - .collect(); - chain.push(project_key.name.clone()); - return Err(ResolveError::CycleDetected(chain)); - } - - // Collision handling: Name collision / Version conflict - for node in graph.nodes.values() { - if node.key.name == project_key.name { - if node.key.version != project_key.version { - graph.trace.steps.push(ResolutionStep::Conflict { - name: project_key.name.clone(), - existing_version: node.key.version.clone(), - new_version: project_key.version.clone(), - }); - return Err(ResolveError::VersionConflict { - name: project_key.name.clone(), - v1: node.key.version.clone(), - v2: project_key.version.clone(), - }); - } - if node.path != project_path { - return Err(ResolveError::NameCollision { - name: project_key.name.clone(), - p1: node.path.clone(), - p2: project_path.to_path_buf(), - }); - } - } - } - - // If already fully visited, return the ID - if visited.contains(&project_id) { - graph.trace.steps.push(ResolutionStep::UsingCached { project_id: project_key.clone() }); - return Ok(project_id); - } - - graph.trace.steps.push(ResolutionStep::Resolved { project_id: project_key.clone(), path: project_path.to_path_buf() }); - - visited.insert(project_id); - stack.push(project_id); - - let mut edges = Vec::new(); - for (alias, spec) in &manifest.dependencies { - graph.trace.steps.push(ResolutionStep::TryResolve { alias: alias.clone(), spec: format!("{:?}", spec) }); - - let dep_path = match fetch_dependency(alias, spec, project_path, root_project_dir) { - Ok(p) => p, - Err(e) => { - graph.trace.steps.push(ResolutionStep::Error { message: format!("Fetch error for '{}': {}", alias, e) }); - return Err(e.into()); - } - }; - - let dep_id = match resolve_recursive(&dep_path, root_project_dir, graph, visited, stack) { - Ok(id) => id, - Err(e) => return Err(e), - }; - - edges.push(ResolvedEdge { alias: alias.clone(), to: dep_id }); - } - - stack.pop(); - graph.nodes.insert(project_id, ResolvedNode { id: project_id, key: project_key, path: project_path.to_path_buf(), manifest, sources }); - graph.edges.insert(project_id, edges); - - Ok(project_id) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_simple_graph() { - let dir = tempdir().unwrap(); - let root = dir.path().join("root"); - let dep = dir.path().join("dep"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&dep).unwrap(); - - fs::write(root.join("prometeu.json"), r#"{ - "name": "root", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "d": "../dep" } - }"#).unwrap(); - - fs::write(dep.join("prometeu.json"), r#"{ - "name": "dep", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - let graph = resolve_graph(&root).unwrap(); - assert_eq!(graph.nodes.len(), 2); - let root_id = graph.root_id.as_ref().unwrap(); - let root_meta = graph.registry.meta(*root_id).unwrap(); - assert_eq!(root_meta.name, "root"); - - let edges = graph.edges.get(root_id).unwrap(); - assert_eq!(edges.len(), 1); - assert_eq!(edges[0].alias, "d"); - let dep_meta = graph.registry.meta(edges[0].to).unwrap(); - assert_eq!(dep_meta.name, "dep"); - } - - #[test] - fn test_cycle_detection() { - let dir = tempdir().unwrap(); - let a = dir.path().join("a"); - let b = dir.path().join("b"); - fs::create_dir_all(&a).unwrap(); - fs::create_dir_all(&b).unwrap(); - - fs::write(a.join("prometeu.json"), r#"{ - "name": "a", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "b": "../b" } - }"#).unwrap(); - - fs::write(b.join("prometeu.json"), r#"{ - "name": "b", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "a": "../a" } - }"#).unwrap(); - - let err = resolve_graph(&a).unwrap_err(); - match err { - ResolveError::WithTrace { source, .. } => { - if let ResolveError::CycleDetected(chain) = *source { - assert_eq!(chain, vec!["a", "b", "a"]); - } else { - panic!("Expected CycleDetected error, got {:?}", source); - } - } - _ => panic!("Expected WithTrace containing CycleDetected error, got {:?}", err), - } - } - - #[test] - fn test_alias_does_not_change_identity() { - let dir = tempdir().unwrap(); - let root = dir.path().join("root"); - let dep = dir.path().join("dep"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&dep).unwrap(); - - fs::write(root.join("prometeu.json"), r#"{ - "name": "root", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "my_alias": "../dep" } - }"#).unwrap(); - - fs::write(dep.join("prometeu.json"), r#"{ - "name": "actual_name", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - let graph = resolve_graph(&root).unwrap(); - let root_id = graph.root_id.as_ref().unwrap(); - let edges = graph.edges.get(root_id).unwrap(); - assert_eq!(edges[0].alias, "my_alias"); - let dep_meta = graph.registry.meta(edges[0].to).unwrap(); - assert_eq!(dep_meta.name, "actual_name"); - assert!(graph.nodes.contains_key(&edges[0].to)); - } - - #[test] - fn test_version_conflict() { - let dir = tempdir().unwrap(); - let root = dir.path().join("root"); - let dep1 = dir.path().join("dep1"); - let dep2 = dir.path().join("dep2"); - let shared = dir.path().join("shared1"); - let shared2 = dir.path().join("shared2"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&dep1).unwrap(); - fs::create_dir_all(&dep2).unwrap(); - fs::create_dir_all(&shared).unwrap(); - fs::create_dir_all(&shared2).unwrap(); - - fs::write(root.join("prometeu.json"), r#"{ - "name": "root", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "d1": "../dep1", "d2": "../dep2" } - }"#).unwrap(); - - fs::write(dep1.join("prometeu.json"), r#"{ - "name": "dep1", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "s": "../shared1" } - }"#).unwrap(); - - fs::write(dep2.join("prometeu.json"), r#"{ - "name": "dep2", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "s": "../shared2" } - }"#).unwrap(); - - fs::write(shared.join("prometeu.json"), r#"{ - "name": "shared", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - fs::write(shared2.join("prometeu.json"), r#"{ - "name": "shared", - "version": "2.0.0", - "kind": "lib" - }"#).unwrap(); - - let err = resolve_graph(&root).unwrap_err(); - match err { - ResolveError::WithTrace { source, .. } => { - if let ResolveError::VersionConflict { name, .. } = *source { - assert_eq!(name, "shared"); - } else { - panic!("Expected VersionConflict error, got {:?}", source); - } - } - _ => panic!("Expected WithTrace containing VersionConflict error, got {:?}", err), - } - } - - #[test] - fn test_name_collision() { - let dir = tempdir().unwrap(); - let root = dir.path().join("root"); - let dep1 = dir.path().join("dep1"); - let dep2 = dir.path().join("dep2"); - let p1 = dir.path().join("p1"); - let p2 = dir.path().join("p2"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&dep1).unwrap(); - fs::create_dir_all(&dep2).unwrap(); - fs::create_dir_all(&p1).unwrap(); - fs::create_dir_all(&p2).unwrap(); - - fs::write(root.join("prometeu.json"), r#"{ - "name": "root", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "d1": "../dep1", "d2": "../dep2" } - }"#).unwrap(); - - fs::write(dep1.join("prometeu.json"), r#"{ - "name": "dep1", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "p": "../p1" } - }"#).unwrap(); - - fs::write(dep2.join("prometeu.json"), r#"{ - "name": "dep2", - "version": "0.1.0", - "kind": "lib", - "dependencies": { "p": "../p2" } - }"#).unwrap(); - - // Both p1 and p2 claim to be "collision" version 1.0.0 - fs::write(p1.join("prometeu.json"), r#"{ - "name": "collision", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - fs::write(p2.join("prometeu.json"), r#"{ - "name": "collision", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - let err = resolve_graph(&root).unwrap_err(); - match err { - ResolveError::WithTrace { source, .. } => { - if let ResolveError::NameCollision { name, .. } = *source { - assert_eq!(name, "collision"); - } else { - panic!("Expected NameCollision error, got {:?}", source); - } - } - _ => panic!("Expected WithTrace containing NameCollision error, got {:?}", err), - } - } - - #[test] - fn test_resolve_with_git_dependency_mock() { - let tmp = tempdir().unwrap(); - let root = tmp.path().join("root"); - let remote = tmp.path().join("remote"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&remote).unwrap(); - - // Setup remote - let _ = std::process::Command::new("git").arg("init").current_dir(&remote).status(); - let _ = std::process::Command::new("git").arg("config").arg("user.email").arg("you@example.com").current_dir(&remote).status(); - let _ = std::process::Command::new("git").arg("config").arg("user.name").arg("Your Name").current_dir(&remote).status(); - fs::write(remote.join("prometeu.json"), r#"{"name": "remote", "version": "1.2.3", "kind": "lib"}"#).unwrap(); - let _ = std::process::Command::new("git").arg("add").arg(".").current_dir(&remote).status(); - let _ = std::process::Command::new("git").arg("commit").arg("-m").arg("init").current_dir(&remote).status(); - - // Setup root - fs::write(root.join("prometeu.json"), format!(r#"{{ - "name": "root", - "version": "0.1.0", - "kind": "lib", - "dependencies": {{ - "rem": {{ "git": "file://{}" }} - }} - }}"#, remote.display())).unwrap(); - - let graph = resolve_graph(&root); - - if let Ok(graph) = graph { - assert_eq!(graph.nodes.len(), 2); - let rem_node = graph.nodes.values().find(|n| n.key.name == "remote").unwrap(); - assert_eq!(rem_node.key.version, "1.2.3"); - - // Verify cache manifest was created - assert!(root.join("cache/cache.json").exists()); - } - } - - #[test] - fn test_resolve_import_path() { - let dir = tempdir().unwrap(); - let root = dir.path().join("root"); - let sdk = dir.path().join("sdk"); - fs::create_dir_all(&root).unwrap(); - fs::create_dir_all(&sdk).unwrap(); - let root = root.canonicalize().unwrap(); - let sdk = sdk.canonicalize().unwrap(); - - fs::create_dir_all(root.join("src/main/modules")).unwrap(); - fs::create_dir_all(sdk.join("src/main/modules/math")).unwrap(); - fs::write(root.join("src/main/modules/main.pbs"), "").unwrap(); - - fs::write(root.join("prometeu.json"), r#"{ - "name": "root", - "version": "0.1.0", - "kind": "app", - "dependencies": { "sdk": "../sdk" } - }"#).unwrap(); - - fs::write(sdk.join("prometeu.json"), r#"{ - "name": "sdk", - "version": "1.0.0", - "kind": "lib" - }"#).unwrap(); - - let graph = resolve_graph(&root).unwrap(); - let root_id = graph.root_id.as_ref().unwrap(); - - // Resolve @sdk:math - let path = graph.resolve_import_path(root_id, "@sdk:math").unwrap(); - assert_eq!(path.canonicalize().unwrap(), sdk.join("src/main/modules/math").canonicalize().unwrap()); - - // Resolve local module - let path = graph.resolve_import_path(root_id, "local_mod").unwrap(); - let expected = root.join("src/main/modules/local_mod"); - assert_eq!(path, expected); - } - - #[test] - fn test_resolution_trace_and_explain() { - let dir = tempdir().unwrap(); - let root_dir = dir.path().join("root"); - fs::create_dir_all(&root_dir).unwrap(); - let root_dir = root_dir.canonicalize().unwrap(); - - // Root project - fs::write(root_dir.join("prometeu.json"), r#"{ - "name": "root", - "version": "1.0.0", - "dependencies": { - "dep1": { "path": "../dep1" } - } - }"#).unwrap(); - fs::create_dir_all(root_dir.join("src/main/modules")).unwrap(); - fs::write(root_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - // Dep 1 - let dep1_dir = dir.path().join("dep1"); - fs::create_dir_all(&dep1_dir).unwrap(); - let dep1_dir = dep1_dir.canonicalize().unwrap(); - fs::write(dep1_dir.join("prometeu.json"), r#"{ - "name": "dep1", - "version": "1.1.0" - }"#).unwrap(); - fs::create_dir_all(dep1_dir.join("src/main/modules")).unwrap(); - fs::write(dep1_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let graph = resolve_graph(&root_dir).unwrap(); - let explanation = graph.explain(); - - assert!(explanation.contains("--- Dependency Resolution Trace ---")); - assert!(explanation.contains("[✓] Resolved 'root' v1.0.0")); - assert!(explanation.contains("[?] Resolving 'dep1'")); - assert!(explanation.contains("[✓] Resolved 'dep1' v1.1.0")); - - assert!(explanation.contains("--- Resolved Dependency Graph ---")); - assert!(explanation.contains("root v1.0.0")); - assert!(explanation.contains("└── dep1: dep1 v1.1.0")); - } - - #[test] - fn test_conflict_explanation() { - let dir = tempdir().unwrap(); - let root_dir = dir.path().join("root"); - fs::create_dir_all(&root_dir).unwrap(); - let root_dir = root_dir.canonicalize().unwrap(); - - // Root -> A, B - // A -> C v1 - // B -> C v2 - - fs::write(root_dir.join("prometeu.json"), r#"{ - "name": "root", - "version": "1.0.0", - "dependencies": { - "a": { "path": "../a" }, - "b": { "path": "../b" } - } - }"#).unwrap(); - fs::create_dir_all(root_dir.join("src/main/modules")).unwrap(); - fs::write(root_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let a_dir = dir.path().join("a"); - fs::create_dir_all(&a_dir).unwrap(); - let a_dir = a_dir.canonicalize().unwrap(); - fs::write(a_dir.join("prometeu.json"), r#"{ - "name": "a", - "version": "1.0.0", - "dependencies": { "c": { "path": "../c1" } } - }"#).unwrap(); - fs::create_dir_all(a_dir.join("src/main/modules")).unwrap(); - fs::write(a_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let b_dir = dir.path().join("b"); - fs::create_dir_all(&b_dir).unwrap(); - let b_dir = b_dir.canonicalize().unwrap(); - fs::write(b_dir.join("prometeu.json"), r#"{ - "name": "b", - "version": "1.0.0", - "dependencies": { "c": { "path": "../c2" } } - }"#).unwrap(); - fs::create_dir_all(b_dir.join("src/main/modules")).unwrap(); - fs::write(b_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let c1_dir = dir.path().join("c1"); - fs::create_dir_all(&c1_dir).unwrap(); - let c1_dir = c1_dir.canonicalize().unwrap(); - fs::write(c1_dir.join("prometeu.json"), r#"{ - "name": "c", - "version": "1.0.0" - }"#).unwrap(); - fs::create_dir_all(c1_dir.join("src/main/modules")).unwrap(); - fs::write(c1_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let c2_dir = dir.path().join("c2"); - fs::create_dir_all(&c2_dir).unwrap(); - let c2_dir = c2_dir.canonicalize().unwrap(); - fs::write(c2_dir.join("prometeu.json"), r#"{ - "name": "c", - "version": "2.0.0" - }"#).unwrap(); - fs::create_dir_all(c2_dir.join("src/main/modules")).unwrap(); - fs::write(c2_dir.join("src/main/modules/main.pbs"), "").unwrap(); - - let res = resolve_graph(&root_dir); - assert!(res.is_err()); - - if let Err(ResolveError::WithTrace { trace, source }) = res { - let mut dummy = ResolvedGraph::default(); - dummy.trace = trace; - let explanation = dummy.explain(); - - assert!(explanation.contains("[!] CONFLICT for 'c': 1.0.0 vs 2.0.0")); - assert!(source.to_string().contains("Version conflict for project 'c': 1.0.0 vs 2.0.0")); - } else { - panic!("Expected WithTrace error"); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/mod.rs b/crates/compiler/prometeu-compiler/src/frontends/mod.rs deleted file mode 100644 index 23395127..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; -use crate::ir_lang; -use std::path::Path; - -use crate::common::files::FileManager; - -pub mod pbs; - -pub trait Frontend { - fn language(&self) -> &'static str; - - fn compile_to_ir( - &self, - entry: &Path, - file_manager: &mut FileManager, - ) -> Result; -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/adapter.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/adapter.rs deleted file mode 100644 index 39995331..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/adapter.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::frontends::pbs::{parser::Parser, SymbolCollector, ModuleSymbols, Resolver, ModuleProvider, Lowerer}; -use crate::frontends::pbs::typecheck::TypeChecker; -use crate::lowering::core_to_vm; -use crate::common::spans::FileId; -use language_api::traits::{Frontend as CanonFrontend, FrontendUnit}; -use language_api::types::{ - Diagnostic as CanonDiagnostic, - Severity as CanonSeverity, - ExportItem, - ItemName, - LoweredIr, - ImportRef, - CanonicalFnKey, - SignatureRef, - parse_pbs_from_string, -}; -use prometeu_analysis::NameInterner; -use std::path::Path; - -/// Adapter implementing the canonical Frontend contract for PBS. -pub struct PbsFrontendAdapter; - -impl CanonFrontend for PbsFrontendAdapter { - fn parse_and_analyze(&self, entry_path: &str) -> FrontendUnit { - // Minimal translation: run existing PBS pipeline and package results into canonical unit. - let path = Path::new(entry_path); - let mut diags: Vec = Vec::new(); - - let source = match std::fs::read_to_string(path) { - Ok(s) => s, - Err(e) => { - diags.push(CanonDiagnostic::error(format!("Failed to read file: {}", e))); - return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() }; - } - }; - - let mut interner = NameInterner::new(); - let mut parser = Parser::new(&source, FileId(0), &mut interner); - let parsed = match parser.parse_file() { - Ok(p) => p, - Err(d) => { - // Translate diagnostics coarsely - diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error }); - return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() }; - } - }; - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = match collector.collect(&parsed.arena, parsed.root) { - Ok(v) => v, - Err(d) => { - diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error }); - return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() }; - } - }; - let mut module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - // Ensure primitives are interned in this FE-local interner - let primitives = ["int", "bool", "float", "string", "bounded", "void"]; - for p in primitives { interner.intern(p); } - // Resolver scope (immutable borrow of module_symbols limited to this block) - let imported_symbols = { - let mut resolver = Resolver::new(&module_symbols, &EmptyProvider, &interner); - resolver.bootstrap_types(&interner); - if let Err(d) = resolver.resolve(&parsed.arena, parsed.root) { - diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error }); - return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() }; - } - resolver.imported_symbols.clone() - }; - - // Run PBS typechecker to compute function/method signatures and basic type info - // This ensures exported/imported symbols carry `PbsType::Function { params, .. }` so - // lowering can perform deterministic overload resolution by exact signature. - let mut tc = TypeChecker::new(&mut module_symbols, &imported_symbols, &EmptyProvider, &interner); - if let Err(d) = tc.check(&parsed.arena, parsed.root) { - diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error }); - return FrontendUnit { diagnostics: diags, imports: vec![], exports: vec![], lowered_ir: LoweredIr::default() }; - } - - // Collect canonical imports from AST imports: `from` must be "@alias:module" and items single identifiers - let mut imports: Vec = Vec::new(); - if let crate::frontends::pbs::ast::NodeKind::File(file_node) = parsed.arena.kind(parsed.root) { - for imp_id in &file_node.imports { - if let crate::frontends::pbs::ast::NodeKind::Import(imp) = parsed.arena.kind(*imp_id) { - // Parse project/module from `from` string - match parse_pbs_from_string(&imp.from) { - Ok((project, module)) => { - // Resolve item names from spec node - if let crate::frontends::pbs::ast::NodeKind::ImportSpec(spec) = parsed.arena.kind(imp.spec) { - for &name_id in &spec.path { - let name_str = interner.resolve(name_id); - match ItemName::new(name_str) { - Ok(item) => imports.push(ImportRef::new(project.clone(), module.clone(), item)), - Err(e) => diags.push(CanonDiagnostic::error(format!( - "Invalid import item '{}': {}", - name_str, e - ))), - } - } - } - } - Err(e) => diags.push(CanonDiagnostic::error(format!( - "Invalid import path '{}': {}", - &imp.from, e - ))), - } - } - } - } - - // Prepare canonical exports from symbol tables (canonical, no string protocols) - let mut exports: Vec = Vec::new(); - for list in module_symbols.type_symbols.symbols.values() { - for sym in list { - if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; } - if let Ok(name) = ItemName::new(interner.resolve(sym.name)) { - match sym.kind { - crate::frontends::pbs::symbols::SymbolKind::Service => exports.push(ExportItem::Service { name }), - _ => exports.push(ExportItem::Type { name }), - } - } - } - } - for list in module_symbols.value_symbols.symbols.values() { - for sym in list { - if crate::frontends::pbs::symbols::Visibility::Pub != sym.visibility { continue; } - // Build canonical function key for free fns and methods - let raw_name = interner.resolve(sym.name); - if let crate::frontends::pbs::symbols::SymbolKind::Function = sym.kind { - // No legacy string protocol inference (e.g., `svc:`). Owner is provided only via canonical models. - let owner_name = None; - // We don't have a stable signature id from PBS yet; use 0 as placeholder until resolver exposes it. - let sig = SignatureRef(0); - if let Ok(name_item) = ItemName::new(raw_name) { - let fn_key = CanonicalFnKey::new(owner_name, name_item, sig); - exports.push(ExportItem::Function { fn_key }); - } - } - } - } - - // Lower to VM IR and wrap as LoweredIr bytes - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported_symbols, &EmptyProvider, &interner); - let module_name = path.file_stem().unwrap().to_string_lossy(); - let core_program = match lowerer.lower_file(parsed.root, &module_name) { - Ok(c) => c, - Err(d) => { - diags.push(CanonDiagnostic { message: format!("{:?}", d), severity: CanonSeverity::Error }); - return FrontendUnit { diagnostics: diags, imports, exports, lowered_ir: LoweredIr::default() }; - } - }; - if let Err(e) = crate::ir_core::validate_program(&core_program) { - diags.push(CanonDiagnostic::error(format!("Core IR Invariant Violation: {}", e))); - return FrontendUnit { diagnostics: diags, imports, exports, lowered_ir: LoweredIr::default() }; - } - let vm_ir = match core_to_vm::lower_program(&core_program) { - Ok(m) => m, - Err(e) => { - diags.push(CanonDiagnostic::error(format!("Lowering error: {}", e))); - return FrontendUnit { diagnostics: diags, imports, exports, lowered_ir: LoweredIr::default() }; - } - }; - - let mut bytes = Vec::new(); - // Serialize VM IR using bincode-like debug encoding (placeholder); for now use JSON as opaque bytes. - // Backend owns the meaning; format tag indicates VM-IR. - if let Ok(s) = serde_json::to_string(&vm_ir) { - bytes.extend_from_slice(s.as_bytes()); - } - - FrontendUnit { - diagnostics: diags, - imports, - exports, - lowered_ir: LoweredIr::new("vm-ir-json", bytes), - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/ast.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/ast.rs deleted file mode 100644 index 598b28d1..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/ast.rs +++ /dev/null @@ -1,305 +0,0 @@ -use crate::common::spans::Span; -use prometeu_analysis::{NameId, NodeId}; -use serde::{Deserialize, Serialize}; - -// Use canonical NodeId from prometeu-analysis - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] -pub struct AstArena { - pub nodes: Vec, - pub spans: Vec, - pub roots: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ParsedAst { - pub arena: AstArena, - pub root: NodeId, -} - -impl AstArena { - pub fn push(&mut self, kind: NodeKind, span: Span) -> NodeId { - let id = NodeId(self.nodes.len() as u32); - self.nodes.push(kind); - self.spans.push(span); - id - } - - pub fn kind(&self, id: NodeId) -> &NodeKind { - &self.nodes[id.0 as usize] - } - - pub fn span(&self, id: NodeId) -> Span { - self.spans[id.0 as usize].clone() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(tag = "kind")] -pub enum NodeKind { - File(FileNodeArena), - Import(ImportNodeArena), - ImportSpec(ImportSpecNodeArena), - ServiceDecl(ServiceDeclNodeArena), - ServiceFnSig(ServiceFnSigNodeArena), - ServiceFnDecl(ServiceFnDeclNodeArena), - FnDecl(FnDeclNodeArena), - TypeDecl(TypeDeclNodeArena), - TypeBody(TypeBodyNodeArena), - Block(BlockNodeArena), - LetStmt(LetStmtNodeArena), - ExprStmt(ExprStmtNodeArena), - ReturnStmt(ReturnStmtNodeArena), - IntLit(IntLitNodeArena), - FloatLit(FloatLitNodeArena), - BoundedLit(BoundedLitNodeArena), - StringLit(StringLitNodeArena), - Ident(IdentNodeArena), - Call(CallNodeArena), - Unary(UnaryNodeArena), - Binary(BinaryNodeArena), - Cast(CastNodeArena), - IfExpr(IfExprNodeArena), - WhenExpr(WhenExprNodeArena), - WhenArm(WhenArmNodeArena), - TypeName(TypeNameNodeArena), - TypeApp(TypeAppNodeArena), - ConstructorDecl(ConstructorDeclNodeArena), - ConstantDecl(ConstantDeclNodeArena), - Alloc(AllocNodeArena), - Mutate(MutateNodeArena), - Borrow(BorrowNodeArena), - Peek(PeekNodeArena), - MemberAccess(MemberAccessNodeArena), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FileNodeArena { - pub imports: Vec, - pub decls: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ImportNodeArena { - pub spec: NodeId, - pub from: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ImportSpecNodeArena { - pub path: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ServiceDeclNodeArena { - pub vis: Option, - pub name: NameId, - pub extends: Option, - pub members: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ServiceFnSigNodeArena { - pub name: NameId, - pub params: Vec, - pub ret: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ServiceFnDeclNodeArena { - pub name: NameId, - pub params: Vec, - pub ret: NodeId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ParamNodeArena { - pub span: Span, - pub name: NameId, - pub ty: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FnDeclNodeArena { - pub vis: Option, - pub name: NameId, - pub params: Vec, - pub ret: Option, - pub else_fallback: Option, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TypeDeclNodeArena { - pub vis: Option, - pub type_kind: String, - pub name: NameId, - pub is_host: bool, - pub params: Vec, - pub constructors: Vec, - pub constants: Vec, - pub body: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ConstructorDeclNodeArena { - pub params: Vec, - pub initializers: Vec, - pub name: NameId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ConstantDeclNodeArena { - pub name: NameId, - pub value: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TypeBodyNodeArena { - pub members: Vec, - pub methods: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TypeMemberNodeArena { - pub span: Span, - pub name: NameId, - pub ty: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BlockNodeArena { - pub stmts: Vec, - pub tail: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct LetStmtNodeArena { - pub name: NameId, - pub is_mut: bool, - pub ty: Option, - pub init: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ExprStmtNodeArena { - pub expr: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ReturnStmtNodeArena { - pub expr: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IntLitNodeArena { - pub value: i64, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FloatLitNodeArena { - pub value: f64, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BoundedLitNodeArena { - pub value: u32, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct StringLitNodeArena { - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IdentNodeArena { - pub name: NameId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct CallNodeArena { - pub callee: NodeId, - pub args: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct UnaryNodeArena { - pub op: String, - pub expr: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BinaryNodeArena { - pub op: String, - pub left: NodeId, - pub right: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct CastNodeArena { - pub expr: NodeId, - pub ty: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct IfExprNodeArena { - pub cond: NodeId, - pub then_block: NodeId, - pub else_block: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct WhenExprNodeArena { - pub arms: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct WhenArmNodeArena { - pub cond: NodeId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TypeNameNodeArena { - pub name: NameId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TypeAppNodeArena { - pub base: NameId, - pub args: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct AllocNodeArena { - pub ty: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct MutateNodeArena { - pub target: NodeId, - pub binding: NameId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct BorrowNodeArena { - pub target: NodeId, - pub binding: NameId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct PeekNodeArena { - pub target: NodeId, - pub binding: NameId, - pub body: NodeId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct MemberAccessNodeArena { - pub object: NodeId, - pub member: NameId, -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/collector.rs deleted file mode 100644 index 2175e7fa..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/collector.rs +++ /dev/null @@ -1,242 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; -use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::symbols::*; -use crate::semantics::export_surface::ExportSurfaceKind; -use prometeu_analysis::{NameInterner, NodeId}; - -pub struct SymbolCollector<'a> { - interner: &'a NameInterner, - type_symbols: SymbolTable, - value_symbols: SymbolTable, - diagnostics: Vec, -} - -impl<'a> SymbolCollector<'a> { - pub fn new(interner: &'a NameInterner) -> Self { - Self { - interner, - type_symbols: SymbolTable::new(), - value_symbols: SymbolTable::new(), - diagnostics: Vec::new(), - } - } - - pub fn collect( - &mut self, - arena: &AstArena, - root: NodeId, - ) -> Result<(SymbolTable, SymbolTable), DiagnosticBundle> { - let file = match arena.kind(root) { - NodeKind::File(file) => file, - _ => { - return Err(DiagnosticBundle::error( - "E_COLLECT_INVALID_ROOT", - "Expected File node as root".to_string(), - arena.span(root), - )) - } - }; - - for decl in &file.decls { - match arena.kind(*decl) { - NodeKind::FnDecl(fn_decl) => self.collect_fn(arena, *decl, fn_decl), - NodeKind::ServiceDecl(service_decl) => self.collect_service(arena, *decl, service_decl), - NodeKind::TypeDecl(type_decl) => self.collect_type(arena, *decl, type_decl), - _ => {} - } - } - - if !self.diagnostics.is_empty() { - return Err(DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - }); - } - - Ok(( - std::mem::replace(&mut self.type_symbols, SymbolTable::new()), - std::mem::replace(&mut self.value_symbols, SymbolTable::new()), - )) - } - - fn collect_fn(&mut self, arena: &AstArena, id: NodeId, decl: &FnDeclNodeArena) { - let vis = match decl.vis.as_deref() { - Some("pub") => Visibility::Pub, - Some("mod") => Visibility::Mod, - _ => Visibility::FilePrivate, - }; - - let span = arena.span(id); - self.check_export_eligibility(SymbolKind::Function, vis, &span); - - let symbol = Symbol { - name: decl.name, - kind: SymbolKind::Function, - namespace: Namespace::Value, - visibility: vis, - ty: None, // Will be resolved later - is_host: false, - span: span.clone(), - origin: None, - }; - self.insert_value_symbol(symbol); - } - - fn collect_service(&mut self, arena: &AstArena, id: NodeId, decl: &ServiceDeclNodeArena) { - let vis = match decl.vis.as_deref() { - Some("pub") => Visibility::Pub, - _ => Visibility::Mod, // Defaults to Mod - }; - - let span = arena.span(id); - - self.check_export_eligibility(SymbolKind::Service, vis, &span); - - let symbol = Symbol { - name: decl.name, - kind: SymbolKind::Service, - namespace: Namespace::Type, // Service is a type - visibility: vis, - ty: None, - is_host: false, - span: span.clone(), - origin: None, - }; - self.insert_type_symbol(symbol); - - for member in &decl.members { - match arena.kind(*member) { - NodeKind::ServiceFnDecl(method) => { - // Export also as a value symbol (function) — simple name (unqualified) - let sym = Symbol { - name: method.name, - kind: SymbolKind::Function, - namespace: Namespace::Value, - visibility: vis, // herda do service - ty: None, - is_host: false, - span: arena.span(*member), - // No legacy string protocol markers (e.g., `svc:Service`). Canonical owner is handled elsewhere. - origin: None, - }; - self.insert_value_symbol(sym); - } - NodeKind::ServiceFnSig(method) => { - // Mesmo para assinaturas sem corpo, export surface deve conhecer o símbolo - let sym = Symbol { - name: method.name, - kind: SymbolKind::Function, - namespace: Namespace::Value, - visibility: vis, - ty: None, - is_host: false, - span: arena.span(*member), - origin: None, - }; - self.insert_value_symbol(sym); - } - _ => {} - } - } - } - - fn collect_type(&mut self, arena: &AstArena, id: NodeId, decl: &TypeDeclNodeArena) { - let vis = match decl.vis.as_deref() { - Some("pub") => Visibility::Pub, - Some("mod") => Visibility::Mod, - _ => Visibility::FilePrivate, - }; - let kind = match decl.type_kind.as_str() { - "struct" => SymbolKind::Struct, - "contract" => SymbolKind::Contract, - "error" => SymbolKind::ErrorType, - _ => SymbolKind::Struct, // Default - }; - - let span = arena.span(id); - - self.check_export_eligibility(kind.clone(), vis, &span); - - let symbol = Symbol { - name: decl.name, - kind, - namespace: Namespace::Type, - visibility: vis, - ty: None, - is_host: decl.is_host, - span: span.clone(), - origin: None, - }; - self.insert_type_symbol(symbol); - } - - fn insert_type_symbol(&mut self, symbol: Symbol) { - // Check for collision in value namespace first - if let Some(existing) = self.value_symbols.get(symbol.name) { - let existing = existing.clone(); - self.error_collision(&symbol, &existing); - return; - } - - if let Err(()) = self.type_symbols.insert(symbol.clone()) { - if let Some(existing) = self.type_symbols.get(symbol.name).cloned() { - self.error_duplicate(&symbol, &existing); - } - } - } - - fn insert_value_symbol(&mut self, symbol: Symbol) { - // Check for collision in type namespace first - if let Some(existing) = self.type_symbols.get(symbol.name) { - let existing = existing.clone(); - self.error_collision(&symbol, &existing); - return; - } - - if let Err(()) = self.value_symbols.insert(symbol.clone()) { - if let Some(existing) = self.value_symbols.get(symbol.name).cloned() { - self.error_duplicate(&symbol, &existing); - } - } - } - - fn error_duplicate(&mut self, symbol: &Symbol, existing: &Symbol) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(), - message: format!( - "Duplicate symbol '{}' already defined at {:?}", - self.interner.resolve(symbol.name), - &existing.span - ), - span: symbol.span.clone(), - related: Vec::new(), - }); - } - - fn error_collision(&mut self, symbol: &Symbol, existing: &Symbol) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_NAMESPACE_COLLISION".to_string(), - message: format!( - "DebugSymbol '{}' collides with another symbol in the {:?} namespace defined at {:?}", - self.interner.resolve(symbol.name), - existing.namespace, - &existing.span - ), - span: symbol.span.clone(), - related: Vec::new(), - }); - } - - fn check_export_eligibility(&mut self, kind: SymbolKind, vis: Visibility, span: &crate::common::spans::Span) { - if let Err(msg) = ExportSurfaceKind::validate_visibility(kind, vis) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_SEMANTIC_EXPORT_RESTRICTION".to_string(), - message: msg, - span: span.clone(), - related: Vec::new(), - }); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/contracts.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/contracts.rs deleted file mode 100644 index 9906d293..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/contracts.rs +++ /dev/null @@ -1,294 +0,0 @@ -use crate::frontends::pbs::types::PbsType; -use std::collections::HashMap; - -pub struct ContractMethod { - pub id: u32, - pub params: Vec, - pub return_type: PbsType, -} - -pub struct ContractRegistry { - mappings: HashMap>, -} - -impl ContractRegistry { - pub fn new() -> Self { - let mut mappings = HashMap::new(); - - // GFX mappings - let mut gfx = HashMap::new(); - gfx.insert("clear".to_string(), ContractMethod { - id: 0x1010, - params: vec![PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("fillRect".to_string(), ContractMethod { - id: 0x1002, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("drawLine".to_string(), ContractMethod { - id: 0x1003, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("drawCircle".to_string(), ContractMethod { - id: 0x1004, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("drawDisc".to_string(), ContractMethod { - id: 0x1005, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("drawSquare".to_string(), ContractMethod { - id: 0x1006, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - gfx.insert("setSprite".to_string(), ContractMethod { - id: 0x1007, - params: vec![PbsType::Int, PbsType::Int, PbsType::Int], - return_type: PbsType::Void, - }); - gfx.insert("drawText".to_string(), ContractMethod { - id: 0x1008, - params: vec![PbsType::Int, PbsType::Int, PbsType::String, PbsType::Struct("Color".to_string())], - return_type: PbsType::Void, - }); - mappings.insert("Gfx".to_string(), gfx); - - // Input legacy mappings (kept for backward compatibility) - let mut input = HashMap::new(); - input.insert("pad".to_string(), ContractMethod { - id: 0x2010, - params: vec![], - return_type: PbsType::Struct("Pad".to_string()), - }); - input.insert("touch".to_string(), ContractMethod { - id: 0x2011, - params: vec![], - return_type: PbsType::Struct("Touch".to_string()), - }); - mappings.insert("Input".to_string(), input); - - // New Pad service-based mappings (each button as a method returning Button) - let mut pad = HashMap::new(); - // NOTE: The syscalls for per-button access must be handled by the runtime. - // We reserve the 0x2200..0x220B range for Pad buttons returning a Button struct (6 bytes). - pad.insert("up".to_string(), ContractMethod { - id: 0x2200, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("down".to_string(), ContractMethod { - id: 0x2201, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("left".to_string(), ContractMethod { - id: 0x2202, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("right".to_string(), ContractMethod { - id: 0x2203, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("a".to_string(), ContractMethod { - id: 0x2204, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("b".to_string(), ContractMethod { - id: 0x2205, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("x".to_string(), ContractMethod { - id: 0x2206, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("y".to_string(), ContractMethod { - id: 0x2207, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("l".to_string(), ContractMethod { - id: 0x2208, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("r".to_string(), ContractMethod { - id: 0x2209, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("start".to_string(), ContractMethod { - id: 0x220A, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - pad.insert("select".to_string(), ContractMethod { - id: 0x220B, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - mappings.insert("Pad".to_string(), pad); - - // Touch mappings (service-based) - let mut touch = HashMap::new(); - // SDK agora expõe screen_x/screen_y/finger() - touch.insert("screen_x".to_string(), ContractMethod { - id: 0x2101, - params: vec![], - return_type: PbsType::Int, - }); - touch.insert("screen_y".to_string(), ContractMethod { - id: 0x2102, - params: vec![], - return_type: PbsType::Int, - }); - // Novo syscall dedicado para retornar Button completo do touch (dedo) - touch.insert("finger".to_string(), ContractMethod { - id: 0x2107, - params: vec![], - return_type: PbsType::Struct("Button".to_string()), - }); - mappings.insert("Touch".to_string(), touch); - - // Audio mappings - let mut audio = HashMap::new(); - audio.insert("playSample".to_string(), ContractMethod { - id: 0x3001, - params: vec![PbsType::Int], - return_type: PbsType::Void, - }); - audio.insert("play".to_string(), ContractMethod { - id: 0x3002, - params: vec![PbsType::Int], - return_type: PbsType::Void, - }); - mappings.insert("Audio".to_string(), audio); - - // FS mappings - let mut fs = HashMap::new(); - fs.insert("open".to_string(), ContractMethod { - id: 0x4001, - params: vec![PbsType::String, PbsType::String], - return_type: PbsType::Int, - }); - fs.insert("read".to_string(), ContractMethod { - id: 0x4002, - params: vec![PbsType::Int], - return_type: PbsType::String, - }); - fs.insert("write".to_string(), ContractMethod { - id: 0x4003, - params: vec![PbsType::Int, PbsType::String], - return_type: PbsType::Int, - }); - fs.insert("close".to_string(), ContractMethod { - id: 0x4004, - params: vec![PbsType::Int], - return_type: PbsType::Void, - }); - fs.insert("listDir".to_string(), ContractMethod { - id: 0x4005, - params: vec![PbsType::String], - return_type: PbsType::String, - }); - fs.insert("exists".to_string(), ContractMethod { - id: 0x4006, - params: vec![PbsType::String], - return_type: PbsType::Bool, - }); - fs.insert("delete".to_string(), ContractMethod { - id: 0x4007, - params: vec![PbsType::String], - return_type: PbsType::Bool, - }); - mappings.insert("Fs".to_string(), fs); - - // Log mappings (host) - let mut log = HashMap::new(); - log.insert("write".to_string(), ContractMethod { - id: 0x5001, - params: vec![PbsType::Int, PbsType::String], - // Log syscalls não retornam valor (void) — evita lixo de pilha - return_type: PbsType::Void, - }); - log.insert("writeTag".to_string(), ContractMethod { - id: 0x5002, - params: vec![PbsType::Int, PbsType::Int, PbsType::String], - return_type: PbsType::Void, - }); - // O contrato host exposto no SDK é LogHost (não-público) - mappings.insert("LogHost".to_string(), log); - - // System mappings - let mut system = HashMap::new(); - system.insert("hasCart".to_string(), ContractMethod { - id: 0x0001, - params: vec![], - return_type: PbsType::Bool, - }); - system.insert("runCart".to_string(), ContractMethod { - id: 0x0002, - params: vec![], - return_type: PbsType::Void, - }); - mappings.insert("System".to_string(), system); - - // Asset mappings - let mut asset = HashMap::new(); - asset.insert("load".to_string(), ContractMethod { - id: 0x6001, - params: vec![PbsType::String], - return_type: PbsType::Int, - }); - asset.insert("status".to_string(), ContractMethod { - id: 0x6002, - params: vec![PbsType::Int], - return_type: PbsType::Int, - }); - asset.insert("commit".to_string(), ContractMethod { - id: 0x6003, - params: vec![PbsType::Int], - return_type: PbsType::Void, - }); - asset.insert("cancel".to_string(), ContractMethod { - id: 0x6004, - params: vec![PbsType::Int], - return_type: PbsType::Void, - }); - mappings.insert("Asset".to_string(), asset); - - // Bank mappings - let mut bank = HashMap::new(); - bank.insert("info".to_string(), ContractMethod { - id: 0x6101, - params: vec![PbsType::Int], - return_type: PbsType::String, - }); - bank.insert("slotInfo".to_string(), ContractMethod { - id: 0x6102, - params: vec![PbsType::Int, PbsType::Int], - return_type: PbsType::String, - }); - mappings.insert("Bank".to_string(), bank); - - Self { mappings } - } - - pub fn resolve(&self, contract: &str, method: &str) -> Option { - self.mappings.get(contract).and_then(|m| m.get(method)).map(|m| m.id) - } - - pub fn get_method(&self, contract: &str, method: &str) -> Option<&ContractMethod> { - self.mappings.get(contract).and_then(|m| m.get(method)) - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/frontend.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/frontend.rs deleted file mode 100644 index a71320c3..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/frontend.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; -use crate::frontends::pbs::ast::ParsedAst; -use crate::frontends::pbs::resolver::ModuleProvider; -use crate::frontends::pbs::symbols::ModuleSymbols; -use crate::frontends::pbs::typecheck::TypeChecker; -use prometeu_analysis::NameInterner; - -/// Unified frontend entrypoint: typecheck a pre-collected module, ensuring all public -/// symbols are typed. This function does not perform collection — callers must provide -/// a `ModuleSymbols` that already contains all symbols for the current module (across files). -/// -/// Invariant after success: -/// - Every public function/service method has `sym.ty = Some(PbsType::Function { ... })`. -pub fn build_typed_module_symbols( - parsed: &ParsedAst, - current_module: &mut ModuleSymbols, - imported_symbols: &ModuleSymbols, - interner: &mut NameInterner, -) -> Result<(), DiagnosticBundle> { - // 0) Ensure primitive names are interned (resolver/typechecker expect them present) - for p in ["int", "bool", "float", "string", "bounded", "void"] { - interner.intern(p); - } - - // Typecheck using an empty provider (exports typing must not consult provider) - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None } - } - let provider = EmptyProvider; - - let mut checker = TypeChecker::new(current_module, imported_symbols, &provider, &interner); - checker.check(&parsed.arena, parsed.root)?; - - // Done — typed symbols populated in-place - Ok(()) -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/lexer.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/lexer.rs deleted file mode 100644 index bd577d1b..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/lexer.rs +++ /dev/null @@ -1,442 +0,0 @@ -use super::token::{Token, TokenKind}; -use crate::common::spans::{FileId, Span}; -use std::iter::Peekable; -use std::str::Chars; - -pub struct Lexer<'a> { - chars: Peekable>, - file_id: FileId, - pos: u32, -} - -impl<'a> Lexer<'a> { - pub fn new(source: &'a str, file_id: FileId) -> Self { - Self { - chars: source.chars().peekable(), - file_id, - pos: 0, - } - } - - fn peek(&mut self) -> Option { - self.chars.peek().copied() - } - - fn next(&mut self) -> Option { - let c = self.chars.next(); - if let Some(c) = c { - self.pos += c.len_utf8() as u32; - } - c - } - - fn skip_whitespace(&mut self) { - while let Some(c) = self.peek() { - if c.is_whitespace() { - self.next(); - } else if c == '/' { - if self.peek_next() == Some('/') { - // Line comment - self.next(); // / - self.next(); // / - while let Some(c) = self.peek() { - if c == '\n' { - break; - } - self.next(); - } - } else { - break; - } - } else { - break; - } - } - } - - fn peek_next(&self) -> Option { - let mut cloned = self.chars.clone(); - cloned.next(); - cloned.peek().copied() - } - - pub fn next_token(&mut self) -> Token { - self.skip_whitespace(); - - let start = self.pos; - let c = match self.next() { - Some(c) => c, - None => return Token::new(TokenKind::Eof, Span::new(self.file_id, start, start)), - }; - - let kind = match c { - '(' => TokenKind::OpenParen, - ')' => TokenKind::CloseParen, - '{' => TokenKind::OpenBrace, - '}' => TokenKind::CloseBrace, - '[' => { - if self.peek() == Some('[') { - self.next(); - TokenKind::OpenDoubleBracket - } else { - TokenKind::OpenBracket - } - } - ']' => { - if self.peek() == Some(']') { - self.next(); - TokenKind::CloseDoubleBracket - } else { - TokenKind::CloseBracket - } - } - ',' => TokenKind::Comma, - '.' => TokenKind::Dot, - ':' => TokenKind::Colon, - ';' => TokenKind::Semicolon, - '=' => { - if self.peek() == Some('=') { - self.next(); - TokenKind::Eq - } else { - TokenKind::Assign - } - } - '+' => TokenKind::Plus, - '-' => { - if self.peek() == Some('>') { - self.next(); - TokenKind::Arrow - } else { - TokenKind::Minus - } - } - '*' => TokenKind::Star, - '/' => TokenKind::Slash, - '%' => TokenKind::Percent, - '!' => { - if self.peek() == Some('=') { - self.next(); - TokenKind::Neq - } else { - TokenKind::Not - } - } - '<' => { - if self.peek() == Some('=') { - self.next(); - TokenKind::Lte - } else { - TokenKind::Lt - } - } - '>' => { - if self.peek() == Some('=') { - self.next(); - TokenKind::Gte - } else { - TokenKind::Gt - } - } - '&' => { - if self.peek() == Some('&') { - self.next(); - TokenKind::And - } else { - TokenKind::Invalid("&".to_string()) - } - } - '|' => { - if self.peek() == Some('|') { - self.next(); - TokenKind::Or - } else { - TokenKind::Invalid("|".to_string()) - } - } - '"' => self.lex_string(), - '0'..='9' => self.lex_number(c), - c if is_identifier_start(c) => self.lex_identifier(c), - _ => TokenKind::Invalid(c.to_string()), - }; - - Token::new(kind, Span::new(self.file_id, start, self.pos)) - } - - fn lex_string(&mut self) -> TokenKind { - let mut s = String::new(); - while let Some(c) = self.peek() { - if c == '"' { - self.next(); - return TokenKind::StringLit(s); - } - if c == '\n' { - break; // Unterminated string - } - s.push(self.next().unwrap()); - } - TokenKind::Invalid("Unterminated string".to_string()) - } - - fn lex_number(&mut self, first: char) -> TokenKind { - let mut s = String::new(); - s.push(first); - let mut is_float = false; - - while let Some(c) = self.peek() { - if c.is_ascii_digit() { - s.push(self.next().unwrap()); - } else if c == '.' && !is_float { - if let Some(next_c) = self.peek_next() { - if next_c.is_ascii_digit() { - is_float = true; - s.push(self.next().unwrap()); // . - s.push(self.next().unwrap()); // next digit - } else { - break; - } - } else { - break; - } - } else { - break; - } - } - - if self.peek() == Some('b') && !is_float { - self.next(); // consume 'b' - if let Ok(val) = s.parse::() { - return TokenKind::BoundedLit(val); - } - } - - if is_float { - if let Ok(val) = s.parse::() { - return TokenKind::FloatLit(val); - } - } else { - if let Ok(val) = s.parse::() { - return TokenKind::IntLit(val); - } - } - - TokenKind::Invalid(s) - } - - fn lex_identifier(&mut self, first: char) -> TokenKind { - let mut s = String::new(); - s.push(first); - while let Some(c) = self.peek() { - if is_identifier_part(c) { - s.push(self.next().unwrap()); - } else { - break; - } - } - - match s.as_str() { - "import" => TokenKind::Import, - "pub" => TokenKind::Pub, - "mod" => TokenKind::Mod, - "service" => TokenKind::Service, - "fn" => TokenKind::Fn, - "let" => TokenKind::Let, - "mut" => TokenKind::Mut, - "declare" => TokenKind::Declare, - "struct" => TokenKind::Struct, - "contract" => TokenKind::Contract, - "host" => TokenKind::Host, - "error" => TokenKind::Error, - "optional" => TokenKind::Optional, - "result" => TokenKind::Result, - "some" => TokenKind::Some, - "none" => TokenKind::None, - "ok" => TokenKind::Ok, - "err" => TokenKind::Err, - "if" => TokenKind::If, - "else" => TokenKind::Else, - "when" => TokenKind::When, - "for" => TokenKind::For, - "in" => TokenKind::In, - "return" => TokenKind::Return, - "handle" => TokenKind::Handle, - "borrow" => TokenKind::Borrow, - "mutate" => TokenKind::Mutate, - "peek" => TokenKind::Peek, - "take" => TokenKind::Take, - "alloc" => TokenKind::Alloc, - "weak" => TokenKind::Weak, - "as" => TokenKind::As, - "bounded" => TokenKind::Bounded, - _ => TokenKind::Identifier(s), - } - } -} - -fn is_identifier_start(c: char) -> bool { - c.is_alphabetic() || c == '_' -} - -fn is_identifier_part(c: char) -> bool { - c.is_alphanumeric() || c == '_' -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::frontends::pbs::token::TokenKind; - - #[test] - fn test_lex_basic_tokens() { - let source = "( ) { } [ ] , . : ; -> = == + - * / % ! != < > <= >= && ||"; - let mut lexer = Lexer::new(source, FileId(0)); - - let expected = vec![ - TokenKind::OpenParen, TokenKind::CloseParen, - TokenKind::OpenBrace, TokenKind::CloseBrace, - TokenKind::OpenBracket, TokenKind::CloseBracket, - TokenKind::Comma, TokenKind::Dot, TokenKind::Colon, TokenKind::Semicolon, - TokenKind::Arrow, TokenKind::Assign, TokenKind::Eq, - TokenKind::Plus, TokenKind::Minus, TokenKind::Star, TokenKind::Slash, TokenKind::Percent, - TokenKind::Not, TokenKind::Neq, - TokenKind::Lt, TokenKind::Gt, TokenKind::Lte, TokenKind::Gte, - TokenKind::And, TokenKind::Or, - TokenKind::Eof, - ]; - - for kind in expected { - let token = lexer.next_token(); - assert_eq!(token.kind, kind); - } - } - - #[test] - fn test_lex_keywords() { - let source = "import pub mod service fn let mut declare struct contract host error optional result some none ok err if else when for in return handle borrow mutate peek take alloc weak as"; - let mut lexer = Lexer::new(source, FileId(0)); - - let expected = vec![ - TokenKind::Import, TokenKind::Pub, TokenKind::Mod, TokenKind::Service, - TokenKind::Fn, TokenKind::Let, TokenKind::Mut, TokenKind::Declare, - TokenKind::Struct, TokenKind::Contract, TokenKind::Host, TokenKind::Error, - TokenKind::Optional, TokenKind::Result, TokenKind::Some, TokenKind::None, - TokenKind::Ok, TokenKind::Err, TokenKind::If, TokenKind::Else, - TokenKind::When, TokenKind::For, TokenKind::In, TokenKind::Return, - TokenKind::Handle, TokenKind::Borrow, TokenKind::Mutate, TokenKind::Peek, - TokenKind::Take, TokenKind::Alloc, TokenKind::Weak, TokenKind::As, - TokenKind::Eof, - ]; - - for kind in expected { - let token = lexer.next_token(); - assert_eq!(token.kind, kind); - } - } - - #[test] - fn test_lex_identifiers() { - let source = "foo bar _baz qux123"; - let mut lexer = Lexer::new(source, FileId(0)); - - let expected = vec![ - TokenKind::Identifier("foo".to_string()), - TokenKind::Identifier("bar".to_string()), - TokenKind::Identifier("_baz".to_string()), - TokenKind::Identifier("qux123".to_string()), - TokenKind::Eof, - ]; - - for kind in expected { - let token = lexer.next_token(); - assert_eq!(token.kind, kind); - } - } - - #[test] - fn test_lex_literals() { - let source = "123 3.14 255b \"hello world\""; - let mut lexer = Lexer::new(source, FileId(0)); - - let expected = vec![ - TokenKind::IntLit(123), - TokenKind::FloatLit(3.14), - TokenKind::BoundedLit(255), - TokenKind::StringLit("hello world".to_string()), - TokenKind::Eof, - ]; - - for kind in expected { - let token = lexer.next_token(); - assert_eq!(token.kind, kind); - } - } - - #[test] - fn test_lex_comments() { - let source = "let x = 10; // this is a comment\nlet y = 20;"; - let mut lexer = Lexer::new(source, FileId(0)); - - let expected = vec![ - TokenKind::Let, - TokenKind::Identifier("x".to_string()), - TokenKind::Assign, - TokenKind::IntLit(10), - TokenKind::Semicolon, - TokenKind::Let, - TokenKind::Identifier("y".to_string()), - TokenKind::Assign, - TokenKind::IntLit(20), - TokenKind::Semicolon, - TokenKind::Eof, - ]; - - for kind in expected { - let token = lexer.next_token(); - assert_eq!(token.kind, kind); - } - } - - #[test] - fn test_lex_spans() { - let source = "let x = 10;"; - let mut lexer = Lexer::new(source, FileId(0)); - - let t1 = lexer.next_token(); // let - assert_eq!(t1.span.start, 0); - assert_eq!(t1.span.end, 3); - - let t2 = lexer.next_token(); // x - assert_eq!(t2.span.start, 4); - assert_eq!(t2.span.end, 5); - - let t3 = lexer.next_token(); // = - assert_eq!(t3.span.start, 6); - assert_eq!(t3.span.end, 7); - - let t4 = lexer.next_token(); // 10 - assert_eq!(t4.span.start, 8); - assert_eq!(t4.span.end, 10); - - let t5 = lexer.next_token(); // ; - assert_eq!(t5.span.start, 10); - assert_eq!(t5.span.end, 11); - } - - #[test] - fn test_lex_invalid_tokens() { - let source = "@ #"; - let mut lexer = Lexer::new(source, FileId(0)); - - assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_))); - assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_))); - assert_eq!(lexer.next_token().kind, TokenKind::Eof); - } - - #[test] - fn test_lex_unterminated_string() { - let source = "\"hello"; - let mut lexer = Lexer::new(source, FileId(0)); - - assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_))); - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/lowering.rs deleted file mode 100644 index 10176cef..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/lowering.rs +++ /dev/null @@ -1,2876 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; -use crate::common::spans::Span; -use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::contracts::ContractRegistry; -use crate::frontends::pbs::symbols::*; -use crate::frontends::pbs::resolver::ModuleProvider; -use crate::frontends::pbs::types::PbsType; -use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId, SigId}; -use crate::ir_core::{Block, ConstPool, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type}; -use crate::ir_core::signature::{Signature, global_signature_interner}; -use prometeu_analysis::{NameInterner, NodeId}; -use std::collections::HashMap; - -#[derive(Clone)] -struct LocalInfo { - slot: u32, - ty: Type, -} - -pub struct Lowerer<'a> { - arena: &'a AstArena, - module_symbols: &'a ModuleSymbols, - imported_symbols: &'a ModuleSymbols, - module_provider: &'a dyn ModuleProvider, - interner: &'a NameInterner, - program: Program, - current_function: Option, - current_block: Option, - next_block_id: u32, - next_func_id: u32, - next_type_id: u32, - local_vars: Vec>, - function_ids: HashMap, - type_ids: HashMap, - struct_slots: HashMap, - struct_constructors: HashMap>, - type_constants: HashMap>, - current_type_context: Option, - user_struct_field_offsets: HashMap>, - user_struct_field_types: HashMap>, - method_self_slot: Option, - contract_registry: ContractRegistry, - diagnostics: Vec, - max_slots_used: u32, - current_span: Option, - import_bindings: HashMap, -} - -impl<'a> Lowerer<'a> { - // Infer a minimal PbsType for an expression node. This is intentionally - // lightweight and only covers cases needed for overload resolution. - fn infer_pbs_type(&self, node: NodeId) -> PbsType { - match self.arena.kind(node) { - NodeKind::IntLit(_) => PbsType::Int, - NodeKind::FloatLit(_) => PbsType::Float, - NodeKind::BoundedLit(_) => PbsType::Bounded, - NodeKind::StringLit(_) => PbsType::String, - NodeKind::Ident(id) => { - // Try value symbols (current or imported) - if let Some(sym) = self.module_symbols.value_symbols.get(id.name) - .or_else(|| self.imported_symbols.value_symbols.get(id.name)) - { - if let Some(ty) = &sym.ty { - return ty.clone(); - } - } - // Try type symbols for constructors/constants resolved as identifiers - if let Some(sym) = self.module_symbols.type_symbols.get(id.name) - .or_else(|| self.imported_symbols.type_symbols.get(id.name)) - { - match sym.kind { - SymbolKind::Struct => return PbsType::Struct(self.interner.resolve(id.name).to_string()), - SymbolKind::Service => return PbsType::Service(self.interner.resolve(id.name).to_string()), - SymbolKind::Contract => return PbsType::Contract(self.interner.resolve(id.name).to_string()), - SymbolKind::ErrorType => return PbsType::ErrorType(self.interner.resolve(id.name).to_string()), - _ => {} - } - } - PbsType::Void - } - // For member access and other expressions, we don't need deep inference for matching - // in v0 scope; return Void which will never match and will surface a deterministic error. - _ => PbsType::Void, - } - } - fn sig_from_pbs_fn(&self, pbs: &PbsType) -> Option { - if let PbsType::Function { params, return_type } = pbs { - let mut core_params = Vec::with_capacity(params.len()); - for p in params { - core_params.push(self.convert_pbs_type(p)); - } - let core_ret = self.convert_pbs_type(return_type); - let sig = Signature { params: core_params, return_type: core_ret }; - // Global, deterministic interner across the compiler process - let mut guard = global_signature_interner().lock().unwrap(); - Some(guard.intern(sig)) - } else { - None - } - } - // #[inline] - // fn hash_tag_u16(s: &str) -> u16 { - // // FNV-1a 16-bit (simple, deterministic, allows small collisions) - // let mut hash: u16 = 0x811C; // offset basis (truncated) - // let prime: u16 = 0x0101; // 257 - // for &b in s.as_bytes() { - // hash ^= b as u16; - // hash = hash.wrapping_mul(prime); - // } - // hash - // } - pub fn new( - arena: &'a AstArena, - module_symbols: &'a ModuleSymbols, - imported_symbols: &'a ModuleSymbols, - module_provider: &'a dyn ModuleProvider, - interner: &'a NameInterner, - ) -> Self { - let mut field_offsets = HashMap::new(); - field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation - - let mut struct_slots = HashMap::new(); - struct_slots.insert("Color".to_string(), 1); - struct_slots.insert("ButtonState".to_string(), 4); - // New service-based input returns `Button` (same layout as legacy ButtonState: 4 slots) - struct_slots.insert("Button".to_string(), 4); - struct_slots.insert("Pad".to_string(), 48); - struct_slots.insert("Touch".to_string(), 6); - - Self { - arena, - module_symbols, - imported_symbols, - module_provider, - interner, - program: Program { - const_pool: ConstPool::new(), - modules: Vec::new(), - field_offsets, - field_types: HashMap::new(), - }, - current_function: None, - current_block: None, - next_block_id: 0, - next_func_id: 1, - next_type_id: 1, - local_vars: Vec::new(), - function_ids: HashMap::new(), - type_ids: HashMap::new(), - struct_slots, - struct_constructors: HashMap::new(), - type_constants: HashMap::new(), - current_type_context: None, - user_struct_field_offsets: HashMap::new(), - user_struct_field_types: HashMap::new(), - method_self_slot: None, - contract_registry: ContractRegistry::new(), - diagnostics: Vec::new(), - max_slots_used: 0, - current_span: None, - import_bindings: HashMap::new(), - } - } - - fn error(&mut self, code: &str, message: String, span: Span) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: code.to_string(), - message, - span, - related: Vec::new(), - }); - } - - pub fn lower_file(mut self, root: NodeId, module_name: &str) -> Result { - // Ensure per-module function id space starts clean and small - self.next_func_id = 1; - self.function_ids.clear(); - let file = match self.arena.kind(root) { - NodeKind::File(file) => file, - _ => { - return Err(DiagnosticBundle::error( - "E_LOWER_INVALID_ROOT", - "Expected File node as root".to_string(), - self.arena.span(root), - )) - } - }; - - // Construir mapa de imports: para cada import do arquivo, se vier do formato - // "@alias:module", associe cada símbolo importado em `spec.path` ao par (alias,module). - self.import_bindings.clear(); - for &imp in &file.imports { - if let NodeKind::Import(imp_node) = self.arena.kind(imp) { - let from = imp_node.from.as_str(); - if let Some(rest) = from.strip_prefix('@') { - // Espera-se formato @alias:module_path - let mut parts = rest.splitn(2, ':'); - if let (Some(alias), Some(module_path)) = (parts.next(), parts.next()) { - if let NodeKind::ImportSpec(spec) = self.arena.kind(imp_node.spec) { - for name in &spec.path { - let sym = self.interner.resolve(*name).to_string(); - self.import_bindings.insert(sym, (alias.to_string(), module_path.to_string())); - } - } - } - } - } - } - // Pre-scan for function declarations to assign IDs - for decl in &file.decls { - match self.arena.kind(*decl) { - NodeKind::FnDecl(n) => { - let id = FunctionId(self.next_func_id); - self.next_func_id += 1; - self.function_ids - .insert(self.interner.resolve(n.name).to_string(), id); - } - NodeKind::ServiceDecl(n) => { - let service_name = self.interner.resolve(n.name).to_string(); - for m in &n.members { - if let NodeKind::ServiceFnDecl(decl) = self.arena.kind(*m) { - let full_name = format!("{}.{}", service_name, self.interner.resolve(decl.name)); - let id = FunctionId(self.next_func_id); - self.next_func_id += 1; - self.function_ids.insert(full_name, id); - } - } - } - NodeKind::TypeDecl(n) => { - let id = TypeId(self.next_type_id); - self.next_type_id += 1; - self.type_ids - .insert(self.interner.resolve(n.name).to_string(), id); - - // Pré-scan de métodos dentro do tipo (apenas FnDecl com corpo) - let type_name = self.interner.resolve(n.name).to_string(); - if let Some(body_id) = n.body { - if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { - for m in &tb.methods { - if let NodeKind::FnDecl(md) = self.arena.kind(*m) { - let full_name = format!("{}.{}", type_name, self.interner.resolve(md.name)); - let id = FunctionId(self.next_func_id); - self.next_func_id += 1; - self.function_ids.insert(full_name, id); - } - } - } - } - } - _ => {} - } - } - - // Second pre-scan: calculate struct slots (recursive) - let mut struct_nodes = HashMap::new(); - for decl in &file.decls { - if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { - if n.type_kind == "struct" { - struct_nodes.insert(self.interner.resolve(n.name).to_string(), *decl); - } - } - } - - let mut changed = true; - while changed { - changed = false; - for (name, node_id) in &struct_nodes { - if !self.struct_slots.contains_key(name) { - let mut slots = 0; - let mut all_known = true; - let node = match self.arena.kind(*node_id) { - NodeKind::TypeDecl(node) => node, - _ => continue, - }; - for param in &node.params { - let member_ty = self.lower_type_node(param.ty); - match &member_ty { - Type::Struct(sname) => { - if let Some(s_slots) = self.get_builtin_struct_slots(sname) { - slots += s_slots; - } else if let Some(s_slots) = self.struct_slots.get(sname) { - slots += s_slots; - } else { - all_known = false; - break; - } - } - _ => slots += self.get_type_slots(&member_ty), - } - } - if all_known { - self.struct_slots.insert(name.clone(), slots); - changed = true; - } - } - } - } - - for decl in &file.decls { - if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { - let type_name = self.interner.resolve(n.name).to_string(); - let mut constants = HashMap::new(); - for c in &n.constants { - if let NodeKind::ConstantDecl(constant) = self.arena.kind(*c) { - constants.insert( - self.interner.resolve(constant.name).to_string(), - constant.value, - ); - } - } - self.type_constants.insert(type_name.clone(), constants); - - let mut ctors = HashMap::new(); - - // Default constructor: TypeName(...) - if n.type_kind == "struct" { - if let Some(default_ctor) = n.constructors.first() { - if let NodeKind::ConstructorDecl(_) = self.arena.kind(*default_ctor) { - ctors.insert(type_name.clone(), *default_ctor); - } - } - } - - for ctor in &n.constructors { - if let NodeKind::ConstructorDecl(ctor_node) = self.arena.kind(*ctor) { - ctors.insert(self.interner.resolve(ctor_node.name).to_string(), *ctor); - } - } - self.struct_constructors.insert(type_name, ctors); - } - } - - // Calcular offsets e tipos de campos para structs de usuário - for decl in &file.decls { - if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { - if n.type_kind == "struct" { - let type_name = self.interner.resolve(n.name).to_string(); - let mut offsets = HashMap::new(); - let mut types = HashMap::new(); - let mut acc: u32 = 0; - - // Campos do cabeçalho - for p in &n.params { - let ty = self.lower_type_node(p.ty); - let slots = self.get_type_slots(&ty); - offsets.insert(self.interner.resolve(p.name).to_string(), acc); - types.insert(self.interner.resolve(p.name).to_string(), ty); - acc += slots; - } - // Campos adicionais (members) no corpo - if let Some(body_id) = n.body { - if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { - for m in &tb.members { - let ty = self.lower_type_node(m.ty); - let slots = self.get_type_slots(&ty); - offsets.insert(self.interner.resolve(m.name).to_string(), acc); - types.insert(self.interner.resolve(m.name).to_string(), ty); - acc += slots; - } - } - } - - self.user_struct_field_offsets.insert(type_name.clone(), offsets); - self.user_struct_field_types.insert(type_name, types); - } - } - } - - let mut module = Module { - name: module_name.to_string(), - functions: Vec::new(), - }; - - for decl in &file.decls { - match self.arena.kind(*decl) { - NodeKind::FnDecl(_) => { - let func = self.lower_function(*decl).map_err(|_| DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - })?; - module.functions.push(func); - } - NodeKind::ServiceDecl(n) => { - let service_name = self.interner.resolve(n.name).to_string(); - for m in &n.members { - if let NodeKind::ServiceFnDecl(_) = self.arena.kind(*m) { - let func = self.lower_service_function(&service_name, *m).map_err(|_| DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - })?; - module.functions.push(func); - } - } - } - _ => {} - } - } - - // Baixar métodos de structs - for decl in &file.decls { - if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { - let type_name = self.interner.resolve(n.name).to_string(); - if let Some(body_id) = n.body { - if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) { - for m in &tb.methods { - if let NodeKind::FnDecl(_) = self.arena.kind(*m) { - let func = self.lower_method_function(&type_name, *m).map_err(|_| DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - })?; - module.functions.push(func); - } - } - } - } - } - } - - self.program.modules.push(module); - Ok(self.program) - } - - fn lower_function(&mut self, node: NodeId) -> Result { - let n = match self.arena.kind(node) { - NodeKind::FnDecl(n) => n, - _ => return Err(()), - }; - let func_name = self.interner.resolve(n.name).to_string(); - let func_id = *self.function_ids.get(&func_name).unwrap(); - self.next_block_id = 0; - self.local_vars = vec![HashMap::new()]; - self.max_slots_used = 0; - - let mut params = Vec::new(); - let mut local_types = HashMap::new(); - let mut param_slots = 0u32; - for param in &n.params { - let ty = self.lower_type_node(param.ty); - let slots = self.get_type_slots(&ty); - params.push(Param { - name: self.interner.resolve(param.name).to_string(), - ty: ty.clone(), - }); - self.local_vars[0].insert( - self.interner.resolve(param.name).to_string(), - LocalInfo { - slot: param_slots, - ty: ty.clone(), - }, - ); - for i in 0..slots { - local_types.insert(param_slots + i, ty.clone()); - } - param_slots += slots; - } - self.max_slots_used = param_slots; - - let ret_ty = if let Some(ret) = n.ret { - self.lower_type_node(ret) - } else { - Type::Void - }; - let return_slots = self.get_type_slots(&ret_ty); - - // Build Signature and intern to SigId - let func_sig = Signature { - params: params.iter().map(|p| p.ty.clone()).collect(), - return_type: ret_ty.clone(), - }; - let sig_id = { - let mut interner = global_signature_interner().lock().unwrap(); - interner.intern(func_sig) - }; - - let func = Function { - id: func_id, - name: func_name, - sig: sig_id, - params, - return_type: ret_ty, - blocks: Vec::new(), - local_types, - param_slots: param_slots as u16, - local_slots: 0, - return_slots: return_slots as u16, - }; - - self.current_function = Some(func); - self.start_block(); - self.lower_node(n.body)?; - - // Ensure every function ends with a return if not already terminated - if let Some(mut block) = self.current_block.take() { - if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { - block.terminator = Terminator::Return; - } - if let Some(func) = &mut self.current_function { - func.blocks.push(block); - } - } - - let mut final_func = self.current_function.take().unwrap(); - final_func.local_slots = (self.max_slots_used - param_slots) as u16; - Ok(final_func) - } - - fn lower_service_function(&mut self, service_name: &str, node: NodeId) -> Result { - let n = match self.arena.kind(node) { - NodeKind::ServiceFnDecl(n) => n, - _ => return Err(()), - }; - let method_name = self.interner.resolve(n.name).to_string(); - let full_name = format!("{}.{}", service_name, method_name); - let func_id = match self.function_ids.get(&full_name) { - Some(id) => *id, - None => { - self.error( - "E_LOWER_UNSUPPORTED", - format!("Missing function id for service method '{}'", full_name), - self.arena.span(node), - ); - return Err(()); - } - }; - - self.next_block_id = 0; - self.local_vars = vec![HashMap::new()]; - self.max_slots_used = 0; - - let mut params = Vec::new(); - let mut local_types = HashMap::new(); - let mut param_slots = 0u32; - for p in &n.params { - let ty = self.lower_type_node(p.ty); - let slots = self.get_type_slots(&ty); - params.push(Param { name: self.interner.resolve(p.name).to_string(), ty: ty.clone() }); - self.local_vars[0].insert( - self.interner.resolve(p.name).to_string(), - LocalInfo { slot: param_slots, ty: ty.clone() }, - ); - for i in 0..slots { local_types.insert(param_slots + i, ty.clone()); } - param_slots += slots; - } - self.max_slots_used = param_slots; - - let ret_ty = self.lower_type_node(n.ret); - let return_slots = self.get_type_slots(&ret_ty); - // Build Signature and intern to SigId (espelha lower_function) - let func_sig = Signature { - params: params.iter().map(|p| p.ty.clone()).collect(), - return_type: ret_ty.clone(), - }; - let sig_id = { - let mut interner = global_signature_interner().lock().unwrap(); - interner.intern(func_sig) - }; - // Inicializa a função atual (espelha lower_function) - let func = Function { - id: func_id, - // Nome público da função no módulo: use apenas o nome do método. - // O module_path fará a desambiguação durante export/link. - name: method_name, - sig: sig_id, - params, - return_type: ret_ty, - blocks: Vec::new(), - local_types, - param_slots: param_slots as u16, - local_slots: 0, - return_slots: return_slots as u16, - }; - - // Registrar como função corrente para que start_block/lower_node - // acumulem instruções corretamente. - self.current_function = Some(func); - self.start_block(); - self.lower_node(n.body)?; - - // Garantir terminador e empurrar bloco final - if let Some(mut block) = self.current_block.take() { - if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { - block.terminator = Terminator::Return; - } - if let Some(func) = &mut self.current_function { - func.blocks.push(block); - } - } - - // Finalizar função: calcular local_slots e devolver - let mut final_func = self.current_function.take().unwrap(); - final_func.local_slots = (self.max_slots_used - param_slots) as u16; - Ok(final_func) - } - - fn lower_node(&mut self, node: NodeId) -> Result<(), ()> { - let old_span = self.current_span.clone(); - self.current_span = Some(self.arena.span(node)); - - let res = match self.arena.kind(node) { - NodeKind::Block(n) => self.lower_block(node, n), - NodeKind::LetStmt(n) => self.lower_let_stmt(node, n), - NodeKind::ExprStmt(n) => self.lower_node(n.expr), - NodeKind::ReturnStmt(n) => self.lower_return_stmt(node, n), - NodeKind::IntLit(n) => { - let id = self.program.const_pool.add_int(n.value); - self.emit(InstrKind::PushConst(id)); - Ok(()) - } - NodeKind::FloatLit(n) => { - let id = self.program.const_pool.add_float(n.value); - self.emit(InstrKind::PushConst(id)); - Ok(()) - } - NodeKind::StringLit(n) => { - let id = self.program.const_pool.add_string(n.value.clone()); - self.emit(InstrKind::PushConst(id)); - Ok(()) - } - NodeKind::BoundedLit(n) => { - self.emit(InstrKind::PushBounded(n.value)); - Ok(()) - } - NodeKind::Ident(n) => self.lower_ident(node, n), - NodeKind::MemberAccess(n) => self.lower_member_access(node, n), - NodeKind::Call(n) => self.lower_call(node, n), - NodeKind::Binary(n) => self.lower_binary(node, n), - NodeKind::Unary(n) => self.lower_unary(node, n), - NodeKind::IfExpr(n) => self.lower_if_expr(node, n), - NodeKind::WhenExpr(n) => self.lower_when_expr(node, n), - NodeKind::Alloc(n) => self.lower_alloc(node, n), - NodeKind::Mutate(n) => self.lower_mutate(node, n), - NodeKind::Borrow(n) => self.lower_borrow(node, n), - NodeKind::Peek(n) => self.lower_peek(node, n), - _ => { - self.error( - "E_LOWER_UNSUPPORTED", - format!("Lowering for node kind {:?} not supported", self.arena.kind(node)), - self.arena.span(node), - ); - Err(()) - } - }; - - self.current_span = old_span; - res - } - - fn lower_alloc(&mut self, _node: NodeId, n: &AllocNodeArena) -> Result<(), ()> { - let (ty_id, slots) = self.get_type_id_and_slots(n.ty)?; - self.emit(InstrKind::Alloc { ty: ty_id, slots }); - Ok(()) - } - - fn get_type_id_and_slots(&mut self, node: NodeId) -> Result<(TypeId, u32), ()> { - match self.arena.kind(node) { - NodeKind::TypeName(n) => { - let name = self.interner.resolve(n.name); - let slots = self.struct_slots.get(name).cloned().unwrap_or(1); - let id = self.get_or_create_type_id(name); - Ok((id, slots)) - } - NodeKind::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => { - let size = if ta.args.len() > 1 { - if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { - il.value as u32 - } else { - 1 - } - } else { - 1 - }; - let elem_ty = self.lower_type_node(ta.args[0]); - let name = format!("array<{}>[{}]", elem_ty, size); - let id = self.get_or_create_type_id(&name); - Ok((id, size)) - } - _ => { - self.error( - "E_RESOLVE_UNDEFINED", - format!("Unknown type in allocation: {:?}", self.arena.kind(node)), - self.arena.span(node), - ); - Err(()) - } - } - } - - fn get_or_create_type_id(&mut self, name: &str) -> TypeId { - if let Some(id) = self.type_ids.get(name) { - *id - } else { - let id = TypeId(self.next_type_id); - self.next_type_id += 1; - self.type_ids.insert(name.to_string(), id); - id - } - } - - fn lower_peek(&mut self, _node: NodeId, n: &PeekNodeArena) -> Result<(), ()> { - // 1. Evaluate target (gate) - self.lower_node(n.target)?; - - // 2. Preserve gate identity - let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); - self.emit(InstrKind::SetLocal(gate_slot)); - - // 3. Begin Operation - self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) }); - self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); - - // 4. Bind view to local - self.local_vars.push(HashMap::new()); - let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); - self.emit(InstrKind::SetLocal(view_slot)); - - // 5. Body - self.lower_node(n.body)?; - - // 6. End Operation - self.emit(InstrKind::EndPeek); - - self.local_vars.pop(); - Ok(()) - } - - fn lower_borrow(&mut self, _node: NodeId, n: &BorrowNodeArena) -> Result<(), ()> { - // 1. Evaluate target (gate) - self.lower_node(n.target)?; - - // 2. Preserve gate identity - let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); - self.emit(InstrKind::SetLocal(gate_slot)); - - // 3. Begin Operation - self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) }); - self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); - - // 4. Bind view to local - self.local_vars.push(HashMap::new()); - let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); - self.emit(InstrKind::SetLocal(view_slot)); - - // 5. Body - self.lower_node(n.body)?; - - // 6. End Operation - self.emit(InstrKind::EndBorrow); - - self.local_vars.pop(); - Ok(()) - } - - fn lower_mutate(&mut self, _node: NodeId, n: &MutateNodeArena) -> Result<(), ()> { - // 1. Evaluate target (gate) - self.lower_node(n.target)?; - - // 2. Preserve gate identity - let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); - self.emit(InstrKind::SetLocal(gate_slot)); - - // 3. Begin Operation - self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) }); - self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); - - // 4. Bind view to local - self.local_vars.push(HashMap::new()); - let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); - self.emit(InstrKind::SetLocal(view_slot)); - - // 5. Body - self.lower_node(n.body)?; - - // 6. End Operation - self.emit(InstrKind::EndMutate); - - self.local_vars.pop(); - Ok(()) - } - - fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> { - self.local_vars.push(HashMap::new()); - for stmt in &n.stmts { - // Lower the statement normally - self.lower_node(*stmt)?; - - // Guardrail: if the statement is a standalone Call that returns values, - // discard them to keep the stack balanced. This is essential for host - // contract calls that return multi-slot structs (e.g., Button=4 slots). - if let NodeKind::Call(call) = self.arena.kind(*stmt) { - // Try to compute return slots conservatively - let mut return_slots: u32 = 0; - - match self.arena.kind(call.callee) { - NodeKind::MemberAccess(ma) => { - // Static contract call: Contract.method(...) - if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - let contract_name = self.interner.resolve(obj_id.name); - let method_name = self.interner.resolve(ma.member); - if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { - let ty = self.convert_pbs_type(&method.return_type); - return_slots = self.get_type_slots(&ty); - } - } - } - NodeKind::Ident(id_node) => { - // Calling a local function or imported symbol — attempt best effort via symbol table - let callee_name = self.interner.resolve(id_node.name).to_string(); - if let Some(func_id) = self.function_ids.get(&callee_name) { - let _ = func_id; // unresolved return info in v0 → assume 0 (no discard) - } else { - // ImportCall or unknown — assume 0 (safe) - } - } - _ => {} - } - - if return_slots > 0 { - for _ in 0..return_slots { - self.emit(InstrKind::Pop); - } - } - } - } - if let Some(tail) = n.tail { - self.lower_node(tail)?; - } - self.local_vars.pop(); - Ok(()) - } - - fn lower_let_stmt(&mut self, _node: NodeId, n: &LetStmtNodeArena) -> Result<(), ()> { - self.lower_node(n.init)?; - - let ty = if let Some(ty_node) = n.ty { - self.lower_type_node(ty_node) - } else { - // Very basic inference for host calls - if let NodeKind::Call(call) = self.arena.kind(n.init) { - if let NodeKind::MemberAccess(ma) = self.arena.kind(call.callee) { - if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj.name); - let member_name = self.interner.resolve(ma.member); - // 1) Prefer exact contract registry mapping (Pad/Touch services, etc.) - if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { - self.convert_pbs_type(&method.return_type) - } else { - // 2) Legacy snapshot helpers - match (obj_name, member_name) { - ("Input", "pad") => Type::Struct("Pad".to_string()), - ("Input", "touch") => Type::Struct("Touch".to_string()), - _ => Type::Int, - } - } - } else { - Type::Int - } - } else { - Type::Int - } - } else if let NodeKind::MemberAccess(ma) = self.arena.kind(n.init) { - // Inferência para constantes de struct: Type.CONST - if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj.name); - if let Some(consts) = self.type_constants.get(obj_name) { - let member_name = self.interner.resolve(ma.member); - if consts.contains_key(member_name) { - Type::Struct(obj_name.to_string()) - } else { - Type::Int - } - } else { - Type::Int - } - } else { - Type::Int - } - } else { - Type::Int - } - }; - - let slots = self.get_type_slots(&ty); - let slot = self.add_local_to_scope(self.interner.resolve(n.name).to_string(), ty); - - for i in (0..slots).rev() { - self.emit(InstrKind::SetLocal(slot + i)); - } - Ok(()) - } - - fn lower_return_stmt(&mut self, _node: NodeId, n: &ReturnStmtNodeArena) -> Result<(), ()> { - if let Some(expr) = n.expr { - self.lower_node(expr)?; - } - self.terminate(Terminator::Return); - Ok(()) - } - - fn lower_ident(&mut self, node: NodeId, n: &IdentNodeArena) -> Result<(), ()> { - let name_str = self.interner.resolve(n.name); - if let Some(info) = self.find_local(name_str) { - let slots = self.get_type_slots(&info.ty); - for i in 0..slots { - self.emit(InstrKind::GetLocal(info.slot + i)); - } - Ok(()) - } else { - // Se estamos no corpo de um método, permitir acessar campos de `self` implicitamente - if let (Some(struct_name), Some(self_slot)) = (self.current_type_context.as_ref(), self.method_self_slot) { - let maybe_off = self - .user_struct_field_offsets - .get(struct_name) - .and_then(|m| m.get(name_str).cloned()); - if let Some(off_val) = maybe_off { - let ty = self - .user_struct_field_types - .get(struct_name) - .and_then(|m| m.get(name_str)) - .cloned() - .unwrap_or(Type::Int); - let slots = self.get_type_slots(&ty); - for i in 0..slots { - self.emit(InstrKind::GetLocal(self_slot + off_val + i)); - } - return Ok(()); - } - } - - // Check for special identifiers - match name_str { - "true" => { - let id = self.program.const_pool.add_int(1); - self.emit(InstrKind::PushConst(id)); - return Ok(()); - } - "false" => { - let id = self.program.const_pool.add_int(0); - self.emit(InstrKind::PushConst(id)); - return Ok(()); - } - "none" => { - // For now, treat none as 0. This should be refined when optional is fully implemented. - let id = self.program.const_pool.add_int(0); - self.emit(InstrKind::PushConst(id)); - return Ok(()); - } - _ => {} - } - - // Check if it's a function (for first-class functions if supported) - if let Some(_id) = self.function_ids.get(name_str) { - // Push function reference? Not in v0. - self.error( - "E_LOWER_UNSUPPORTED", - format!("First-class function reference '{}' not supported", name_str), - self.arena.span(node), - ); - Err(()) - } else { - self.error( - "E_RESOLVE_UNDEFINED", - format!("Undefined identifier '{}'", name_str), - self.arena.span(node), - ); - Err(()) - } - } - } - - fn lower_member_access(&mut self, node: NodeId, n: &MemberAccessNodeArena) -> Result<(), ()> { - if let NodeKind::Ident(id) = self.arena.kind(n.object) { - let type_name = self.interner.resolve(id.name); - let member_name = self.interner.resolve(n.member); - if let Some(constants) = self.type_constants.get(type_name).cloned() { - if let Some(const_val) = constants.get(member_name) { - let old_ctx = self.current_type_context.replace(type_name.to_string()); - let res = self.lower_node(*const_val); - self.current_type_context = old_ctx; - return res; - } - } - - if type_name == "Color" { - let val = match member_name { - "BLACK" => 0x0000, - "WHITE" => 0xFFFF, - "RED" => 0xF800, - "GREEN" => 0x07E0, - "BLUE" => 0x001F, - "MAGENTA" => 0xF81F, - "TRANSPARENT" => 0x0000, - "COLOR_KEY" => 0x0000, - _ => { - // Check if it's a method call like Color.rgb, handled in lower_call - return Ok(()); - } - }; - self.emit(InstrKind::PushBounded(val)); - return Ok(()); - } - } - - if let Some((slot, ty)) = self.resolve_member_access(node) { - let slots = self.get_type_slots(&ty); - for i in 0..slots { - self.emit(InstrKind::GetLocal(slot + i)); - } - return Ok(()); - } - - // Fallback: Handle member access where the object is a Call to a host contract method - // Example: Pad.a().down — object is a Call(MemberAccess(Pad, a)) - if let NodeKind::Call(call_node) = self.arena.kind(n.object) { - if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) { - if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) { - let contract_name = self.interner.resolve(obj_id.name); - let method_name = self.interner.resolve(inner_ma.member); - if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { - // Determine return type and slots - let ret_ty = self.convert_pbs_type(&method.return_type); - let slots = self.get_type_slots(&ret_ty); - if let Type::Struct(struct_name) = &ret_ty { - // Lower the call to push all slots - self.lower_call(n.object, &call_node)?; - // Store into a temp local to avoid leaving extra slots on the operand stack - let tmp_slot = self.add_local_to_scope( - format!("$tmp_{}", self.get_next_local_slot()), - ret_ty.clone(), - ); - for i in (0..slots).rev() { - self.emit(InstrKind::SetLocal(tmp_slot + i)); - } - // Load only the requested field - let field_name = self.interner.resolve(n.member); - let field_off = self.get_field_offset(struct_name, field_name); - self.emit(InstrKind::GetLocal(tmp_slot + field_off)); - return Ok(()); - } - } - } - } - } - - Ok(()) - } - - fn resolve_member_access(&self, node: NodeId) -> Option<(u32, Type)> { - match self.arena.kind(node) { - NodeKind::MemberAccess(n) => match self.arena.kind(n.object) { - NodeKind::Ident(id) => { - let name_str = self.interner.resolve(id.name); - let member_str = self.interner.resolve(n.member); - let info = self.find_local(name_str)?; - if let Type::Struct(sname) = &info.ty { - let offset = self.get_field_offset(sname, member_str); - let ty = self.get_field_type(sname, member_str); - Some((info.slot + offset, ty)) - } else { - None - } - } - NodeKind::MemberAccess(_) => { - let member_str = self.interner.resolve(n.member); - let (base_slot, ty) = self.resolve_member_access(n.object)?; - if let Type::Struct(sname) = &ty { - let offset = self.get_field_offset(sname, member_str); - let final_ty = self.get_field_type(sname, member_str); - Some((base_slot + offset, final_ty)) - } else { - None - } - } - _ => None, - }, - _ => None, - } - } - - fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 { - if let Some(map) = self.user_struct_field_offsets.get(struct_name) { - if let Some(off) = map.get(field_name) { - return *off; - } - } - match struct_name { - // New `Button` mirrors legacy `ButtonState` offsets - "Button" => match field_name { - "pressed" => 0, - "released" => 1, - "down" => 2, - "hold_frames" => 3, - _ => 0, - }, - "ButtonState" => match field_name { - "pressed" => 0, - "released" => 1, - "down" => 2, - "hold_frames" => 3, - _ => 0, - }, - "Pad" => match field_name { - "up" => 0, - "down" => 4, - "left" => 8, - "right" => 12, - "a" => 16, - "b" => 20, - "x" => 24, - "y" => 28, - "l" => 32, - "r" => 36, - "start" => 40, - "select" => 44, - _ => 0, - }, - "Touch" => match field_name { - "f" => 0, - "x" => 4, - "y" => 5, - _ => 0, - }, - _ => 0, - } - } - - fn get_field_type(&self, struct_name: &str, field_name: &str) -> Type { - if let Some(map) = self.user_struct_field_types.get(struct_name) { - if let Some(ty) = map.get(field_name) { - return ty.clone(); - } - } - match struct_name { - // Pad's per-button service returns a `Button` in the new API - "Pad" => Type::Struct("Button".to_string()), - // Field types for `Button` mirror legacy `ButtonState` - "Button" => match field_name { - "hold_frames" => Type::Bounded, - _ => Type::Bool, - }, - "ButtonState" => match field_name { - "hold_frames" => Type::Bounded, - _ => Type::Bool, - }, - "Touch" => match field_name { - // Touch.f() now returns a `Button` - "f" => Type::Struct("Button".to_string()), - _ => Type::Int, - }, - _ => Type::Int, - } - } - - fn lower_call(&mut self, node: NodeId, n: &CallNodeArena) -> Result<(), ()> { - match self.arena.kind(n.callee) { - NodeKind::Ident(id_node) => { - let callee_name = self.interner.resolve(id_node.name).to_string(); - // 1. Check for constructor call: TypeName(...) - let ctor = self - .struct_constructors - .get(&callee_name) - .and_then(|ctors| ctors.get(&callee_name)) - .copied(); - - if let Some(ctor) = ctor { - return self.lower_constructor_call(ctor, &n.args); - } - - if let Some(ctx) = &self.current_type_context { - let ctor = self - .struct_constructors - .get(ctx) - .and_then(|ctors| ctors.get(&callee_name)) - .copied(); - - if let Some(ctor) = ctor { - return self.lower_constructor_call(ctor, &n.args); - } - } - - for arg in &n.args { - self.lower_node(*arg)?; - } - if let Some(func_id) = self.function_ids.get(&callee_name) { - self.emit(InstrKind::Call(*func_id, n.args.len() as u32)); - Ok(()) - } else if let Some(sym) = self.imported_symbols.value_symbols.get(id_node.name) { - if let Some(origin) = &sym.origin { - if origin.starts_with('@') { - // Format: @dep_alias:module_path - let parts: Vec<&str> = origin[1..].splitn(2, ':').collect(); - if parts.len() == 2 { - let dep_alias = parts[0].to_string(); - let module_path = parts[1].to_string(); - // Compute signature id from symbol type - let base_name = self.interner.resolve(sym.name).to_string(); - if let Some(ty) = &sym.ty { - if let Some(sig) = self.sig_from_pbs_fn(ty) { - self.emit(InstrKind::ImportCall { - dep_alias, - module_path, - owner: None, - base_name, - sig, - arg_count: n.args.len() as u32, - }); - return Ok(()); - } - } - self.error( - "E_LOWER_SIGNATURE", - format!("Missing or non-function type for imported symbol '{}' to compute signature id", base_name), - self.arena.span(n.callee), - ); - return Err(()); - } - } - } - - self.error( - "E_LOWER_UNSUPPORTED", - format!( - "Calling symbol '{}' with origin {:?} is not supported yet in v0", - callee_name, - sym.origin - ), - self.arena.span(n.callee), - ); - Err(()) - } else { - // Try default constructor for struct calls like TypeName(...) - if let Some(ctors) = self.struct_constructors.get(&callee_name) { - if let Some(ctor) = ctors.get(&callee_name) { - return self.lower_constructor_call(*ctor, &n.args); - } - } - - let type_sym = self - .module_symbols - .type_symbols - .get(id_node.name) - .or_else(|| self.imported_symbols.type_symbols.get(id_node.name)); - if let Some(sym) = type_sym { - if sym.kind == SymbolKind::Struct { - // TODO: handle implicit struct constructor (lower args as field values) - return Ok(()); - } - } - - // Check for special built-in functions - match callee_name.as_str() { - "some" | "ok" | "err" => { - for arg in &n.args { - self.lower_node(*arg)?; - } - return Ok(()); - } - _ => {} - } - - self.error( - "E_RESOLVE_UNDEFINED", - format!("Undefined function '{}'", callee_name), - self.arena.span(n.callee), - ); - Err(()) - } - } - NodeKind::MemberAccess(ma) => { - // Special-case: Member access over a call expression that returns a struct from a host contract, - // e.g., Pad.a().down — we must: - // 1) lower the call (pushing all struct slots), - // 2) store it into a temporary local (all slots), - // 3) load only the requested field slot back to the stack. - if let NodeKind::Call(call_node) = self.arena.kind(ma.object) { - // Try to resolve if callee is a host contract method to infer return struct and slots - if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) { - if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) { - let contract_name = self.interner.resolve(obj_id.name); - let method_name = self.interner.resolve(inner_ma.member); - if let Some(method) = self.contract_registry.get_method(contract_name, method_name) { - // Determine slots from the declared return type BEFORE lowering the call - let ret_ty = self.convert_pbs_type(&method.return_type); - let slots = self.get_type_slots(&ret_ty); - let struct_name = match &ret_ty { Type::Struct(s) => s.clone(), _ => String::new() }; - - // Lower the call first (this will push all return slots from HostCall) - self.lower_call(n.callee, &call_node)?; - - // Allocate a temp local to capture the struct - let tmp_slot = self.add_local_to_scope( - format!("$tmp_{}", self.get_next_local_slot()), - ret_ty.clone(), - ); - // Store all slots (top of stack has the last slot). We must store in reverse order. - for i in (0..slots).rev() { - self.emit(InstrKind::SetLocal(tmp_slot + i)); - } - - // Compute field offset/type and load only that field - let field_name = self.interner.resolve(ma.member); - let field_off = self.get_field_offset(&struct_name, field_name); - let _field_ty = self.get_field_type(&struct_name, field_name); - self.emit(InstrKind::GetLocal(tmp_slot + field_off)); - return Ok(()); - } - } - } - } - // Check if it's a constructor alias: TypeName.Alias(...) - let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj_id.name); - let member_name = self.interner.resolve(ma.member); - self.struct_constructors - .get(obj_name) - .and_then(|ctors| ctors.get(member_name)) - .copied() - } else { - None - }; - - if let Some(ctor) = ctor { - return self.lower_constructor_call(ctor, &n.args); - } - - // Check for Pad.any() - let member_name = self.interner.resolve(ma.member); - if member_name == "any" { - if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj_id.name); - if let Some(info) = self.find_local(obj_name) { - if let Type::Struct(sname) = &info.ty { - if sname == "Pad" { - self.lower_pad_any(info.slot); - return Ok(()); - } - } - } - } - } - - // Host contract static calls: Contract.method(...) - if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj_id.name); - let is_local = self.find_local(obj_name).is_some(); - - if !is_local { - // Check type symbol (current or imported) for a host contract - let sym_opt = self - .module_symbols - .type_symbols - .get(obj_id.name) - .or_else(|| self.imported_symbols.type_symbols.get(obj_id.name)); - if let Some(sym) = sym_opt { - // Suporte a chamada estática de service: Service.method(...) - if sym.kind == SymbolKind::Service { - let full_name = format!("{}.{}", obj_name, member_name); - // Compute argument types first to resolve overload deterministically - let arg_types: Vec = n.args.iter().map(|a| self.infer_pbs_type(*a)).collect(); - - // Lower arguments after resolution logic decides the callee (stack order preserved) - for arg in &n.args { self.lower_node(*arg)?; } - - if let Some(func_id) = self.function_ids.get(&full_name).cloned() { - self.emit(InstrKind::Call(func_id, n.args.len() as u32)); - } else { - // Usar o binding real do import para este Service (ex.: Log -> (sdk, log)) - let obj_name_str = obj_name.to_string(); - if let Some((dep_alias, module_path)) = self.import_bindings.get(&obj_name_str).cloned() { - // Determine the canonical module origin used when we synthesized dependency symbols - // Only supported style: "@alias:module" - let canonical_origin = format!("@{}:{}", dep_alias, module_path); - - // Find candidates among imported value symbols matching: - // - name in the new canonical form: "Service.member#sigN" (prefix match on qualified base) - // - origin equals the bound synthetic module path - let mut candidates: Vec<&Symbol> = Vec::new(); - for list in self.imported_symbols.value_symbols.symbols.values() { - for s in list { - let sname = self.interner.resolve(s.name); - // Accept both canonical qualified form and legacy simple form for compatibility - let qualified_base = format!("{}.{}", obj_name, member_name); - let matches_qualified = sname.starts_with(&format!("{}#sig", &qualified_base)); - let matches_legacy = sname.starts_with(&format!("{}#sig", member_name)); - if matches_qualified || matches_legacy { - if let Some(orig) = &s.origin { - if *orig == canonical_origin { - candidates.push(s); - } - } - } - } - } - - // Deterministic exact-match selection by full signature - let exact: Vec<&Symbol> = candidates.into_iter().filter(|s| { - if let Some(PbsType::Function { params, .. }) = &s.ty { - *params == arg_types - } else { false } - }).collect(); - - let sig_opt = if exact.len() == 1 { - exact[0].ty.as_ref().and_then(|t| self.sig_from_pbs_fn(t)) - } else if exact.is_empty() { - // Not found: stable diagnostic including provided arg types - let args_desc = { - let parts: Vec = arg_types.iter().map(|t| t.to_string()).collect(); - parts.join(", ") - }; - self.error( - "E_OVERLOAD_NOT_FOUND", - format!( - "No matching overload for imported service method '{}.{}' with parameter types ({})", - obj_name, member_name, args_desc - ), - self.arena.span(n.callee), - ); - return Err(()); - } else { - // Ambiguous: multiple exact matches; sort deterministically for diagnostics - let mut infos: Vec = exact.iter().filter_map(|s| { - s.ty.as_ref().map(|t| t.to_string()) - }).collect(); - infos.sort(); - let list = infos.join(", "); - self.error( - "E_OVERLOAD_AMBIGUOUS", - format!( - "Ambiguous imported service method '{}.{}' (candidates: [{}])", - obj_name, member_name, list - ), - self.arena.span(n.callee), - ); - return Err(()); - }; - - if let Some(sig) = sig_opt { - let base_name = member_name.to_string(); - self.emit(InstrKind::ImportCall { - dep_alias, - module_path, - owner: Some(obj_name.to_string()), - base_name, - sig, - arg_count: n.args.len() as u32, - }); - } else { - // Fallback: attempt to compute from service method type if exposed in type_constants or error deterministically - self.error( - "E_LOWER_SIGNATURE", - format!( - "Unable to determine signature for imported service method '{}.{}' (missing type info)", - obj_name, member_name - ), - self.arena.span(n.callee), - ); - return Err(()); - } - } else { - // Sem binding de import conhecido: erro claro de serviço não importado - self.error( - "E_RESOLVE_UNDEFINED", - format!("Undefined service member '{}.{}' (service not imported)", obj_name, member_name), - self.arena.span(n.callee), - ); - return Err(()); - } - } - return Ok(()); - } - - if sym.kind == SymbolKind::Contract && sym.is_host { - // 1) Caminho padrão via ContractRegistry - if self.contract_registry.get_method(obj_name, member_name).is_some() { - // Extrai valores necessários sem manter o empréstimo do registry vivo - let (id, return_ty) = { - let method = self.contract_registry.get_method(obj_name, member_name).unwrap(); - (method.id, method.return_type.clone()) - }; - - // Lower arguments primeiro - for arg in &n.args { - self.lower_node(*arg)?; - } - - // Compute return slots a partir do tipo retornado - let return_slots = match &return_ty { - PbsType::Void | PbsType::None => 0, - PbsType::Struct(name) => { - // Prefer builtin struct slots, then fallback to struct_slots map, default 1 - if let Some(bi) = self.get_builtin_struct_slots(name) { bi } else { *self.struct_slots.get(name).unwrap_or(&1) } - } - other => { - let ty = self.convert_pbs_type(other); - self.get_type_slots(&ty) - } - }; - - self.emit(InstrKind::HostCall(id, return_slots)); - return Ok(()); - } - - // (Açúcar de Log movido para antes do branch host) - } - } - } - } - - // Check for .raw() - if member_name == "raw" { - self.lower_node(ma.object)?; - return Ok(()); - } - - // Check for Color.rgb - if member_name == "rgb" { - if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - if self.interner.resolve(obj_id.name) == "Color" { - if n.args.len() == 3 { - // Try to get literal values for r, g, b - let mut literals = Vec::new(); - for arg in &n.args { - if let NodeKind::IntLit(lit) = self.arena.kind(*arg) { - literals.push(Some(lit.value)); - } else if let NodeKind::BoundedLit(lit) = self.arena.kind(*arg) { - literals.push(Some(lit.value as i64)); - } else { - literals.push(None); - } - } - - if let (Some(r), Some(g), Some(b)) = - (literals[0], literals[1], literals[2]) - { - let r5 = (r & 0xFF) >> 3; - let g6 = (g & 0xFF) >> 2; - let b5 = (b & 0xFF) >> 3; - let rgb565 = (r5 << 11) | (g6 << 5) | b5; - self.emit(InstrKind::PushBounded(rgb565 as u32)); - return Ok(()); - } else { - self.error( - "E_LOWER_UNSUPPORTED", - "Color.rgb only supports literal arguments in this version" - .to_string(), - self.arena.span(node), - ); - return Err(()); - } - } - } - } - } - - // Tentativa de chamada de método de instância: obj.method(...) - // 1) Descobrir tipo do objeto e slot - let mut obj_info: Option<(u32, Type)> = None; - match self.arena.kind(ma.object) { - NodeKind::Ident(id) => { - let obj_name = self.interner.resolve(id.name); - if let Some(info) = self.find_local(obj_name) { - obj_info = Some((info.slot, info.ty.clone())); - } - } - NodeKind::MemberAccess(_) => { - if let Some(info) = self.resolve_member_access(ma.object) { - obj_info = Some(info); - } - } - _ => {} - } - - if let Some((base_slot, ty)) = obj_info.clone() { - if let Type::Struct(ref sname) = ty { - let member_name = self.interner.resolve(ma.member); - let full_name = format!("{}.{}", sname, member_name); - let func_id_opt = self.function_ids.get(&full_name).cloned(); - if let Some(func_id) = func_id_opt { - // Empilha self (todas as slots da instância) - let self_slots = self.struct_slots.get(sname).cloned().unwrap_or(1); - for i in 0..self_slots { - self.emit(InstrKind::GetLocal(base_slot + i)); - } - // Empilha argumentos - for arg in &n.args { - self.lower_node(*arg)?; - } - let arg_slots = n.args.len() as u32; - self.emit(InstrKind::Call(func_id, self_slots + arg_slots)); - return Ok(()); - } - } - } - - // Fallback original - for arg in &n.args { - self.lower_node(*arg)?; - } - - if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { - let obj_name = self.interner.resolve(obj_id.name); - let is_host_contract = self - .module_symbols - .type_symbols - .get(obj_id.name) - .map(|sym| sym.kind == SymbolKind::Contract && sym.is_host) - .unwrap_or(false); - - let is_shadowed = self.find_local(obj_name).is_some(); - - if is_host_contract && !is_shadowed { - if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { - let ir_ty = self.convert_pbs_type(&method.return_type); - let return_slots = self.get_type_slots(&ir_ty); - self.emit(InstrKind::HostCall(method.id, return_slots)); - return Ok(()); - } else { - self.error( - "E_RESOLVE_UNDEFINED", - format!("Undefined contract member '{}.{}'", obj_name, member_name), - self.arena.span(n.callee), - ); - return Err(()); - } - } - } - - self.error( - "E_LOWER_UNSUPPORTED", - "Method calls not supported in v0".to_string(), - self.arena.span(n.callee), - ); - Err(()) - } - _ => { - for arg in &n.args { - self.lower_node(*arg)?; - } - self.error( - "E_LOWER_UNSUPPORTED", - "Indirect calls not supported in v0".to_string(), - self.arena.span(n.callee), - ); - Err(()) - } - } - } - - fn lower_method_function(&mut self, type_name: &str, node: NodeId) -> Result { - let n = match self.arena.kind(node) { - NodeKind::FnDecl(n) => n, - _ => return Err(()), - }; - - let full_name = format!("{}.{}", type_name, self.interner.resolve(n.name)); - let func_id = match self.function_ids.get(&full_name) { - Some(id) => *id, - None => { - self.error( - "E_LOWER_UNSUPPORTED", - format!("Missing function id for method '{}'", full_name), - self.arena.span(node), - ); - return Err(()); - } - }; - - self.next_block_id = 0; - self.local_vars = vec![HashMap::new()]; - self.max_slots_used = 0; - - let mut params = Vec::new(); - let mut local_types = HashMap::new(); - let mut param_slots = 0u32; - // Guardar contexto anterior - let prev_ctx = self.current_type_context.clone(); - let prev_self_slot = self.method_self_slot.take(); - self.current_type_context = Some(type_name.to_string()); - - for param in &n.params { - let ty = self.lower_type_node(param.ty); - let slots = self.get_type_slots(&ty); - let param_name = self.interner.resolve(param.name).to_string(); - params.push(Param { - name: param_name.clone(), - ty: ty.clone(), - }); - // Slot inicial deste parâmetro - let this_param_start = param_slots; - self.local_vars[0].insert( - self.interner.resolve(param.name).to_string(), - LocalInfo { - slot: this_param_start, - ty: ty.clone(), - }, - ); - for i in 0..slots { - local_types.insert(this_param_start + i, ty.clone()); - } - if self.interner.resolve(param.name) == "self" { - self.method_self_slot = Some(this_param_start); - } - param_slots += slots; - } - self.max_slots_used = param_slots; - - let ret_ty = if let Some(ret) = n.ret { self.lower_type_node(ret) } else { Type::Void }; - let return_slots = self.get_type_slots(&ret_ty); - - // Build Signature and intern to SigId - let func_sig = Signature { - params: params.iter().map(|p| p.ty.clone()).collect(), - return_type: ret_ty.clone(), - }; - let sig_id = { - let mut interner = global_signature_interner().lock().unwrap(); - interner.intern(func_sig) - }; - - let func = Function { - id: func_id, - name: full_name, - sig: sig_id, - params, - return_type: ret_ty, - blocks: Vec::new(), - local_types, - param_slots: param_slots as u16, - local_slots: 0, - return_slots: return_slots as u16, - }; - - self.current_function = Some(func); - self.start_block(); - self.lower_node(n.body)?; - - if let Some(mut block) = self.current_block.take() { - if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) { - block.terminator = Terminator::Return; - } - if let Some(func) = &mut self.current_function { - func.blocks.push(block); - } - } - - let mut final_func = self.current_function.take().unwrap(); - final_func.local_slots = (self.max_slots_used - param_slots) as u16; - - // Restaurar contexto - self.current_type_context = prev_ctx; - self.method_self_slot = prev_self_slot; - - Ok(final_func) - } - - fn lower_constructor_call(&mut self, ctor: NodeId, args: &[NodeId]) -> Result<(), ()> { - let ctor_id = ctor; - let ctor = match self.arena.kind(ctor) { - NodeKind::ConstructorDecl(ctor) => ctor, - _ => return Err(()), - }; - - if args.len() != ctor.params.len() { - self.error( - "E_TYPE_MISMATCH", - format!( - "Expected {} arguments, found {}", - ctor.params.len(), - args.len() - ), - self.arena.span(ctor_id), - ); - return Err(()); - } - - self.local_vars.push(HashMap::new()); - let mut param_slots = Vec::new(); - - for param in &ctor.params { - let ty = self.lower_type_node(param.ty); - let slot = self.add_local_to_scope(self.interner.resolve(param.name).to_string(), ty.clone()); - param_slots.push((slot, ty)); - } - - for (index, arg) in args.iter().enumerate() { - if let Some((slot, ty)) = param_slots.get(index) { - self.lower_node(*arg)?; - let slots = self.get_type_slots(ty); - for i in (0..slots).rev() { - self.emit(InstrKind::SetLocal(slot + i)); - } - } - } - - for init in &ctor.initializers { - self.lower_node(*init)?; - } - - self.local_vars.pop(); - Ok(()) - } - - fn lower_pad_any(&mut self, base_slot: u32) { - for i in 0..12 { - let btn_base = base_slot + (i * 4); - self.emit(InstrKind::GetLocal(btn_base)); // pressed - self.emit(InstrKind::GetLocal(btn_base + 1)); // released - self.emit(InstrKind::Or); - self.emit(InstrKind::GetLocal(btn_base + 2)); // down - self.emit(InstrKind::Or); - if i > 0 { - self.emit(InstrKind::Or); - } - } - } - - fn lower_binary(&mut self, node: NodeId, n: &BinaryNodeArena) -> Result<(), ()> { - self.lower_node(n.left)?; - self.lower_node(n.right)?; - match n.op.as_str() { - "+" => self.emit(InstrKind::Add), - "-" => self.emit(InstrKind::Sub), - "*" => self.emit(InstrKind::Mul), - "/" => self.emit(InstrKind::Div), - "==" => self.emit(InstrKind::Eq), - "!=" => self.emit(InstrKind::Neq), - "<" => self.emit(InstrKind::Lt), - "<=" => self.emit(InstrKind::Lte), - ">" => self.emit(InstrKind::Gt), - ">=" => self.emit(InstrKind::Gte), - "&&" => self.emit(InstrKind::And), - "||" => self.emit(InstrKind::Or), - _ => { - self.error( - "E_LOWER_UNSUPPORTED", - format!("Binary operator '{}' not supported", n.op), - self.arena.span(node), - ); - return Err(()); - } - } - Ok(()) - } - - fn lower_unary(&mut self, node: NodeId, n: &UnaryNodeArena) -> Result<(), ()> { - self.lower_node(n.expr)?; - match n.op.as_str() { - "-" => self.emit(InstrKind::Neg), - "!" => self.emit(InstrKind::Not), - _ => { - self.error( - "E_LOWER_UNSUPPORTED", - format!("Unary operator '{}' not supported", n.op), - self.arena.span(node), - ); - return Err(()); - } - } - Ok(()) - } - - fn lower_if_expr(&mut self, _node: NodeId, n: &IfExprNodeArena) -> Result<(), ()> { - let then_id = self.reserve_block_id(); - let else_id = self.reserve_block_id(); - let merge_id = self.reserve_block_id(); - - self.lower_node(n.cond)?; - self.terminate(Terminator::JumpIfFalse { - target: else_id, - else_target: then_id, - }); - - // Then block - self.start_block_with_id(then_id); - self.lower_node(n.then_block)?; - self.terminate(Terminator::Jump(merge_id)); - - // Else block - self.start_block_with_id(else_id); - if let Some(else_block) = n.else_block { - self.lower_node(else_block)?; - } - self.terminate(Terminator::Jump(merge_id)); - - // Merge block - self.start_block_with_id(merge_id); - Ok(()) - } - - fn lower_when_expr(&mut self, node: NodeId, n: &WhenExprNodeArena) -> Result<(), ()> { - if n.arms.is_empty() { - return Ok(()); - } - - let merge_id = self.reserve_block_id(); - - for (idx, arm_id) in n.arms.iter().enumerate() { - let arm = match self.arena.kind(*arm_id) { - NodeKind::WhenArm(arm) => arm, - _ => { - self.error( - "E_LOWER_UNSUPPORTED", - "Expected when arm".to_string(), - self.arena.span(node), - ); - return Err(()); - } - }; - - let body_id = self.reserve_block_id(); - let next_cond_id = if idx + 1 < n.arms.len() { - self.reserve_block_id() - } else { - merge_id - }; - - self.lower_node(arm.cond)?; - self.terminate(Terminator::JumpIfFalse { - target: next_cond_id, - else_target: body_id, - }); - - self.start_block_with_id(body_id); - self.lower_node(arm.body)?; - self.terminate(Terminator::Jump(merge_id)); - - if idx + 1 < n.arms.len() { - self.start_block_with_id(next_cond_id); - } - } - - self.start_block_with_id(merge_id); - Ok(()) - } - - fn lower_type_node(&mut self, node: NodeId) -> Type { - match self.arena.kind(node) { - NodeKind::TypeName(n) => match self.interner.resolve(n.name) { - "int" => Type::Int, - "bounded" => Type::Bounded, - "float" => Type::Float, - "bool" => Type::Bool, - "string" => Type::String, - "void" => Type::Void, - "this" => { - if let Some(ctx) = &self.current_type_context { - Type::Struct(ctx.clone()) - } else { - Type::Void - } - } - _ => { - if let Some(sym) = self - .module_symbols - .type_symbols - .get(n.name) - .or_else(|| self.imported_symbols.type_symbols.get(n.name)) - { - let name = self.interner.resolve(n.name).to_string(); - match sym.kind { - SymbolKind::Struct => Type::Struct(name), - SymbolKind::Service => Type::Service(name), - SymbolKind::Contract => Type::Contract(name), - SymbolKind::ErrorType => Type::ErrorType(name), - _ => Type::Struct(name), - } - } else { - Type::Struct(self.interner.resolve(n.name).to_string()) - } - } - }, - NodeKind::TypeApp(ta) => { - let base_name = self.interner.resolve(ta.base); - if base_name == "array" { - let elem_ty = self.lower_type_node(ta.args[0]); - let size = if ta.args.len() > 1 { - if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { - il.value as u32 - } else { - 0 - } - } else { - 0 - }; - Type::Array(Box::new(elem_ty), size) - } else if base_name == "optional" { - Type::Optional(Box::new(self.lower_type_node(ta.args[0]))) - } else if base_name == "result" { - Type::Result( - Box::new(self.lower_type_node(ta.args[0])), - Box::new(self.lower_type_node(ta.args[1])) - ) - } else { - Type::Struct(format!("{}<{}>", base_name, ta.args.len())) - } - } - _ => Type::Void, - } - } - - fn start_block(&mut self) { - let id = self.reserve_block_id(); - self.start_block_with_id(id); - } - - fn start_block_with_id(&mut self, id: u32) { - if let Some(block) = self.current_block.take() { - if let Some(func) = &mut self.current_function { - func.blocks.push(block); - } - } - self.current_block = Some(Block { - id, - instrs: Vec::new(), - terminator: Terminator::Return, // Default, will be overwritten - }); - } - - fn reserve_block_id(&mut self) -> u32 { - let id = self.next_block_id; - self.next_block_id += 1; - id - } - - fn emit(&mut self, kind: InstrKind) { - if let Some(block) = &mut self.current_block { - block.instrs.push(Instr::new(kind, self.current_span.clone())); - } - } - - fn terminate(&mut self, terminator: Terminator) { - if let Some(mut block) = self.current_block.take() { - block.terminator = terminator; - if let Some(func) = &mut self.current_function { - func.blocks.push(block); - } - } - } - - fn get_next_local_slot(&self) -> u32 { - self.local_vars.iter().flat_map(|s| s.values()).map(|info| self.get_type_slots(&info.ty)).sum() - } - - fn add_local_to_scope(&mut self, name: String, ty: Type) -> u32 { - let slot = self.get_next_local_slot(); - let slots = self.get_type_slots(&ty); - - if slot + slots > self.max_slots_used { - self.max_slots_used = slot + slots; - } - - self.local_vars.last_mut().unwrap().insert(name, LocalInfo { slot, ty: ty.clone() }); - - if let Some(func) = &mut self.current_function { - for i in 0..slots { - func.local_types.insert(slot + i, ty.clone()); - } - } - slot - } - - fn find_local(&self, name: &str) -> Option { - for scope in self.local_vars.iter().rev() { - if let Some(info) = scope.get(name) { - return Some(info.clone()); - } - } - None - } - - fn get_builtin_struct_slots(&self, name: &str) -> Option { - match name { - "Pad" => Some(48), - "ButtonState" => Some(4), - "Color" => Some(1), - "Touch" => Some(6), - _ => None, - } - } - - fn get_type_slots(&self, ty: &Type) -> u32 { - match ty { - Type::Void => 0, - Type::Struct(name) => { - if let Some(slots) = self.get_builtin_struct_slots(name) { - slots - } else { - self.struct_slots.get(name).cloned().unwrap_or(1) - } - } - Type::Array(_, size) => *size, - _ => 1, - } - } - - fn convert_pbs_type(&self, ty: &PbsType) -> Type { - match ty { - PbsType::Int => Type::Int, - PbsType::Float => Type::Float, - PbsType::Bool => Type::Bool, - PbsType::String => Type::String, - PbsType::Void => Type::Void, - PbsType::None => Type::Void, - PbsType::Bounded => Type::Bounded, - PbsType::Optional(inner) => Type::Optional(Box::new(self.convert_pbs_type(inner))), - PbsType::Result(ok, err) => Type::Result( - Box::new(self.convert_pbs_type(ok)), - Box::new(self.convert_pbs_type(err)), - ), - PbsType::Struct(name) => Type::Struct(name.clone()), - PbsType::Service(name) => Type::Service(name.clone()), - PbsType::Contract(name) => Type::Contract(name.clone()), - PbsType::ErrorType(name) => Type::ErrorType(name.clone()), - PbsType::Function { params, return_type } => Type::Function { - params: params.iter().map(|p| self.convert_pbs_type(p)).collect(), - return_type: Box::new(self.convert_pbs_type(return_type)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::spans::FileId; - use crate::frontends::pbs::collector::SymbolCollector; - use crate::frontends::pbs::parser::Parser; - use crate::frontends::pbs::symbols::ModuleSymbols; - use prometeu_analysis::NameInterner; - - struct NullProvider; - impl crate::frontends::pbs::resolver::ModuleProvider for NullProvider { - fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None } - } - - fn lower_program(code: &str) -> Program { - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - lowerer.lower_file(parsed.root, "test").expect("Lowering failed") - } - - #[test] - fn test_basic_lowering() { - let code = " - fn add(a: int, b: int): int { - return a + b; - } - fn main() { - let x = add(10, 20); - } - "; - let program = lower_program(code); - - // Verify program structure - assert_eq!(program.modules.len(), 1); - let module = &program.modules[0]; - assert_eq!(module.functions.len(), 2); - - let add_func = module.functions.iter().find(|f| f.name == "add").unwrap(); - assert_eq!(add_func.params.len(), 2); - assert_eq!(add_func.return_type, Type::Int); - - // Verify blocks - assert!(add_func.blocks.len() >= 1); - let first_block = &add_func.blocks[0]; - // Check for Add instruction - assert!(first_block.instrs.iter().any(|i| matches!(i.kind, InstrKind::Add))); - } - - #[test] - fn test_binary_ops_lowering() { - let code = " - fn main() { - let a = 1 + 2; - let b = 3 - 1; - let c = 2 * 3; - let d = 8 / 2; - let e = 1 == 1; - let f = 1 < 2; - let g = 2 > 1; - let h = true && false; - let i = true || false; - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Add))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Sub))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Mul))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Div))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Eq))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Lt))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Gt))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::And))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Or))); - } - - #[test] - fn test_unary_ops_lowering() { - let code = " - fn main() { - let a = -1; - let b = !true; - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Neg))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Not))); - } - - #[test] - fn test_control_flow_lowering() { - let code = " - fn max(a: int, b: int): int { - if (a > b) { - return a; - } else { - return b; - } - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let max_func = &program.modules[0].functions[0]; - // Should have multiple blocks for if-else - assert!(max_func.blocks.len() >= 3); - } - - #[test] - fn test_if_expr_lowering() { - let code = " - fn main(a: int, b: int) { - if (a > b) { - let x = a; - } else { - let y = b; - } - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); - - assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. }))); - assert!(terminators.iter().any(|t| matches!(t, Terminator::Jump(_)))); - assert!(main_func.blocks.len() >= 3); - } - - #[test] - fn test_when_expr_lowering() { - let code = " - fn main(x: int) { - when { - x == 0 -> { return; }, - x == 1 -> { return; } - }; - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); - - assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. }))); - assert!(main_func.blocks.len() >= 5); - } - - #[test] - fn test_lower_type_node() { - let code = " - service MyService { - fn ping(): void; - } - declare contract MyContract host {} - declare error MyError {} - declare struct Point(x: int) - - fn main( - s: MyService, - c: MyContract, - e: MyError, - p: Point, - o: optional, - r: result, - a: int[3] - ) {} - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let params: Vec<_> = main_func.params.iter().map(|p| p.ty.clone()).collect(); - - assert_eq!(params[0], Type::Service("MyService".to_string())); - assert_eq!(params[1], Type::Contract("MyContract".to_string())); - assert_eq!(params[2], Type::ErrorType("MyError".to_string())); - assert_eq!(params[3], Type::Struct("Point".to_string())); - assert_eq!( - params[4], - Type::Optional(Box::new(Type::Int)) - ); - assert_eq!( - params[5], - Type::Result( - Box::new(Type::Int), - Box::new(Type::String) - ) - ); - assert_eq!( - params[6], - Type::Array(Box::new(Type::Int), 3) - ); - } - - #[test] - fn test_call_lowering() { - let code = " - fn add(a: int, b: int): int { - return a + b; - } - fn main() { - let x = add(1, 2); - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::Call(_, 2)))); - } - - #[test] - fn test_host_call_lowering() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.clear(Color.WHITE); - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::HostCall(_, _)))); - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::PushBounded(_)))); - } - - #[test] - fn test_member_access_lowering() { - let code = " - declare contract Input host {} - fn main() { - let p: Pad = Input.pad(); - let b: ButtonState = p.a; - let d: bool = p.a.down; - let c: Color = Color.WHITE; - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::GetLocal(16)))); - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::GetLocal(18)))); - assert!(instrs - .iter() - .any(|i| matches!(i.kind, InstrKind::PushBounded(_)))); - } - - #[test] - fn test_constructor_call_lowering() { - let code = " - declare struct Vec2(x: int, y: int) - [ - (x: int, y: int): (x, y) as default { } - (s: int): (s, s) as square { } - ] - fn main() { - let a = Vec2(1, 2); - let b = Vec2.square(3); - } - "; - let program = lower_program(code); - - let module = &program.modules[0]; - let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); - let instrs: Vec<_> = main_func - .blocks - .iter() - .flat_map(|b| b.instrs.iter()) - .collect(); - - let push_consts = instrs - .iter() - .filter(|i| matches!(i.kind, InstrKind::PushConst(_))) - .count(); - - assert_eq!(push_consts, 3); - assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::Call(_, _)))); - assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(_, _)))); - } - - #[test] - fn test_hip_lowering() { - let code = " - fn test_hip() { - let g = alloc int; - mutate g as x { - let y = x + 1; - } - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Alloc { .. }))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. }))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate))); - } - - #[test] - fn test_hip_lowering_golden() { - let code = " - fn test_hip() { - let g = alloc int; - mutate g as x { - let y = x + 1; - } - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .unwrap(); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let json = serde_json::to_string_pretty(&program).unwrap(); - - // Assertions for PR-20 HIP Semantics: - // 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0)) - // 2. BeginMutate uses that local (BeginMutate { gate: 1 }) - // 3. EndMutate exists - // 4. No ReadGate/WriteGate (they were removed from Instr) - - assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local"); - assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate"); - assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local"); - assert!(json.contains("\"EndMutate\""), "Should have EndMutate"); - assert!(!json.contains("ReadGate"), "ReadGate should be gone"); - assert!(!json.contains("WriteGate"), "WriteGate should be gone"); - } - - #[test] - fn test_hip_semantics_distinction() { - let code = " - fn test_hip(g: int) { - peek g as p { - let x = p; - } - borrow g as b { - let y = b; - } - mutate g as m { - let z = m; - } - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - // Assert distinct Core IR instruction sequences - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. }))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndPeek))); - - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginBorrow { .. }))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndBorrow))); - - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. }))); - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate))); - } - - #[test] - fn test_host_contract_call_lowering() { - let code = " - declare contract Gfx host {} - declare contract LogHost host {} - fn main() { - Gfx.clear(0); - LogHost.write(2, \"Hello\"); - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - // Gfx.clear -> 0x1010 - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x1010, 0)))); - // LogHost.write -> 0x5001 (registry updated to LogHost) - assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x5001, _)))); - } - - #[test] - fn test_contract_call_without_host_lowering() { - let code = " - declare contract Gfx {} - fn main() { - Gfx.clear(0); - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let result = lowerer.lower_file(parsed.root, "test"); - - assert!(result.is_err()); - let bundle = result.err().unwrap(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED")); - } - - #[test] - fn test_shadowed_contract_call_lowering() { - let code = " - declare contract Gfx host {} - fn main() { - let Gfx = 10; - Gfx.clear(0); - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let result = lowerer.lower_file(parsed.root, "test"); - - assert!(result.is_err()); - let bundle = result.err().unwrap(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED")); - } - - #[test] - fn test_invalid_contract_call_lowering() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.invalidMethod(0); - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let result = lowerer.lower_file(parsed.root, "test"); - - assert!(result.is_err()); - let bundle = result.err().unwrap(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); - } - - #[test] - fn test_alloc_struct_slots() { - let code = " - declare struct Vec3(x: int, y: int, z: int) - fn main() { - let v = alloc Vec3; - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - let alloc = instrs.iter().find_map(|i| { - if let InstrKind::Alloc { ty, slots } = &i.kind { - Some((ty, slots)) - } else { - None - } - }).expect("Should have Alloc instruction"); - - assert_eq!(*alloc.1, 3, "Vec3 should have 3 slots"); - assert!(alloc.0.0 > 0, "Should have a valid TypeId"); - } - - #[test] - fn test_alloc_array_slots() { - let code = " - fn main() { - let a = alloc array[10b]; - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - let alloc = instrs.iter().find_map(|i| { - if let InstrKind::Alloc { ty, slots } = &i.kind { - Some((ty, slots)) - } else { - None - } - }).expect("Should have Alloc instruction"); - - assert_eq!(*alloc.1, 10, "array[10b] should have 10 slots"); - assert!(alloc.0.0 > 0, "Should have a valid TypeId"); - } - - #[test] - fn test_alloc_primitive_slots() { - let code = " - fn main() { - let x = alloc int; - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); - - let func = &program.modules[0].functions[0]; - let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); - - let alloc = instrs.iter().find_map(|i| { - if let InstrKind::Alloc { ty, slots } = &i.kind { - Some((ty, slots)) - } else { - None - } - }).expect("Should have Alloc instruction"); - - assert_eq!(*alloc.1, 1, "Primitive int should have 1 slot"); - assert!(alloc.0.0 > 0, "Should have a valid TypeId"); - } - - #[test] - fn test_missing_function_error() { - let code = " - fn main() { - missing_func(); - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let result = lowerer.lower_file(parsed.root, "test"); - - assert!(result.is_err()); - let bundle = result.err().unwrap(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); - assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined function 'missing_func'"))); - } - - #[test] - fn test_unresolved_ident_error() { - let code = " - fn main() { - let x = undefined_var; - } - "; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(code, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("Failed to parse"); - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = collector - .collect(&parsed.arena, parsed.root) - .expect("Failed to collect symbols"); - let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - let imported = ModuleSymbols::new(); - let provider = NullProvider; - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner); - let result = lowerer.lower_file(parsed.root, "test"); - - assert!(result.is_err()); - let bundle = result.err().unwrap(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); - assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'"))); - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/mod.rs deleted file mode 100644 index d4e9b95e..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -pub mod token; -pub mod lexer; -pub mod ast; -pub mod parser; -pub mod types; -pub mod symbols; -pub mod collector; -pub mod resolver; -pub mod resolve; -pub mod typecheck; -pub mod lowering; -pub mod contracts; -pub mod frontend; -pub mod adapter; - -pub use collector::SymbolCollector; -pub use lexer::Lexer; -pub use lowering::Lowerer; -pub use resolver::{ModuleProvider, Resolver}; -pub use symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, SymbolTable, Visibility}; -pub use token::{Token, TokenKind}; -pub use typecheck::TypeChecker; -pub use frontend::build_typed_module_symbols; - -use crate::common::diagnostics::DiagnosticBundle; -use crate::common::files::FileManager; -use crate::frontends::Frontend; -use crate::ir_lang; -use crate::lowering::core_to_vm; -use prometeu_analysis::NameInterner; -use crate::common::spans::FileId; -use std::path::Path; - -pub struct PbsFrontend; - -impl Frontend for PbsFrontend { - fn language(&self) -> &'static str { - "pbs" - } - - fn compile_to_ir( - &self, - entry: &Path, - file_manager: &mut FileManager, - ) -> Result { - let source = std::fs::read_to_string(entry).map_err(|e| { - crate::common::diagnostics::DiagnosticBundle::error( - "E_FRONTEND_IO", - format!("Failed to read file: {}", e), - crate::common::spans::Span::new(FileId::INVALID, 0, 0), - ) - })?; - let file_id = file_manager.add(entry.to_path_buf(), source.clone()); - - let mut interner = NameInterner::new(); - let mut parser = parser::Parser::new(&source, FileId(file_id as u32), &mut interner); - let parsed = parser.parse_file()?; - - let mut collector = SymbolCollector::new(&interner); - let (type_symbols, value_symbols) = - collector.collect(&parsed.arena, parsed.root)?; - let mut module_symbols = ModuleSymbols { type_symbols, value_symbols }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - let mut resolver = Resolver::new(&module_symbols, &EmptyProvider, &interner); - // Step 3: Resolve and type expressions - // Ensure primitives are interned in the shared FileManager interner - { - let inter = file_manager.interner_mut(); - let primitives = ["int", "bool", "float", "string", "bounded", "void"]; - for p in primitives { inter.intern(p); } - } - let inter_ro = file_manager.interner(); - resolver.bootstrap_types(inter_ro); - resolver.resolve(&parsed.arena, parsed.root)?; - let imported_symbols = resolver.imported_symbols; - - let mut typechecker = TypeChecker::new(&mut module_symbols, &imported_symbols, &EmptyProvider, &interner); - typechecker.check(&parsed.arena, parsed.root)?; - - // Lower to Core IR - let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported_symbols, &EmptyProvider, &interner); - let module_name = entry.file_stem().unwrap().to_string_lossy(); - let core_program = lowerer.lower_file(parsed.root, &module_name)?; - - // Validate Core IR Invariants - crate::ir_core::validate_program(&core_program).map_err(|e| { - crate::common::diagnostics::DiagnosticBundle::error( - "E_CORE_INVARIANT", - format!("Core IR Invariant Violation: {}", e), - crate::common::spans::Span::new(FileId::INVALID, 0, 0), - ) - })?; - - // Lower Core IR to VM IR - core_to_vm::lower_program(&core_program).map_err(|e| { - crate::common::diagnostics::DiagnosticBundle::error( - "E_LOWERING", - format!("Lowering error: {}", e), - crate::common::spans::Span::new(FileId::INVALID, 0, 0), - ) - }) - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/parser.rs deleted file mode 100644 index acd42319..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/parser.rs +++ /dev/null @@ -1,1469 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; -use crate::common::spans::{FileId, Span}; -use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::lexer::Lexer; -use crate::frontends::pbs::token::{Token, TokenKind}; -use prometeu_analysis::{NameId, NameInterner, NodeId}; - -pub struct Parser<'a> { - tokens: Vec, - pos: usize, - file_id: FileId, - errors: Vec, - interner: &'a mut NameInterner, - arena: AstArena, - builtin_none: NameId, - builtin_some: NameId, - builtin_ok: NameId, - builtin_err: NameId, - builtin_true: NameId, - builtin_false: NameId, - builtin_void: NameId, - builtin_array: NameId, - builtin_optional: NameId, - builtin_result: NameId, - builtin_bounded: NameId, -} - -impl<'a> Parser<'a> { - pub fn new(source: &str, file_id: FileId, interner: &'a mut NameInterner) -> Self { - let mut lexer = Lexer::new(source, file_id); - let mut tokens = Vec::new(); - loop { - let token = lexer.next_token(); - let is_eof = token.kind == TokenKind::Eof; - tokens.push(token); - if is_eof { - break; - } - } - - let builtin_none = interner.intern("none"); - let builtin_some = interner.intern("some"); - let builtin_ok = interner.intern("ok"); - let builtin_err = interner.intern("err"); - let builtin_true = interner.intern("true"); - let builtin_false = interner.intern("false"); - let builtin_void = interner.intern("void"); - let builtin_array = interner.intern("array"); - let builtin_optional = interner.intern("optional"); - let builtin_result = interner.intern("result"); - let builtin_bounded = interner.intern("bounded"); - - Self { - tokens, - pos: 0, - file_id, - errors: Vec::new(), - interner, - arena: AstArena::default(), - builtin_none, - builtin_some, - builtin_ok, - builtin_err, - builtin_true, - builtin_false, - builtin_void, - builtin_array, - builtin_optional, - builtin_result, - builtin_bounded, - } - } - - pub fn parse_file(&mut self) -> Result { - let start_span = self.peek().span.clone(); - let mut imports = Vec::new(); - let mut decls = Vec::new(); - - while self.peek().kind != TokenKind::Eof { - if self.peek().kind == TokenKind::Import { - match self.parse_import() { - Ok(imp) => imports.push(imp), - Err(_) => self.recover_to_top_level(), - } - } else { - match self.parse_top_level_decl() { - Ok(decl) => decls.push(decl), - Err(_) => self.recover_to_top_level(), - } - } - } - - let end_span = self.peek().span.clone(); - - if !self.errors.is_empty() { - return Err(DiagnosticBundle { - diagnostics: self.errors.clone(), - }); - } - - let span = Span::new(self.file_id, start_span.start, end_span.end); - let root = self.arena.push(NodeKind::File(FileNodeArena { imports, decls }), span); - self.arena.roots.push(root); - - Ok(ParsedAst { - arena: std::mem::take(&mut self.arena), - root, - }) - } - - fn recover_to_top_level(&mut self) { - while self.peek().kind != TokenKind::Eof { - match self.peek().kind { - TokenKind::Import - | TokenKind::Fn - | TokenKind::Pub - | TokenKind::Mod - | TokenKind::Declare - | TokenKind::Service => break, - _ => self.advance(), - }; - } - } - - fn parse_import(&mut self) -> Result { - let start_span = self.consume(TokenKind::Import)?.span; - let spec = self.parse_import_spec()?; - self.consume(TokenKind::Identifier("from".to_string()))?; - - let path_tok = match self.peek().kind { - TokenKind::StringLit(ref s) => { - let s = s.clone(); - let span = self.advance().span; - (s, span) - } - _ => return Err(self.error_with_code("Expected string literal after 'from'", Some("E_PARSE_EXPECTED_TOKEN"))), - }; - - if self.peek().kind == TokenKind::Semicolon { - self.advance(); - } - - let span = Span::new(self.file_id, start_span.start, path_tok.1.end); - Ok(self.arena.push( - NodeKind::Import(ImportNodeArena { - spec, - from: path_tok.0, - }), - span, - )) - } - - fn parse_import_spec(&mut self) -> Result { - let mut path = Vec::new(); - let start_span = self.peek().span.clone(); - - if self.peek().kind == TokenKind::OpenBrace { - self.advance(); // { - loop { - let name = match self.peek().kind.clone() { - TokenKind::Identifier(name) => name, - _ => return Err(self.error("Expected identifier in import spec")), - }; - self.advance(); - path.push(self.interner.intern(&name)); - - if self.peek().kind == TokenKind::Comma { - self.advance(); - } else if self.peek().kind == TokenKind::CloseBrace { - break; - } else { - return Err(self.error("Expected ',' or '}' in import spec")); - } - } - self.consume(TokenKind::CloseBrace)?; - } else { - loop { - let name = match self.peek().kind.clone() { - TokenKind::Identifier(name) => name, - _ => return Err(self.error("Expected identifier in import spec")), - }; - self.advance(); - path.push(self.interner.intern(&name)); - - if self.peek().kind == TokenKind::Dot { - self.advance(); - } else { - break; - } - } - } - - let end_span = self.tokens[self.pos - 1].span.clone(); - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push(NodeKind::ImportSpec(ImportSpecNodeArena { path }), span)) - } - - fn parse_top_level_decl(&mut self) -> Result { - match self.peek().kind { - TokenKind::Fn => self.parse_fn_decl(None), - TokenKind::Pub | TokenKind::Mod | TokenKind::Declare | TokenKind::Service => self.parse_decl(), - TokenKind::Invalid(ref msg) => { - let code = if msg.contains("Unterminated string") { - "E_LEX_UNTERMINATED_STRING" - } else { - "E_LEX_INVALID_CHAR" - }; - let msg = msg.clone(); - Err(self.error_with_code(&msg, Some(code))) - } - _ => Err(self.error_with_code("Expected top-level declaration", Some("E_PARSE_UNEXPECTED_TOKEN"))), - } - } - - fn parse_decl(&mut self) -> Result { - let vis = if self.peek().kind == TokenKind::Pub { - self.advance(); - Some("pub".to_string()) - } else if self.peek().kind == TokenKind::Mod { - self.advance(); - Some("mod".to_string()) - } else { - None - }; - - match self.peek().kind { - TokenKind::Service => self.parse_service_decl(vis), - TokenKind::Declare => self.parse_type_decl(vis), - TokenKind::Fn => self.parse_fn_decl(vis), - _ => Err(self.error("Expected 'service', 'declare', or 'fn'")), - } - } - - fn parse_service_decl(&mut self, vis: Option) -> Result { - let start_span = self.consume(TokenKind::Service)?.span; - let name = self.expect_identifier()?; - let mut extends = None; - if self.peek().kind == TokenKind::Colon { - self.advance(); - extends = Some(self.expect_identifier()?); - } - - self.consume(TokenKind::OpenBrace)?; - let mut members = Vec::new(); - while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { - // Ignora pontos-e-vírgula extras - if self.peek().kind == TokenKind::Semicolon { - self.advance(); - continue; - } - - members.push(self.parse_service_member()?); - // Opcional ';' após assinatura - if self.peek().kind == TokenKind::Semicolon { - self.advance(); - } - } - let end_span = self.consume(TokenKind::CloseBrace)?.span; - - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push( - NodeKind::ServiceDecl(ServiceDeclNodeArena { - vis, - name, - extends, - members, - }), - span, - )) - } - - fn parse_service_member(&mut self) -> Result { - let start_span = self.consume(TokenKind::Fn)?.span; - let name = self.expect_identifier()?; - let params = self.parse_param_list()?; - let ret = if self.peek().kind == TokenKind::Colon { - self.advance(); - self.parse_type_ref()? - } else { - self.arena.push( - NodeKind::TypeName(TypeNameNodeArena { - name: self.builtin_void, - }), - Span::new(self.file_id, 0, 0), - ) - }; - - // Aceita tanto assinatura quanto corpo - if self.peek().kind == TokenKind::OpenBrace { - let body = self.parse_block()?; - let span = Span::new(self.file_id, start_span.start, self.arena.span(body).end); - Ok(self.arena.push( - NodeKind::ServiceFnDecl(ServiceFnDeclNodeArena { name, params, ret, body }), - span, - )) - } else { - let span = Span::new(self.file_id, start_span.start, self.arena.span(ret).end); - Ok(self.arena.push( - NodeKind::ServiceFnSig(ServiceFnSigNodeArena { name, params, ret }), - span, - )) - } - } - - fn parse_type_decl(&mut self, vis: Option) -> Result { - let start_span = self.consume(TokenKind::Declare)?.span; - let type_kind = match self.peek().kind { - TokenKind::Struct => { self.advance(); "struct".to_string() } - TokenKind::Contract => { self.advance(); "contract".to_string() } - TokenKind::Error => { self.advance(); "error".to_string() } - _ => return Err(self.error_with_code("Expected 'struct', 'contract', or 'error'", Some("E_PARSE_EXPECTED_TOKEN"))), - }; - let name = self.expect_identifier()?; - - // Permitir blocos opcionais em QUALQUER ordem após o nome do tipo. - // Apenas o bloco de parâmetros/dados `(...)` é obrigatório para `struct`. - let mut params: Vec = Vec::new(); - let mut constructors: Vec = Vec::new(); - let mut constants: Vec = Vec::new(); - let mut body: Option = None; - let mut is_host = false; - - let mut seen_params = false; - let mut seen_ctors = false; - let mut seen_consts = false; - let mut seen_body = false; - let mut seen_host = false; - - // Loop consumindo blocos enquanto houver tokens reconhecíveis - loop { - match self.peek().kind { - TokenKind::OpenParen if !seen_params => { - params = self.parse_param_list()?; - seen_params = true; - } - TokenKind::OpenBracket if !seen_ctors => { - constructors = self.parse_constructor_list()?; - seen_ctors = true; - } - TokenKind::OpenDoubleBracket if !seen_consts => { - // Constantes associadas: [[ NAME: expr, ... ]] - self.advance(); - while self.peek().kind != TokenKind::CloseDoubleBracket && self.peek().kind != TokenKind::Eof { - let c_start = self.peek().span.start; - let c_name = self.expect_identifier()?; - self.consume(TokenKind::Colon)?; - let c_value = self.parse_expr(0)?; - let c_span = Span::new(self.file_id, c_start, self.arena.span(c_value).end); - constants.push(self.arena.push( - NodeKind::ConstantDecl(ConstantDeclNodeArena { name: c_name, value: c_value }), - c_span, - )); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } - } - self.consume(TokenKind::CloseDoubleBracket)?; - seen_consts = true; - } - TokenKind::OpenBrace if !seen_body => { - body = Some(self.parse_type_body()?); - seen_body = true; - } - TokenKind::Host if !seen_host => { - self.advance(); - is_host = true; - seen_host = true; - } - _ => break, - } - } - - // Para 'struct', exigir que o bloco de parâmetros tenha sido fornecido - if type_kind == "struct" && !seen_params { - return Err(self.error_with_code("Expected parameter block '(...)' in struct declaration", Some("E_PARSE_EXPECTED_TOKEN"))); - } - - let mut end_pos = start_span.end; - if let Some(b) = body { - end_pos = self.arena.span(b).end; - body = Some(b); - } else if !constants.is_empty() { - // We should use the CloseDoubleBracket span here, but I don't have it easily - // Let's just use the last constant's end - end_pos = self.arena.span(*constants.last().unwrap()).end; - } else if !params.is_empty() { - end_pos = params.last().unwrap().span.end; - } - - let span = Span::new(self.file_id, start_span.start, end_pos); - Ok(self.arena.push( - NodeKind::TypeDecl(TypeDeclNodeArena { - vis, - type_kind, - name, - is_host, - params, - constructors, - constants, - body, - }), - span, - )) - } - - fn parse_type_body(&mut self) -> Result { - let start_span = self.consume(TokenKind::OpenBrace)?.span; - let mut members = Vec::new(); - let mut methods = Vec::new(); - while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { - if self.peek().kind == TokenKind::Pub { - // pub fn ... { ... } - let _ = self.advance(); // consume 'pub' - let method_node = self.parse_type_method(Some("pub".to_string()))?; - methods.push(method_node); - if self.peek().kind == TokenKind::Semicolon { - self.advance(); - } - } else if self.peek().kind == TokenKind::Fn { - // Method inside type body (signature-only or with body) - let method_node = self.parse_type_method(None)?; - methods.push(method_node); - if self.peek().kind == TokenKind::Semicolon { - self.advance(); - } - } else { - let m_start = self.peek().span.start; - let name = self.expect_identifier()?; - self.consume(TokenKind::Colon)?; - let ty = self.parse_type_ref()?; - let m_end = self.arena.span(ty).end; - members.push(TypeMemberNodeArena { - span: Span::new(self.file_id, m_start, m_end), - name, - ty, - }); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } else if self.peek().kind == TokenKind::Semicolon { - self.advance(); - } - } - } - let end_span = self.consume(TokenKind::CloseBrace)?.span; - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push( - NodeKind::TypeBody(TypeBodyNodeArena { members, methods }), - span, - )) - } - - fn parse_type_method(&mut self, vis: Option) -> Result { - let start_span = self.consume(TokenKind::Fn)?.span; - let name = self.expect_identifier()?; - let params = self.parse_param_list()?; - let ret = if self.peek().kind == TokenKind::Colon { - self.advance(); - Some(self.parse_type_ref()?) - } else { - None - }; - - if self.peek().kind == TokenKind::Semicolon { - let ret_node = if let Some(ret) = ret { - ret - } else { - self.arena.push( - NodeKind::TypeName(TypeNameNodeArena { - name: self.builtin_void, - }), - Span::new(self.file_id, 0, 0), - ) - }; - let span = Span::new(self.file_id, start_span.start, self.arena.span(ret_node).end); - return Ok(self.arena.push( - NodeKind::ServiceFnSig(ServiceFnSigNodeArena { name, params, ret: ret_node }), - span, - )); - } - - let mut else_fallback = None; - if self.peek().kind == TokenKind::Else { - self.advance(); - else_fallback = Some(self.parse_block()?); - } - - let body = self.parse_block()?; - let body_span = self.arena.span(body); - - let span = Span::new(self.file_id, start_span.start, body_span.end); - Ok(self.arena.push( - NodeKind::FnDecl(FnDeclNodeArena { - vis, - name, - params, - ret, - else_fallback, - body, - }), - span, - )) - } - - fn parse_fn_decl(&mut self, vis: Option) -> Result { - let start_span = self.consume(TokenKind::Fn)?.span; - let name = self.expect_identifier()?; - let params = self.parse_param_list()?; - let _ret = if self.peek().kind == TokenKind::Colon { - self.advance(); - Some(self.parse_type_ref()?) - } else { - None - }; - - let mut else_fallback = None; - if self.peek().kind == TokenKind::Else { - self.advance(); - else_fallback = Some(self.parse_block()?); - } - - let body = self.parse_block()?; - let body_span = self.arena.span(body); - - let span = Span::new(self.file_id, start_span.start, body_span.end); - Ok(self.arena.push( - NodeKind::FnDecl(FnDeclNodeArena { - vis, - name, - params, - ret: _ret, - else_fallback, - body, - }), - span, - )) - } - - fn parse_param_list(&mut self) -> Result, DiagnosticBundle> { - self.consume(TokenKind::OpenParen)?; - let mut params = Vec::new(); - while self.peek().kind != TokenKind::CloseParen && self.peek().kind != TokenKind::Eof { - let p_start = self.peek().span.start; - let name = self.expect_identifier()?; - self.consume(TokenKind::Colon)?; - let ty = self.parse_type_ref()?; - let p_end = self.arena.span(ty).end; - params.push(ParamNodeArena { - span: Span::new(self.file_id, p_start, p_end), - name, - ty, - }); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } else { - break; - } - } - self.consume(TokenKind::CloseParen)?; - Ok(params) - } - - fn parse_type_ref(&mut self) -> Result { - let id_tok = self.peek().clone(); - let name = match id_tok.kind { - TokenKind::Identifier(ref s) => { - self.advance(); - self.interner.intern(s) - } - TokenKind::Optional => { - self.advance(); - self.builtin_optional - } - TokenKind::Result => { - self.advance(); - self.builtin_result - } - TokenKind::Bounded => { - self.advance(); - self.builtin_bounded - } - TokenKind::None => { - self.advance(); - self.builtin_none - } - TokenKind::Some => { - self.advance(); - self.builtin_some - } - TokenKind::Ok => { - self.advance(); - self.builtin_ok - } - TokenKind::Err => { - self.advance(); - self.builtin_err - } - _ => return Err(self.error_with_code("Expected type name", Some("E_PARSE_EXPECTED_TOKEN"))), - }; - let mut node = if self.peek().kind == TokenKind::Lt { - self.advance(); // < - let mut args = Vec::new(); - loop { - args.push(self.parse_type_ref()?); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } else { - break; - } - } - let end_tok = self.consume(TokenKind::Gt)?; - self.arena.push( - NodeKind::TypeApp(TypeAppNodeArena { base: name, args }), - Span::new(self.file_id, id_tok.span.start, end_tok.span.end), - ) - } else { - self.arena.push( - NodeKind::TypeName(TypeNameNodeArena { name }), - id_tok.span, - ) - }; - - if self.peek().kind == TokenKind::OpenBracket { - self.advance(); - let size_tok = self.peek().clone(); - let size = match size_tok.kind { - TokenKind::IntLit(v) => { - self.advance(); - v as u32 - } - TokenKind::BoundedLit(v) => { - self.advance(); - v - } - _ => { - return Err(self.error_with_code( - "integer or bounded literal for array size", - Some("E_PARSE_EXPECTED_TOKEN"), - )) - } - }; - let end_tok = self.consume(TokenKind::CloseBracket)?; - let size_node = self.arena.push( - NodeKind::IntLit(IntLitNodeArena { value: size as i64 }), - size_tok.span, - ); - let span = Span::new(self.file_id, self.arena.span(node).start, end_tok.span.end); - - let mut wrapped = true; - let index = node.0 as usize; - if let NodeKind::TypeApp(ta) = &mut self.arena.nodes[index] { - if ta.base == self.builtin_array { - ta.args.push(size_node); - self.arena.spans[index] = span.clone(); - wrapped = false; - } - } - - if wrapped { - node = self.arena.push( - NodeKind::TypeApp(TypeAppNodeArena { - base: self.builtin_array, - args: vec![node, size_node], - }), - span, - ); - } - } - - Ok(node) - } - - fn parse_block(&mut self) -> Result { - let start_span = self.consume(TokenKind::OpenBrace)?.span; - let mut stmts = Vec::new(); - let mut tail = None; - - while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { - if self.peek().kind == TokenKind::Let { - stmts.push(self.parse_let_stmt()?); - } else if self.peek().kind == TokenKind::Return { - stmts.push(self.parse_return_stmt()?); - } else { - let expr = self.parse_expr(0)?; - if self.peek().kind == TokenKind::Semicolon { - let semi_span = self.advance().span; - let expr_start = self.arena.span(expr).start; - let span = Span::new(self.file_id, expr_start, semi_span.end); - stmts.push(self.arena.push( - NodeKind::ExprStmt(ExprStmtNodeArena { expr }), - span, - )); - } else if self.peek().kind == TokenKind::CloseBrace { - tail = Some(expr); - } else { - // Treat as ExprStmt even without semicolon (e.g. for if/when used as statement) - let expr_span = self.arena.span(expr); - stmts.push(self.arena.push( - NodeKind::ExprStmt(ExprStmtNodeArena { expr }), - expr_span, - )); - } - } - } - - let end_span = self.consume(TokenKind::CloseBrace)?.span; - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push(NodeKind::Block(BlockNodeArena { stmts, tail }), span)) - } - - fn parse_let_stmt(&mut self) -> Result { - let start_span = self.consume(TokenKind::Let)?.span; - let is_mut = if self.peek().kind == TokenKind::Mut { - self.advance(); - true - } else { - false - }; - let name = self.expect_identifier()?; - let ty = if self.peek().kind == TokenKind::Colon { - self.advance(); - Some(self.parse_type_ref()?) - } else { - None - }; - self.consume(TokenKind::Assign)?; - let init = self.parse_expr(0)?; - let end_span = self.consume(TokenKind::Semicolon)?.span; - - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push( - NodeKind::LetStmt(LetStmtNodeArena { - name, - is_mut, - ty, - init, - }), - span, - )) - } - - fn parse_return_stmt(&mut self) -> Result { - let start_span = self.consume(TokenKind::Return)?.span; - let mut expr = None; - if self.peek().kind != TokenKind::Semicolon { - expr = Some(self.parse_expr(0)?); - } - let end_span = self.consume(TokenKind::Semicolon)?.span; - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push(NodeKind::ReturnStmt(ReturnStmtNodeArena { expr }), span)) - } - - fn parse_alloc(&mut self) -> Result { - let start_span = self.consume(TokenKind::Alloc)?.span; - let ty = self.parse_type_ref()?; - let span = Span::new(self.file_id, start_span.start, self.arena.span(ty).end); - Ok(self.arena.push(NodeKind::Alloc(AllocNodeArena { ty }), span)) - } - - fn parse_mutate_borrow_peek(&mut self, kind: TokenKind) -> Result { - let start_span = self.consume(kind.clone())?.span; - let target_expr = self.parse_expr(0)?; - - let (target, binding) = if let NodeKind::Cast(cast) = self.arena.kind(target_expr) { - let binding = match self.arena.kind(cast.ty) { - NodeKind::Ident(id) => Some(id.name), - NodeKind::TypeName(tn) => Some(tn.name), - _ => None, - }; - if let Some(binding) = binding { - (cast.expr, binding) - } else { - return Err(self.error_with_code( - "Expected binding name after 'as'", - Some("E_PARSE_EXPECTED_TOKEN"), - )); - } - } else { - self.consume(TokenKind::As)?; - let binding = self.expect_identifier()?; - (target_expr, binding) - }; - - let body = self.parse_block()?; - let span = Span::new(self.file_id, start_span.start, self.arena.span(body).end); - - match kind { - TokenKind::Mutate => Ok(self.arena.push( - NodeKind::Mutate(MutateNodeArena { target, binding, body }), - span, - )), - TokenKind::Borrow => Ok(self.arena.push( - NodeKind::Borrow(BorrowNodeArena { target, binding, body }), - span, - )), - TokenKind::Peek => Ok(self.arena.push( - NodeKind::Peek(PeekNodeArena { target, binding, body }), - span, - )), - _ => unreachable!(), - } - } - - fn parse_expr(&mut self, min_precedence: u8) -> Result { - let mut left = self.parse_primary()?; - - loop { - let (op, precedence) = match self.get_binary_precedence() { - Some((op, p)) if p >= min_precedence => (op, p), - _ => break, - }; - - self.advance(); - let right = self.parse_expr(precedence + 1)?; - let span = Span::new( - self.file_id, - self.arena.span(left).start, - self.arena.span(right).end, - ); - left = self.arena.push( - NodeKind::Binary(BinaryNodeArena { op, left, right }), - span, - ); - } - - Ok(left) - } - - fn parse_primary(&mut self) -> Result { - let tok = self.peek().clone(); - match tok.kind { - TokenKind::IntLit(v) => { - self.advance(); - Ok(self.arena.push( - NodeKind::IntLit(IntLitNodeArena { value: v }), - tok.span, - )) - } - TokenKind::FloatLit(v) => { - self.advance(); - Ok(self.arena.push( - NodeKind::FloatLit(FloatLitNodeArena { value: v }), - tok.span, - )) - } - TokenKind::BoundedLit(v) => { - self.advance(); - Ok(self.arena.push( - NodeKind::BoundedLit(BoundedLitNodeArena { value: v }), - tok.span, - )) - } - TokenKind::StringLit(s) => { - self.advance(); - Ok(self.arena.push( - NodeKind::StringLit(StringLitNodeArena { value: s }), - tok.span, - )) - } - TokenKind::Identifier(name) => { - self.advance(); - let name = self.interner.intern(&name); - let mut node = self.arena.push( - NodeKind::Ident(IdentNodeArena { name }), - tok.span, - ); - loop { - if self.peek().kind == TokenKind::OpenParen { - node = self.parse_call(node)?; - } else if self.peek().kind == TokenKind::As { - node = self.parse_cast(node)?; - } else if self.peek().kind == TokenKind::Dot { - node = self.parse_member_access(node)?; - } else { - break; - } - } - Ok(node) - } - TokenKind::None | TokenKind::Some | TokenKind::Ok | TokenKind::Err => { - let name = match tok.kind { - TokenKind::None => self.builtin_none, - TokenKind::Some => self.builtin_some, - TokenKind::Ok => self.builtin_ok, - TokenKind::Err => self.builtin_err, - _ => unreachable!(), - }; - self.advance(); - let mut node = self.arena.push( - NodeKind::Ident(IdentNodeArena { name }), - tok.span, - ); - loop { - if self.peek().kind == TokenKind::OpenParen { - node = self.parse_call(node)?; - } else if self.peek().kind == TokenKind::As { - node = self.parse_cast(node)?; - } else if self.peek().kind == TokenKind::Dot { - node = self.parse_member_access(node)?; - } else { - break; - } - } - Ok(node) - } - TokenKind::OpenParen => { - self.advance(); - let expr = self.parse_expr(0)?; - self.consume(TokenKind::CloseParen)?; - Ok(expr) - } - TokenKind::OpenBrace => self.parse_block(), - TokenKind::If => self.parse_if_expr(), - TokenKind::When => self.parse_when_expr(), - TokenKind::Alloc => self.parse_alloc(), - TokenKind::Mutate => self.parse_mutate_borrow_peek(TokenKind::Mutate), - TokenKind::Borrow => self.parse_mutate_borrow_peek(TokenKind::Borrow), - TokenKind::Peek => self.parse_mutate_borrow_peek(TokenKind::Peek), - TokenKind::Minus | TokenKind::Not => { - self.advance(); - let op = match tok.kind { - TokenKind::Minus => "-".to_string(), - TokenKind::Not => "!".to_string(), - _ => unreachable!(), - }; - let expr = self.parse_expr(11)?; - let span = Span::new(self.file_id, tok.span.start, self.arena.span(expr).end); - Ok(self.arena.push( - NodeKind::Unary(UnaryNodeArena { op, expr }), - span, - )) - } - TokenKind::Invalid(msg) => { - let code = if msg.contains("Unterminated string") { - "E_LEX_UNTERMINATED_STRING" - } else { - "E_LEX_INVALID_CHAR" - }; - Err(self.error_with_code(&msg, Some(code))) - } - _ => Err(self.error_with_code("Expected expression", Some("E_PARSE_UNEXPECTED_TOKEN"))), - } - } - - fn parse_member_access(&mut self, object: NodeId) -> Result { - self.consume(TokenKind::Dot)?; - let member = self.expect_identifier()?; - let span = Span::new( - self.file_id, - self.arena.span(object).start, - self.tokens[self.pos - 1].span.end, - ); - Ok(self.arena.push( - NodeKind::MemberAccess(MemberAccessNodeArena { object, member }), - span, - )) - } - - fn parse_call(&mut self, callee: NodeId) -> Result { - self.consume(TokenKind::OpenParen)?; - let mut args = Vec::new(); - while self.peek().kind != TokenKind::CloseParen && self.peek().kind != TokenKind::Eof { - args.push(self.parse_expr(0)?); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } else { - break; - } - } - let end_span = self.consume(TokenKind::CloseParen)?.span; - let span = Span::new(self.file_id, self.arena.span(callee).start, end_span.end); - Ok(self.arena.push( - NodeKind::Call(CallNodeArena { callee, args }), - span, - )) - } - - fn parse_cast(&mut self, expr: NodeId) -> Result { - self.consume(TokenKind::As)?; - let ty = self.parse_type_ref()?; - let span = Span::new(self.file_id, self.arena.span(expr).start, self.arena.span(ty).end); - Ok(self.arena.push(NodeKind::Cast(CastNodeArena { expr, ty }), span)) - } - - fn parse_if_expr(&mut self) -> Result { - let start_span = self.consume(TokenKind::If)?.span; - let cond = self.parse_expr(0)?; - let then_block = self.parse_block()?; - let mut else_block = None; - if self.peek().kind == TokenKind::Else { - self.advance(); - if self.peek().kind == TokenKind::If { - else_block = Some(self.parse_if_expr()?); - } else { - else_block = Some(self.parse_block()?); - } - } - - let end_span = else_block - .map(|b| self.arena.span(b).end) - .unwrap_or(self.arena.span(then_block).end); - - let span = Span::new(self.file_id, start_span.start, end_span); - Ok(self.arena.push( - NodeKind::IfExpr(IfExprNodeArena { - cond, - then_block, - else_block, - }), - span, - )) - } - - fn parse_when_expr(&mut self) -> Result { - let start_span = self.consume(TokenKind::When)?.span; - self.consume(TokenKind::OpenBrace)?; - let mut arms = Vec::new(); - while self.peek().kind != TokenKind::CloseBrace && self.peek().kind != TokenKind::Eof { - let arm_start = self.peek().span.start; - let cond = self.parse_expr(0)?; - self.consume(TokenKind::Arrow)?; - let body = self.parse_block()?; - let arm_span = Span::new(self.file_id, arm_start, self.arena.span(body).end); - arms.push(self.arena.push( - NodeKind::WhenArm(WhenArmNodeArena { cond, body }), - arm_span, - )); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } - } - let end_span = self.consume(TokenKind::CloseBrace)?.span; - let span = Span::new(self.file_id, start_span.start, end_span.end); - Ok(self.arena.push(NodeKind::WhenExpr(WhenExprNodeArena { arms }), span)) - } - - fn get_binary_precedence(&self) -> Option<(String, u8)> { - match self.peek().kind { - TokenKind::Plus => Some(("+".to_string(), 5)), - TokenKind::Minus => Some(("-".to_string(), 5)), - TokenKind::Star => Some(("*".to_string(), 4)), - TokenKind::Slash => Some(("/".to_string(), 4)), - TokenKind::Percent => Some(("%".to_string(), 4)), - TokenKind::Lt => Some(("<".to_string(), 7)), - TokenKind::Lte => Some(("<=".to_string(), 7)), - TokenKind::Gt => Some((">".to_string(), 7)), - TokenKind::Gte => Some((">=".to_string(), 7)), - TokenKind::Eq => Some(("==".to_string(), 8)), - TokenKind::Neq => Some(("!=".to_string(), 8)), - TokenKind::And => Some(("&&".to_string(), 9)), - TokenKind::Or => Some(("||".to_string(), 10)), - _ => None, - } - } - - fn peek(&self) -> &Token { - &self.tokens[self.pos] - } - - fn advance(&mut self) -> Token { - let tok = self.tokens[self.pos].clone(); - if tok.kind != TokenKind::Eof { - self.pos += 1; - } - tok - } - - fn consume(&mut self, kind: TokenKind) -> Result { - let peeked_kind = self.peek().kind.clone(); - if peeked_kind == kind { - Ok(self.advance()) - } else { - if let TokenKind::Invalid(ref msg) = peeked_kind { - let code = if msg.contains("Unterminated string") { - "E_LEX_UNTERMINATED_STRING" - } else { - "E_LEX_INVALID_CHAR" - }; - let msg = msg.clone(); - Err(self.error_with_code(&msg, Some(code))) - } else { - Err(self.error_with_code(&format!("Expected {:?}, found {:?}", kind, peeked_kind), Some("E_PARSE_EXPECTED_TOKEN"))) - } - } - } - - fn expect_identifier(&mut self) -> Result { - let peeked_kind = self.peek().kind.clone(); - match peeked_kind { - TokenKind::Identifier(name) => { - self.advance(); - Ok(self.interner.intern(&name)) - } - TokenKind::Optional => { - self.advance(); - Ok(self.builtin_optional) - } - TokenKind::Result => { - self.advance(); - Ok(self.builtin_result) - } - TokenKind::None => { - self.advance(); - Ok(self.builtin_none) - } - TokenKind::Some => { - self.advance(); - Ok(self.builtin_some) - } - TokenKind::Ok => { - self.advance(); - Ok(self.builtin_ok) - } - TokenKind::Err => { - self.advance(); - Ok(self.builtin_err) - } - TokenKind::Bounded => { - self.advance(); - Ok(self.builtin_bounded) - } - TokenKind::Invalid(msg) => { - let code = if msg.contains("Unterminated string") { - "E_LEX_UNTERMINATED_STRING" - } else { - "E_LEX_INVALID_CHAR" - }; - Err(self.error_with_code(&msg, Some(code))) - } - _ => Err(self.error_with_code(&format!("Expected identifier, found {:?}", peeked_kind), Some("E_PARSE_EXPECTED_TOKEN"))), - } - } - - fn error(&mut self, message: &str) -> DiagnosticBundle { - self.error_with_code(message, None) - } - - fn error_with_code(&mut self, message: &str, code: Option<&str>) -> DiagnosticBundle { - let diag = Diagnostic { - severity: Severity::Error, - code: code.unwrap_or("E_PARSE_ERROR").to_string(), - message: message.to_string(), - span: self.peek().span.clone(), - related: Vec::new(), - }; - self.errors.push(diag.clone()); - DiagnosticBundle::from(diag) - } - - fn parse_constructor_list(&mut self) -> Result, DiagnosticBundle> { - self.consume(TokenKind::OpenBracket)?; - let mut constructors = Vec::new(); - while self.peek().kind != TokenKind::CloseBracket && self.peek().kind != TokenKind::Eof { - let start_span = self.peek().span.clone(); - let params = self.parse_param_list()?; - self.consume(TokenKind::Colon)?; - - let mut initializers = Vec::new(); - if self.peek().kind == TokenKind::OpenParen { - self.advance(); - while self.peek().kind != TokenKind::CloseParen && self.peek().kind != TokenKind::Eof { - initializers.push(self.parse_expr(0)?); - if self.peek().kind == TokenKind::Comma { - self.advance(); - } - } - self.consume(TokenKind::CloseParen)?; - } else { - initializers.push(self.parse_expr(0)?); - } - - self.consume(TokenKind::As)?; - let name = self.expect_identifier()?; - // Corpo opcional do construtor - let body = if self.peek().kind == TokenKind::OpenBrace { - self.parse_block()? - } else { - // bloco vazio - let empty_span = Span::new(self.file_id, start_span.end, start_span.end); - self.arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), empty_span) - }; - - let span = Span::new(self.file_id, start_span.start, self.arena.span(body).end); - constructors.push(self.arena.push( - NodeKind::ConstructorDecl(ConstructorDeclNodeArena { - params, - initializers, - name, - body, - }), - span, - )); - } - self.consume(TokenKind::CloseBracket)?; - Ok(constructors) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json; - use crate::common::spans::FileId; - - #[test] - fn test_parse_empty_file() { - let mut interner = NameInterner::new(); - let mut parser = Parser::new("", FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.imports.len(), 0); - assert_eq!(file.decls.len(), 0); - } - - #[test] - fn test_parse_imports() { - let source = r#" -import std.io from "std"; -import math from "./math.pbs"; -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.imports.len(), 2); - - let imp = match parsed.arena.kind(file.imports[0]) { - NodeKind::Import(imp) => imp, - _ => panic!("Expected Import"), - }; - assert_eq!(imp.from, "std"); - let spec = match parsed.arena.kind(imp.spec) { - NodeKind::ImportSpec(spec) => spec, - _ => panic!("Expected ImportSpec"), - }; - let path: Vec<&str> = spec.path.iter().map(|id| interner.resolve(*id)).collect(); - assert_eq!(path, vec!["std", "io"]); - } - - #[test] - fn test_parse_fn_decl() { - let source = r#" -fn add(a: int, b: int): int { - return a + b; -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - - if let NodeKind::FnDecl(f) = parsed.arena.kind(file.decls[0]) { - assert_eq!(interner.resolve(f.name), "add"); - assert_eq!(f.params.len(), 2); - assert_eq!(interner.resolve(f.params[0].name), "a"); - assert_eq!(interner.resolve(f.params[1].name), "b"); - } else { - panic!("Expected FnDecl"); - } - } - - #[test] - fn test_parse_type_decl() { - let source = r#" -pub declare struct Point(x: int, y: int){} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - - if let NodeKind::TypeDecl(t) = parsed.arena.kind(file.decls[0]) { - assert_eq!(interner.resolve(t.name), "Point"); - assert_eq!(t.type_kind, "struct"); - assert_eq!(t.vis, Some("pub".to_string())); - } else { - panic!("Expected TypeDecl"); - } - } - - #[test] - fn test_parse_type_body_method_with_body() { - let source = r#" -declare struct Vec2(x: int, y: int) { - fn getX(self: this): int { - return x; - } -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - - let type_decl = match parsed.arena.kind(file.decls[0]) { - NodeKind::TypeDecl(t) => t, - _ => panic!("Expected TypeDecl"), - }; - - let body_id = type_decl.body.expect("Expected type body"); - let body = match parsed.arena.kind(body_id) { - NodeKind::TypeBody(tb) => tb, - _ => panic!("Expected TypeBody"), - }; - assert_eq!(body.methods.len(), 1); - assert!(matches!(parsed.arena.kind(body.methods[0]), NodeKind::FnDecl(_))); - } - - #[test] - fn test_parse_service_decl() { - let source = r#" -pub service Audio { - fn play(sound: Sound); - fn stop(): bool; -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - - if let NodeKind::ServiceDecl(s) = parsed.arena.kind(file.decls[0]) { - assert_eq!(interner.resolve(s.name), "Audio"); - assert_eq!(s.members.len(), 2); - } else { - panic!("Expected ServiceDecl"); - } - } - - #[test] - fn test_parse_expressions() { - let source = r#" -fn main() { - let x = 10 + 20 * 30; - let y = (x - 5) / 2; - foo(x, y); -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - } - - #[test] - fn test_parse_if_when() { - let source = r#" -fn main(x: int) { - if x > 0 { - print("positive"); - } else { - print("non-positive"); - } - - let msg = when { - x == 0 -> { return "zero"; }, - x == 1 -> { return "one"; } - }; -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - assert_eq!(file.decls.len(), 1); - } - - #[test] - fn test_parse_error_recovery() { - let source = r#" -fn bad() { - let x = ; // Missing init - let y = 10; -} - -fn good() {} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let result = parser.parse_file(); - assert!(result.is_err()); - } - - #[test] - fn test_parse_mod_fn() { - let source = "mod fn test() {}"; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("mod fn should be allowed"); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - if let NodeKind::FnDecl(fn_decl) = parsed.arena.kind(file.decls[0]) { - assert_eq!(fn_decl.vis, Some("mod".to_string())); - } else { - panic!("Expected FnDecl"); - } - } - - #[test] - fn test_parse_pub_fn() { - let source = "pub fn test() {}"; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().expect("pub fn should be allowed in parser"); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - if let NodeKind::FnDecl(fn_decl) = parsed.arena.kind(file.decls[0]) { - assert_eq!(fn_decl.vis, Some("pub".to_string())); - } else { - panic!("Expected FnDecl"); - } - } - - #[test] - fn test_ast_json_snapshot() { - let source = r#" -fn main() { - return 42; -} -"#; - let mut interner = NameInterner::new(); - let mut parser = Parser::new(source, FileId(0), &mut interner); - let parsed = parser.parse_file().unwrap(); - let file = match parsed.arena.kind(parsed.root) { - NodeKind::File(file) => file, - _ => panic!("Expected File"), - }; - let json = serde_json::to_string_pretty(parsed.arena.kind(parsed.root)).unwrap(); - - assert!(json.contains("\"kind\": \"File\"")); - if let NodeKind::FnDecl(fn_decl) = parsed.arena.kind(file.decls[0]) { - assert_eq!(interner.resolve(fn_decl.name), "main"); - } else { - panic!("Expected FnDecl"); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/resolve.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/resolve.rs deleted file mode 100644 index 43e3e3f8..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/resolve.rs +++ /dev/null @@ -1,568 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, Severity}; -use crate::frontends::pbs::ast::{AstArena, NodeKind}; -use crate::analysis::symbols::{Symbol, SymbolArena, SymbolKind, DefIndex, DefKey, Namespace, NodeToSymbol, RefIndex}; -use prometeu_analysis::{NameInterner, NodeId, ModuleId}; - -pub fn build_def_index( - arena: &AstArena, - module: ModuleId, - _interner: &NameInterner, - imports: Option<(&SymbolArena, &DefIndex)>, -) -> (SymbolArena, DefIndex, RefIndex, NodeToSymbol, Vec) { - let mut symbols = SymbolArena::new(); - let mut index = DefIndex::new(); - let mut ref_index = RefIndex::new(); - let mut node_to_symbol = NodeToSymbol::new(); - let mut diagnostics = Vec::new(); - - // Passo 1: Coletar definições top-level - for &root_id in &arena.roots { - let root_kind = arena.kind(root_id); - if let NodeKind::File(file_node) = root_kind { - for &decl_id in &file_node.decls { - let decl_kind = arena.kind(decl_id); - let span = arena.span(decl_id); - - let result = match decl_kind { - NodeKind::FnDecl(fn_decl) => { - Some((fn_decl.name, SymbolKind::Function, Namespace::Value, fn_decl.vis.as_deref() == Some("pub"))) - } - NodeKind::TypeDecl(type_decl) => { - let kind = match type_decl.type_kind.as_str() { - "struct" => SymbolKind::Struct, - "contract" => SymbolKind::Contract, - "error" => SymbolKind::ErrorType, - _ => SymbolKind::Type, - }; - Some((type_decl.name, kind, Namespace::Type, type_decl.vis.as_deref() == Some("pub"))) - } - NodeKind::ServiceDecl(service_decl) => { - Some((service_decl.name, SymbolKind::Service, Namespace::Service, service_decl.vis.as_deref() == Some("pub"))) - } - _ => None, - }; - - if let Some((name, kind, namespace, exported)) = result { - let symbol = Symbol { - name, - kind, - exported, - module, - decl_span: span.clone(), - }; - - let symbol_id = symbols.insert(symbol); - node_to_symbol.bind_node(decl_id, symbol_id); - let key = DefKey { - module, - name, - namespace, - }; - - if let Err(mut diag) = index.insert_symbol(key, symbol_id) { - diag.span = span; - diagnostics.push(diag); - } - } - } - } - } - - // Passo 2: Resolver referências (Identifiers) - for &root_id in &arena.roots { - walk_node(root_id, arena, module, &index, imports, &mut ref_index, &mut node_to_symbol, &mut diagnostics); - } - - (symbols, index, ref_index, node_to_symbol, diagnostics) -} - -fn walk_node( - node_id: NodeId, - arena: &AstArena, - module: ModuleId, - index: &DefIndex, - imports: Option<(&SymbolArena, &DefIndex)>, - ref_index: &mut RefIndex, - node_to_symbol: &mut NodeToSymbol, - diagnostics: &mut Vec, -) { - let kind = arena.kind(node_id); - let span = arena.span(node_id); - - match kind { - NodeKind::File(file) => { - for &decl in &file.decls { - walk_node(decl, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::FnDecl(fn_decl) => { - walk_node(fn_decl.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::ServiceFnDecl(_decl) => { - // Não resolve corpo/locais nesta fase (handled em resolver.rs) - } - NodeKind::Block(block) => { - for &stmt in &block.stmts { - walk_node(stmt, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - if let Some(tail) = block.tail { - walk_node(tail, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::LetStmt(let_stmt) => { - walk_node(let_stmt.init, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::ExprStmt(expr_stmt) => { - walk_node(expr_stmt.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::ReturnStmt(ret) => { - if let Some(expr) = ret.expr { - walk_node(expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::Ident(ident) => { - let key = DefKey { - module, - name: ident.name, - namespace: Namespace::Value, - }; - - if let Some(symbol_id) = index.get(key) { - ref_index.record_ref(symbol_id, span.clone()); - node_to_symbol.bind_node(node_id, symbol_id); - } else if let Some((import_arena, import_index)) = imports { - if let Some((_, symbol_id)) = import_index.get_by_name_any_module(ident.name, Namespace::Value) { - let symbol = import_arena.get(symbol_id); - if !symbol.exported && symbol.module != module { - diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_VISIBILITY".to_string(), - message: format!("Symbol is not exported from module {:?}", symbol.module), - span: span.clone(), - related: Vec::new(), - }); - } - ref_index.record_ref(symbol_id, span.clone()); - node_to_symbol.bind_node(node_id, symbol_id); - } else { - diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_UNDEFINED_IDENTIFIER".to_string(), - message: "Undefined identifier".to_string(), - span: span.clone(), - related: Vec::new(), - }); - } - } else { - diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_UNDEFINED_IDENTIFIER".to_string(), - message: "Undefined identifier".to_string(), - span: span.clone(), - related: Vec::new(), - }); - } - } - NodeKind::Call(call) => { - walk_node(call.callee, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - for &arg in &call.args { - walk_node(arg, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::Unary(unary) => { - walk_node(unary.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Binary(binary) => { - walk_node(binary.left, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(binary.right, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Cast(cast) => { - walk_node(cast.expr, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::IfExpr(if_expr) => { - walk_node(if_expr.cond, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(if_expr.then_block, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - if let Some(else_block) = if_expr.else_block { - walk_node(else_block, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::WhenExpr(when_expr) => { - for &arm in &when_expr.arms { - walk_node(arm, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::WhenArm(when_arm) => { - walk_node(when_arm.cond, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(when_arm.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Alloc(alloc) => { - walk_node(alloc.ty, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Mutate(mutate) => { - walk_node(mutate.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(mutate.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Borrow(borrow) => { - walk_node(borrow.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(borrow.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::Peek(peek) => { - walk_node(peek.target, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - walk_node(peek.body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::MemberAccess(member) => { - walk_node(member.object, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - NodeKind::TypeDecl(type_decl) => { - if let Some(body) = type_decl.body { - walk_node(body, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::TypeBody(body) => { - for &method in &body.methods { - walk_node(method, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - NodeKind::ServiceDecl(service) => { - for &member in &service.members { - walk_node(member, arena, module, index, imports, ref_index, node_to_symbol, diagnostics); - } - } - // Literal nodes or those that do not contain other nodes to walk (at this level) - NodeKind::IntLit(_) | NodeKind::FloatLit(_) | NodeKind::BoundedLit(_) | NodeKind::StringLit(_) | - NodeKind::TypeName(_) | NodeKind::TypeApp(_) | NodeKind::Import(_) | NodeKind::ImportSpec(_) | - NodeKind::ServiceFnSig(_) | NodeKind::ConstructorDecl(_) | NodeKind::ConstantDecl(_) => {} - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::frontends::pbs::ast::{ - AstArena, BlockNodeArena, CallNodeArena, ExprStmtNodeArena, FileNodeArena, FnDeclNodeArena, - IdentNodeArena, TypeDeclNodeArena, - }; - use crate::common::spans::{Span, FileId}; - - #[test] - fn test_build_def_index_success() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - // Create a dummy body for the function to avoid panic/invalid access - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 0, 0)); - - let fn_name = interner.intern("my_func"); - let fn_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: Some("pub".to_string()), - name: fn_name, - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 10, 20)); - - let type_name = interner.intern("MyStruct"); - let type_id = arena.push(NodeKind::TypeDecl(TypeDeclNodeArena { - vis: None, - type_kind: "struct".to_string(), - name: type_name, - is_host: false, - params: vec![], - constructors: vec![], - constants: vec![], - body: None, - }), Span::new(FileId(0), 30, 40)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![fn_id, type_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let (symbols, index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None); - - assert!(diagnostics.is_empty()); - assert_eq!(symbols.symbols.len(), 2); - - let fn_sym_id = index.get(DefKey { module: ModuleId(1), name: fn_name, namespace: Namespace::Value }).unwrap(); - let fn_sym = symbols.get(fn_sym_id); - assert_eq!(fn_sym.name, fn_name); - assert_eq!(fn_sym.kind, SymbolKind::Function); - assert!(fn_sym.exported); - - let type_sym_id = index.get(DefKey { module: ModuleId(1), name: type_name, namespace: Namespace::Type }).unwrap(); - let type_sym = symbols.get(type_sym_id); - assert_eq!(type_sym.name, type_name); - assert_eq!(type_sym.kind, SymbolKind::Struct); - assert!(!type_sym.exported); - } - - #[test] - fn test_build_def_index_duplicate() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - let name = interner.intern("conflict"); - - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 0, 0)); - - let fn1_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name, - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 10, 20)); - - let fn2_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name, - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 30, 40)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![fn1_id, fn2_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None); - - assert_eq!(diagnostics.len(), 1); - assert_eq!(diagnostics[0].code, "E_RESOLVE_DUPLICATE_SYMBOL"); - assert_eq!(diagnostics[0].span, Span::new(FileId(0), 30, 40)); - } - - #[test] - fn test_node_to_symbol_binding() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - let name = interner.intern("bound_func"); - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 0, 0)); - let decl_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name, - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 10, 20)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![decl_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let (symbols, _index, _ref_index, node_to_symbol, _diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None); - - let symbol_id = node_to_symbol.get(decl_id).expect("Node should be bound to a symbol"); - let symbol = symbols.get(symbol_id); - assert_eq!(symbol.name, name); - } - - #[test] - fn test_resolve_undefined_identifier() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - let ident_name = interner.intern("unknown"); - let ident_id = arena.push(NodeKind::Ident(IdentNodeArena { name: ident_name }), Span::new(FileId(0), 50, 60)); - let expr_stmt = arena.push(NodeKind::ExprStmt(ExprStmtNodeArena { expr: ident_id }), Span::new(FileId(0), 50, 60)); - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![expr_stmt], tail: None }), Span::new(FileId(0), 40, 70)); - - let fn_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name: interner.intern("main"), - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 10, 80)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![fn_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None); - - assert_eq!(diagnostics.len(), 1); - assert_eq!(diagnostics[0].code, "E_RESOLVE_UNDEFINED_IDENTIFIER"); - assert_eq!(diagnostics[0].span, Span::new(FileId(0), 50, 60)); - } - - #[test] - fn test_resolve_reference_success() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - // fn target() {} - let target_name = interner.intern("target"); - let target_body = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 5, 5)); - let target_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name: target_name, - params: vec![], - ret: None, - else_fallback: None, - body: target_body, - }), Span::new(FileId(0), 0, 10)); - - // fn caller() { target(); } - let ident_id = arena.push(NodeKind::Ident(IdentNodeArena { name: target_name }), Span::new(FileId(0), 50, 56)); - let call_id = arena.push(NodeKind::Call(CallNodeArena { callee: ident_id, args: vec![] }), Span::new(FileId(0), 50, 58)); - let expr_stmt = arena.push(NodeKind::ExprStmt(ExprStmtNodeArena { expr: call_id }), Span::new(FileId(0), 50, 58)); - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![expr_stmt], tail: None }), Span::new(FileId(0), 40, 70)); - - let caller_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name: interner.intern("caller"), - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 30, 80)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![target_id, caller_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let (_symbols, index, ref_index, node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None); - - assert!(diagnostics.is_empty(), "Diagnostics should be empty: {:?}", diagnostics); - - let target_sym_id = index.get(DefKey { module: ModuleId(1), name: target_name, namespace: Namespace::Value }).expect("target should be in index"); - let refs = ref_index.refs_of(target_sym_id); - assert_eq!(refs.len(), 1); - assert_eq!(refs[0], Span::new(FileId(0), 50, 56)); - - let bound_id = node_to_symbol.get(ident_id).expect("ident should be bound to symbol"); - assert_eq!(bound_id, target_sym_id); - } - - #[test] - fn test_resolve_visibility_violation() { - let mut interner = NameInterner::new(); - - // Módulo 1: define função privada - let mut arena1 = AstArena::default(); - let target_name = interner.intern("private_func"); - let body1 = arena1.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 0, 0)); - let decl1 = arena1.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, // Privado - name: target_name, - params: vec![], - ret: None, - else_fallback: None, - body: body1, - }), Span::new(FileId(0), 0, 10)); - let file1 = arena1.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![decl1], - }), Span::new(FileId(0), 0, 100)); - arena1.roots.push(file1); - - let (symbols1, index1, _, _, _) = build_def_index(&arena1, ModuleId(1), &interner, None); - - // Módulo 2: tenta usar função privada do Módulo 1 - let mut arena2 = AstArena::default(); - let ident_id = arena2.push(NodeKind::Ident(IdentNodeArena { name: target_name }), Span::new(FileId(0), 50, 62)); - let expr_stmt = arena2.push(NodeKind::ExprStmt(ExprStmtNodeArena { expr: ident_id }), Span::new(FileId(0), 50, 62)); - let body2 = arena2.push(NodeKind::Block(BlockNodeArena { stmts: vec![expr_stmt], tail: None }), Span::new(FileId(0), 40, 70)); - let caller = arena2.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: Some("pub".to_string()), - name: interner.intern("caller"), - params: vec![], - ret: None, - else_fallback: None, - body: body2, - }), Span::new(FileId(0), 30, 80)); - let file2 = arena2.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![caller], - }), Span::new(FileId(0), 0, 100)); - arena2.roots.push(file2); - - let (_symbols2, _index2, _ref_index2, _node_to_symbol2, diagnostics) = - build_def_index(&arena2, ModuleId(2), &interner, Some((&symbols1, &index1))); - - assert_eq!(diagnostics.len(), 1); - assert_eq!(diagnostics[0].code, "E_RESOLVE_VISIBILITY"); - assert_eq!(diagnostics[0].span, Span::new(FileId(0), 50, 62)); - } - - #[test] - fn test_determinism() { - let mut arena = AstArena::default(); - let mut interner = NameInterner::new(); - - let target_name = interner.intern("target"); - let target_body = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![], tail: None }), Span::new(FileId(0), 5, 5)); - let target_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: Some("pub".to_string()), - name: target_name, - params: vec![], - ret: None, - else_fallback: None, - body: target_body, - }), Span::new(FileId(0), 0, 10)); - - let caller_name = interner.intern("caller"); - let ident_id = arena.push(NodeKind::Ident(IdentNodeArena { name: target_name }), Span::new(FileId(0), 50, 56)); - let call_id = arena.push(NodeKind::Call(CallNodeArena { callee: ident_id, args: vec![] }), Span::new(FileId(0), 50, 58)); - let expr_stmt = arena.push(NodeKind::ExprStmt(ExprStmtNodeArena { expr: call_id }), Span::new(FileId(0), 50, 58)); - let body_id = arena.push(NodeKind::Block(BlockNodeArena { stmts: vec![expr_stmt], tail: None }), Span::new(FileId(0), 40, 70)); - - let caller_id = arena.push(NodeKind::FnDecl(FnDeclNodeArena { - vis: None, - name: caller_name, - params: vec![], - ret: None, - else_fallback: None, - body: body_id, - }), Span::new(FileId(0), 30, 80)); - - let file_id = arena.push(NodeKind::File(FileNodeArena { - imports: vec![], - decls: vec![target_id, caller_id], - }), Span::new(FileId(0), 0, 100)); - - arena.roots.push(file_id); - - let run1 = build_def_index(&arena, ModuleId(1), &interner, None); - let run2 = build_def_index(&arena, ModuleId(1), &interner, None); - - // runX is (SymbolArena, DefIndex, RefIndex, NodeToSymbol, Vec) - - let json1_symbols = serde_json::to_string(&run1.0).unwrap(); - let json2_symbols = serde_json::to_string(&run2.0).unwrap(); - assert_eq!(json1_symbols, json2_symbols, "SymbolArena should be deterministic"); - - let json1_refs = serde_json::to_string(&run1.2).unwrap(); - let json2_refs = serde_json::to_string(&run2.2).unwrap(); - assert_eq!(json1_refs, json2_refs, "RefIndex should be deterministic"); - - let json1_node_to_symbol = serde_json::to_string(&run1.3).unwrap(); - let json2_node_to_symbol = serde_json::to_string(&run2.3).unwrap(); - assert_eq!(json1_node_to_symbol, json2_node_to_symbol, "NodeToSymbol should be deterministic"); - - let json1_diags = serde_json::to_string(&run1.4).unwrap(); - let json2_diags = serde_json::to_string(&run2.4).unwrap(); - assert_eq!(json1_diags, json2_diags, "Diagnostics should be deterministic"); - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/resolver.rs deleted file mode 100644 index 7c3fcfe6..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/resolver.rs +++ /dev/null @@ -1,1389 +0,0 @@ -use crate::analysis::symbols::{SymbolArena, NodeToSymbol}; -use crate::analysis::types::{TypeArena, TypeFacts, TypeKind}; -use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; -use crate::common::spans::Span; -use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::symbols::*; -use prometeu_analysis::{NameId, NameInterner, SymbolId, ModuleId, TypeId, NodeId}; -use std::collections::HashMap; - -pub trait ModuleProvider { - fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols>; -} - -pub struct Resolver<'a> { - interner: &'a NameInterner, - module_provider: &'a dyn ModuleProvider, - current_module: &'a ModuleSymbols, - scopes: Vec)>>, - pub imported_symbols: ModuleSymbols, - diagnostics: Vec, - pub type_arena: TypeArena, - pub type_facts: TypeFacts, - pub symbol_arena: crate::analysis::symbols::SymbolArena, - pub node_to_symbol: crate::analysis::symbols::NodeToSymbol, - primitives: HashMap, -} - -impl<'a> Resolver<'a> { - pub fn new( - current_module: &'a ModuleSymbols, - module_provider: &'a dyn ModuleProvider, - interner: &'a NameInterner, - ) -> Self { - Self { - interner, - module_provider, - current_module, - scopes: Vec::new(), - imported_symbols: ModuleSymbols::new(), - diagnostics: Vec::new(), - type_arena: TypeArena::new(), - type_facts: TypeFacts::new(), - symbol_arena: SymbolArena::new(), - node_to_symbol: NodeToSymbol::new(), - primitives: HashMap::new(), - } - } - - pub fn bootstrap_types(&mut self, interner: &NameInterner) { - let primitive_names = ["int", "bool", "float", "string", "bounded", "void"]; - for name in primitive_names { - let name_id = interner.get(name).expect("primitive name not interned"); - let type_id = self.type_arena.intern_type(TypeKind::Primitive { name: name_id }); - self.primitives.insert(name.to_string(), type_id); - } - } - - pub fn resolve(&mut self, arena: &AstArena, root: NodeId) -> Result<(), DiagnosticBundle> { - let file = match arena.kind(root) { - NodeKind::File(file) => file, - _ => { - return Err(DiagnosticBundle::error( - "E_RESOLVE_INVALID_ROOT", - "Expected File node as root".to_string(), - arena.span(root), - )) - } - }; - - // Step 0: Populate symbol_arena with top-level symbols for global lookup - for (name, list) in &self.current_module.type_symbols.symbols { - for sym in list { - self.symbol_arena.insert(crate::analysis::symbols::Symbol { - name: *name, - kind: match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - }, - exported: sym.visibility == Visibility::Pub, - module: ModuleId(0), - decl_span: sym.span.clone(), - }); - } - } - for (name, list) in &self.current_module.value_symbols.symbols { - for sym in list { - self.symbol_arena.insert(crate::analysis::symbols::Symbol { - name: *name, - kind: match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - }, - exported: sym.visibility == Visibility::Pub, - module: ModuleId(0), - decl_span: sym.span.clone(), - }); - } - } - - // Step 1: Process imports to populate imported_symbols - for imp in &file.imports { - if let NodeKind::Import(imp_node) = arena.kind(*imp) { - self.resolve_import(arena, *imp, imp_node); - } - } - - // Add imported symbols to symbol_arena too - for (name, list) in &self.imported_symbols.type_symbols.symbols { - for sym in list { - self.symbol_arena.insert(crate::analysis::symbols::Symbol { - name: *name, - kind: match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - }, - exported: sym.visibility == Visibility::Pub, - module: ModuleId(0), // Should be target module - decl_span: sym.span.clone(), - }); - } - } - for (name, list) in &self.imported_symbols.value_symbols.symbols { - for sym in list { - self.symbol_arena.insert(crate::analysis::symbols::Symbol { - name: *name, - kind: match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - }, - exported: sym.visibility == Visibility::Pub, - module: ModuleId(0), // Should be target module - decl_span: sym.span.clone(), - }); - } - } - - // Step 2: Resolve all top-level declarations - for decl in &file.decls { - self.resolve_node(arena, *decl); - } - - if !self.diagnostics.is_empty() { - return Err(DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - }); - } - - Ok(()) - } - - fn resolve_import(&mut self, arena: &AstArena, imp_id: NodeId, imp: &ImportNodeArena) { - let provider = self.module_provider; - if let Some(target_symbols) = provider.get_module_symbols(&imp.from) { - let spec = match arena.kind(imp.spec) { - NodeKind::ImportSpec(spec) => spec, - _ => { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_INVALID_IMPORT".to_string(), - message: "Invalid import spec".to_string(), - span: arena.span(imp_id), - related: Vec::new(), - }); - return; - } - }; - - for name in &spec.path { - // Try to find in Type namespace - if let Some(sym) = target_symbols.type_symbols.get(*name) { - if sym.visibility == Visibility::Pub { - let is_service = sym.kind == SymbolKind::Service; - let mut cloned = sym.clone(); - cloned.origin = Some(imp.from.clone()); - if let Err(_) = self.imported_symbols.type_symbols.insert(cloned) { - self.error_duplicate_import(*name, arena.span(imp_id)); - } - // If a Service type is imported, also bring its public methods from the same module into value namespace - if is_service { - let base = self.interner.resolve(*name); - let prefix = format!("{}.", base); - for list in target_symbols.value_symbols.symbols.values() { - for vs in list { - // Apenas métodos do service importado: nomes exportados de métodos devem ser no formato "Service.method#sigN" - let vname = self.interner.resolve(vs.name); - if vname.starts_with(&prefix) { - if vs.visibility == Visibility::Pub { - let mut vsym = vs.clone(); - vsym.origin = Some(imp.from.clone()); - let _ = self.imported_symbols.value_symbols.insert(vsym); - } - } - } - } - } - } else { - self.error_visibility(sym, arena.span(imp_id)); - } - } - // Try to find in Value namespace - else if let Some(sym) = target_symbols.value_symbols.get(*name) { - if sym.visibility == Visibility::Pub { - let mut sym = sym.clone(); - sym.origin = Some(imp.from.clone()); - if let Err(_) = self.imported_symbols.value_symbols.insert(sym) { - self.error_duplicate_import(*name, arena.span(imp_id)); - } - } else { - self.error_visibility(sym, arena.span(imp_id)); - } - } else { - self.error_undefined(*name, arena.span(imp_id)); - } - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_INVALID_IMPORT".to_string(), - message: format!("Module not found: {}", imp.from), - span: arena.span(imp_id), - related: Vec::new(), - }); - } - } - - fn resolve_node(&mut self, arena: &AstArena, node: NodeId) { - match arena.kind(node) { - NodeKind::FnDecl(n) => self.resolve_fn_decl(arena, node, n), - NodeKind::ServiceDecl(n) => self.resolve_service_decl(arena, node, n), - NodeKind::TypeDecl(n) => self.resolve_type_decl(arena, node, n), - NodeKind::Block(n) => self.resolve_block(arena, n), - NodeKind::LetStmt(n) => self.resolve_let_stmt(arena, node, n), - NodeKind::ExprStmt(n) => self.resolve_node(arena, n.expr), - NodeKind::ReturnStmt(n) => { - if let Some(expr) = n.expr { - self.resolve_node(arena, expr); - } - } - NodeKind::Call(n) => { - if let NodeKind::Ident(id) = arena.kind(n.callee) { - if !self.is_builtin(id.name, Namespace::Value) { - let (value_sym, sym_id) = match self.lookup_with_id(id.name, Namespace::Value) { - Some((sym, id)) => (Some(sym), id), - None => (None, None), - }; - if let Some(sid) = sym_id { - self.node_to_symbol.bind_node(n.callee, sid); - } - if value_sym.is_none() && self.lookup_identifier(id.name, Namespace::Type).is_none() { - // TODO: Resolver for v0 allows unresolved call targets (e.g. constructors via prelude) - } - } - } else { - self.resolve_node(arena, n.callee); - } - for arg in &n.args { - self.resolve_node(arena, *arg); - } - } - NodeKind::Unary(n) => { - self.resolve_node(arena, n.expr); - match n.op.as_str() { - "-" => { - let int_type = self.primitives.get("int").copied(); - if let Some(expr_type) = self.type_facts.get_node_type(n.expr) { - if Some(expr_type) == int_type { - if let Some(id) = int_type { - self.type_facts.set_node_type(node, id); - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: "Unary '-' operator expects 'int'".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - } - } - } - "!" => { - let bool_type = self.primitives.get("bool").copied(); - if let Some(expr_type) = self.type_facts.get_node_type(n.expr) { - if Some(expr_type) == bool_type { - if let Some(id) = bool_type { - self.type_facts.set_node_type(node, id); - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: "Unary '!' operator expects 'bool'".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - } - } - } - _ => {} - } - } - NodeKind::Binary(n) => { - self.resolve_node(arena, n.left); - self.resolve_node(arena, n.right); - - let left_type = self.type_facts.get_node_type(n.left); - let right_type = self.type_facts.get_node_type(n.right); - let int_type = self.primitives.get("int").copied(); - let bool_type = self.primitives.get("bool").copied(); - - match n.op.as_str() { - "+" | "-" | "*" | "/" => { - if let (Some(l), Some(r)) = (left_type, right_type) { - if Some(l) == int_type && Some(r) == int_type { - if let Some(id) = int_type { - self.type_facts.set_node_type(node, id); - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: format!("Binary '{}' operator expects 'int' operands", n.op), - span: arena.span(node), - related: Vec::new(), - }); - } - } - } - "==" | "<" | ">" | "<=" | ">=" | "!=" => { - if let (Some(l), Some(r)) = (left_type, right_type) { - if l == r { - if let Some(id) = bool_type { - self.type_facts.set_node_type(node, id); - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: format!("Binary '{}' operator expects operands of the same type", n.op), - span: arena.span(node), - related: Vec::new(), - }); - } - } - } - _ => {} - } - } - NodeKind::TypeName(_) | NodeKind::TypeApp(_) => { - self.resolve_type_ref(arena, node); - } - NodeKind::ConstructorDecl(n) => self.resolve_constructor_decl(arena, node, n), - NodeKind::ConstantDecl(n) => self.resolve_node(arena, n.value), - NodeKind::Alloc(n) => { - self.resolve_type_ref(arena, n.ty); - } - NodeKind::Mutate(n) => { - self.resolve_node(arena, n.target); - self.enter_scope(); - self.define_local(n.binding, arena.span(node), SymbolKind::Local); - self.resolve_node(arena, n.body); - self.exit_scope(); - } - NodeKind::Borrow(n) => { - self.resolve_node(arena, n.target); - self.enter_scope(); - self.define_local(n.binding, arena.span(node), SymbolKind::Local); - self.resolve_node(arena, n.body); - self.exit_scope(); - } - NodeKind::Peek(n) => { - self.resolve_node(arena, n.target); - self.enter_scope(); - self.define_local(n.binding, arena.span(node), SymbolKind::Local); - self.resolve_node(arena, n.body); - self.exit_scope(); - } - NodeKind::Cast(n) => { - self.resolve_node(arena, n.expr); - self.resolve_type_ref(arena, n.ty); - } - NodeKind::IfExpr(n) => { - self.resolve_node(arena, n.cond); - self.resolve_node(arena, n.then_block); - if let Some(else_block) = n.else_block { - self.resolve_node(arena, else_block); - } - } - NodeKind::WhenExpr(n) => { - for arm in &n.arms { - if let NodeKind::WhenArm(arm_node) = arena.kind(*arm) { - self.resolve_node(arena, arm_node.cond); - self.resolve_node(arena, arm_node.body); - } - } - } - NodeKind::Ident(n) => { - if let Some((_sym, Some(sym_id))) = self.lookup_with_id(n.name, Namespace::Value) { - self.node_to_symbol.bind_node(node, sym_id); - if let Some(ty) = self.type_facts.get_symbol_type(sym_id) { - self.type_facts.set_node_type(node, ty); - } - } else { - self.resolve_identifier(n.name, arena.span(node), Namespace::Value); - } - } - NodeKind::IntLit(_) => { - if let Some(id) = self.primitives.get("int") { - self.type_facts.set_node_type(node, *id); - } - } - NodeKind::FloatLit(_) => { - if let Some(id) = self.primitives.get("float") { - self.type_facts.set_node_type(node, *id); - } - } - NodeKind::BoundedLit(_) => { - if let Some(id) = self.primitives.get("bounded") { - self.type_facts.set_node_type(node, *id); - } - } - NodeKind::StringLit(_) => { - if let Some(id) = self.primitives.get("string") { - self.type_facts.set_node_type(node, *id); - } - } - NodeKind::MemberAccess(n) => match arena.kind(n.object) { - NodeKind::Ident(id) => { - let ident_span = arena.span(n.object); - if !self.is_builtin(id.name, Namespace::Type) { - if self.lookup_identifier(id.name, Namespace::Value).is_none() { - // If not found in Value namespace, try Type namespace (for Contracts/Services) - if self.lookup_identifier(id.name, Namespace::Type).is_none() { - // Still not found, use resolve_identifier to report error in Value namespace - self.resolve_identifier(id.name, ident_span, Namespace::Value); - } - } - } - } - _ => { - self.resolve_node(arena, n.object); - } - }, - _ => {} - } - } - - fn resolve_fn_decl(&mut self, arena: &AstArena, _id: NodeId, n: &FnDeclNodeArena) { - self.enter_scope(); - for param in &n.params { - let ty_id = self.resolve_type_ref(arena, param.ty); - let sym_id = self.define_local(param.name, param.span.clone(), SymbolKind::Local); - if let (Some(tid), Some(sid)) = (ty_id, sym_id) { - self.type_facts.set_symbol_type(sid, tid); - // The Ident node of the parameter in the declaration could also be bound - // But ParamNodeArena does not have a direct NodeId in the AST that represents the parameter's Ident. - // `param.name` is a NameId. - } - } - if let Some(ret) = n.ret { - self.resolve_type_ref(arena, ret); - } - self.resolve_node(arena, n.body); - self.exit_scope(); - } - - fn resolve_service_decl(&mut self, arena: &AstArena, id: NodeId, n: &ServiceDeclNodeArena) { - if let Some(ext) = n.extends { - self.resolve_identifier(ext, arena.span(id), Namespace::Type); - } - for member in &n.members { - match arena.kind(*member) { - NodeKind::ServiceFnSig(sig) => { - for param in &sig.params { - self.resolve_type_ref(arena, param.ty); - } - self.resolve_type_ref(arena, sig.ret); - } - NodeKind::ServiceFnDecl(decl) => { - // service com corpo: resolve tipos, define parâmetros como locais e resolve corpo - for param in &decl.params { - self.resolve_type_ref(arena, param.ty); - } - self.resolve_type_ref(arena, decl.ret); - - self.enter_scope(); - for param in &decl.params { - let sym_id = self.define_local(param.name, param.span.clone(), SymbolKind::Local); - if let Some(sym_id) = sym_id { - if let Some(ty_id) = self.resolve_type_ref(arena, param.ty) { - self.type_facts.set_symbol_type(sym_id, ty_id); - } - } - } - self.resolve_node(arena, decl.body); - self.exit_scope(); - } - _ => {} - } - } - } - - fn resolve_type_decl(&mut self, arena: &AstArena, _id: NodeId, n: &TypeDeclNodeArena) { - for param in &n.params { - self.resolve_type_ref(arena, param.ty); - } - for constructor in &n.constructors { - if let NodeKind::ConstructorDecl(ctor) = arena.kind(*constructor) { - self.resolve_constructor_decl(arena, *constructor, ctor); - } - } - self.enter_scope(); - for ctor_id in &n.constructors { - if let NodeKind::ConstructorDecl(ctor) = arena.kind(*ctor_id) { - self.define_local(ctor.name, arena.span(*ctor_id), SymbolKind::Local); - } - } - for constant in &n.constants { - self.resolve_node(arena, *constant); - } - self.exit_scope(); - if let Some(body_node) = n.body { - if let NodeKind::TypeBody(body) = arena.kind(body_node) { - for member in &body.members { - self.resolve_type_ref(arena, member.ty); - } - for method in &body.methods { - if let NodeKind::ServiceFnSig(sig) = arena.kind(*method) { - for param in &sig.params { - self.resolve_type_ref(arena, param.ty); - } - self.resolve_type_ref(arena, sig.ret); - } - } - } - } - } - - fn resolve_constructor_decl( - &mut self, - arena: &AstArena, - _id: NodeId, - n: &ConstructorDeclNodeArena, - ) { - self.enter_scope(); - for param in &n.params { - self.resolve_type_ref(arena, param.ty); - self.define_local(param.name, param.span.clone(), SymbolKind::Local); - } - for init in &n.initializers { - self.resolve_node(arena, *init); - } - self.resolve_node(arena, n.body); - self.exit_scope(); - } - - fn resolve_block(&mut self, arena: &AstArena, n: &BlockNodeArena) { - self.enter_scope(); - for stmt in &n.stmts { - self.resolve_node(arena, *stmt); - } - self.exit_scope(); - } - - fn resolve_let_stmt(&mut self, arena: &AstArena, id: NodeId, n: &LetStmtNodeArena) { - let ty_id = if let Some(ty) = n.ty { - self.resolve_type_ref(arena, ty) - } else { - None - }; - self.resolve_node(arena, n.init); - let sym_id = self.define_local(n.name, arena.span(id), SymbolKind::Local); - - if let Some(sid) = sym_id { - self.node_to_symbol.bind_node(id, sid); - if let Some(tid) = ty_id { - self.type_facts.set_symbol_type(sid, tid); - } - } - } - - fn resolve_type_ref(&mut self, arena: &AstArena, node: NodeId) -> Option { - let type_id = self.lower_type_node(arena, node); - if let Some(tid) = type_id { - self.type_facts.set_node_type(node, tid); - } - type_id - } - - fn lower_type_node(&mut self, arena: &AstArena, node: NodeId) -> Option { - match arena.kind(node) { - NodeKind::TypeName(n) => { - let name = self.interner.resolve(n.name); - if let Some(prim) = self.primitives.get(name) { - return Some(*prim); - } - - // Resolve struct/type symbol - if let Some((_sym, sym_id)) = self.lookup_with_id(n.name, Namespace::Type) { - // Try to find if this symbol is a type symbol that can be converted to TypeId - // For now, we only have TypeKind::Struct - if let Some(sid) = sym_id { - let kind = TypeKind::Struct { sym: sid }; - return Some(self.type_arena.intern_type(kind)); - } else { - // sym_id is None, but _sym exists. This means it's a built-in or something not in analysis arena yet. - // But for Struct resolution we need a SymbolId. - // If it's a built-in it should have been handled by self.primitives. - } - } - - // If not found in current or imports, maybe it's a symbol from another module not yet in SymbolArena? - // Actually, lookup_with_id checks imported_symbols too. - - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_UNKNOWN_TYPE".to_string(), - message: format!("Unknown type: {}", name), - span: arena.span(node), - related: Vec::new(), - }); - None - } - NodeKind::TypeApp(n) => { - let base_name = self.interner.resolve(n.base); - match base_name { - "optional" => { - if n.args.len() != 1 { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_INVALID_ARGS".to_string(), - message: "optional expects exactly 1 argument".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - return None; - } - let inner = self.lower_type_node(arena, n.args[0])?; - Some(self.type_arena.intern_type(TypeKind::Optional { inner })) - } - "result" => { - if n.args.len() != 2 { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_INVALID_ARGS".to_string(), - message: "result expects exactly 2 arguments".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - return None; - } - let ok = self.lower_type_node(arena, n.args[0])?; - let err = self.lower_type_node(arena, n.args[1])?; - Some(self.type_arena.intern_type(TypeKind::Result { ok, err })) - } - "array" => { - if n.args.len() < 1 || n.args.len() > 2 { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_INVALID_ARGS".to_string(), - message: "array or array[N] expects 1 or 2 arguments".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - return None; - } - let inner = self.lower_type_node(arena, n.args[0])?; - let len = if n.args.len() == 2 { - match arena.kind(n.args[1]) { - NodeKind::IntLit(lit) => Some(lit.value as u32), - _ => { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_INVALID_ARGS".to_string(), - message: "Array length must be an integer literal".to_string(), - span: arena.span(n.args[1]), - related: Vec::new(), - }); - None - } - } - } else { - None - }; - Some(self.type_arena.intern_type(TypeKind::Array { inner, len })) - } - _ => { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_UNKNOWN_TYPE".to_string(), - message: format!("Unknown generic type: {}", base_name), - span: arena.span(node), - related: Vec::new(), - }); - None - } - } - } - _ => { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_NOT_A_TYPE".to_string(), - message: "Expected a type node".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - None - } - } - } - - fn resolve_identifier(&mut self, name: NameId, span: Span, namespace: Namespace) -> Option { - if self.is_builtin(name, namespace) { - return None; - } - - if let Some(sym) = self.lookup_identifier(name, namespace) { - Some(sym) - } else { - if namespace == Namespace::Type { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_UNKNOWN_TYPE".to_string(), - message: format!("Unknown type: {}", self.interner.resolve(name)), - span, - related: Vec::new(), - }); - } else { - self.error_undefined(name, span); - } - None - } - } - - fn is_builtin(&self, name: NameId, namespace: Namespace) -> bool { - let name = self.interner.resolve(name); - match namespace { - Namespace::Type => match name { - "int" | "float" | "string" | "bool" | "void" | "optional" | "result" | "bounded" | - "Color" | "ButtonState" | "Pad" | "Touch" => true, - _ => false, - }, - Namespace::Value => match name { - "none" | "some" | "ok" | "err" | "true" | "false" => true, - _ => false, - }, - } - } - - fn lookup_identifier(&self, name: NameId, namespace: Namespace) -> Option { - self.lookup_with_id(name, namespace).map(|(sym, _)| sym) - } - - fn lookup_with_id(&self, name: NameId, namespace: Namespace) -> Option<(Symbol, Option)> { - // 1. local bindings - if namespace == Namespace::Value { - for scope in self.scopes.iter().rev() { - if let Some(pair) = scope.get(&name) { - return Some(pair.clone()); - } - } - } - - let table = if namespace == Namespace::Type { - &self.current_module.type_symbols - } else { - &self.current_module.value_symbols - }; - - // 2 & 3. file-private and module symbols - if let Some(sym) = table.get(name) { - // Se encontrarmos no módulo atual, tentamos achar o SymbolId correspondente na symbol_arena - let sym_id = self.symbol_arena.symbols.iter().enumerate().find(|(_, s)| { - s.name == sym.name && s.kind == match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - } - }).map(|(i, _)| SymbolId(i as u32)); - - return Some((sym.clone(), sym_id)); - } - - // 4. imported symbols - let imp_table = if namespace == Namespace::Type { - &self.imported_symbols.type_symbols - } else { - &self.imported_symbols.value_symbols - }; - if let Some(sym) = imp_table.get(name) { - let sym_id = self.symbol_arena.symbols.iter().enumerate().find(|(_, s)| { - s.name == sym.name && s.kind == match sym.kind { - SymbolKind::Function => crate::analysis::symbols::SymbolKind::Function, - SymbolKind::Service => crate::analysis::symbols::SymbolKind::Service, - SymbolKind::Struct => crate::analysis::symbols::SymbolKind::Struct, - SymbolKind::Contract => crate::analysis::symbols::SymbolKind::Contract, - SymbolKind::ErrorType => crate::analysis::symbols::SymbolKind::ErrorType, - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - } - }).map(|(i, _)| SymbolId(i as u32)); - - return Some((sym.clone(), sym_id)); - } - - // 5. Fallback for constructor calls: check Type namespace if looking for a Value - if namespace == Namespace::Value { - if let Some(sym) = self.current_module.type_symbols.get(name) { - if sym.kind == SymbolKind::Struct { - return Some((sym.clone(), None)); - } - } - if let Some(sym) = self.imported_symbols.type_symbols.get(name) { - if sym.kind == SymbolKind::Struct { - return Some((sym.clone(), None)); - } - } - } - - None - } - - fn define_local(&mut self, name: NameId, span: Span, kind: SymbolKind) -> Option { - let scope = self.scopes.last_mut().expect("No scope to define local"); - - // Check for collision in Type namespace at top-level? - // Actually, the spec says "A name may not exist in both namespaces". - // If we want to be strict, we check current module's type symbols too. - if let Some(existing) = self.current_module.type_symbols.get(name) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_NAMESPACE_COLLISION".to_string(), - message: format!( - "Local variable '{}' collides with a type name", - self.interner.resolve(name) - ), - span, - related: vec![("type defined here".to_string(), existing.span.clone())], - }); - return None; - } - - if let Some((prev_sym, _)) = scope.get(&name) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(), - message: format!("Duplicate local variable '{}'", self.interner.resolve(name)), - span, - related: vec![("previous definition here".to_string(), prev_sym.span.clone())], - }); - None - } else { - let symbol = Symbol { - name, - kind, - namespace: Namespace::Value, - visibility: Visibility::FilePrivate, - ty: None, // Will be set by TypeChecker - is_host: false, - span: span.clone(), - origin: None, - }; - - // Criar símbolo na symbol_arena (análise global) - let sym_id = self.symbol_arena.insert(crate::analysis::symbols::Symbol { - name, - kind: match kind { - SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local, - _ => crate::analysis::symbols::SymbolKind::Value, - }, - exported: false, - module: ModuleId(0), // TODO: set actual module id when available - decl_span: span, - }); - - scope.insert(name, (symbol, Some(sym_id))); - Some(sym_id) - } - } - - fn enter_scope(&mut self) { - self.scopes.push(HashMap::new()); - } - - fn exit_scope(&mut self) { - self.scopes.pop(); - } - - fn error_undefined(&mut self, name: NameId, span: Span) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_UNDEFINED".to_string(), - message: format!("Undefined identifier: {}", self.interner.resolve(name)), - span, - related: Vec::new(), - }); - } - - fn error_duplicate_import(&mut self, name: NameId, span: Span) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_DUPLICATE_SYMBOL".to_string(), - message: format!("Duplicate import: {}", self.interner.resolve(name)), - span, - related: Vec::new(), - }); - } - - fn error_visibility(&mut self, sym: &Symbol, span: Span) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_VISIBILITY".to_string(), - message: format!( - "DebugSymbol '{}' is not visible here", - self.interner.resolve(sym.name) - ), - span, - related: vec![("symbol defined here".to_string(), sym.span.clone())], - }); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::common::files::FileManager; - use crate::common::spans::Span; - use crate::frontends::pbs::*; - use std::path::PathBuf; - - fn setup_test(source: &str) -> (ParsedAst, usize, NameInterner) { - let mut fm = FileManager::new(); - let file_id = fm.add(PathBuf::from("test.pbs"), source.to_string()); - let mut parser = parser::Parser::new(source, crate::common::spans::FileId(file_id as u32), fm.interner_mut()); - let parsed = parser.parse_file().expect("Parsing failed"); - (parsed, file_id, fm.interner().clone()) - } - - #[test] - fn test_duplicate_symbols() { - let source = " - declare struct Foo() - declare struct Foo() - "; - let (parsed, _, interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let result = collector.collect(&parsed.arena, parsed.root); - - assert!(result.is_err()); - let bundle = result.unwrap_err(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_DUPLICATE_SYMBOL")); - } - - #[test] - fn test_namespace_collision() { - let source = " - declare struct Foo() - fn Foo() {} - "; - let (parsed, _, interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let result = collector.collect(&parsed.arena, parsed.root); - - assert!(result.is_err()); - let bundle = result.unwrap_err(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_NAMESPACE_COLLISION")); - } - - #[test] - fn test_undefined_identifier() { - let source = " - fn main() { - let x = y; - } - "; - let (parsed, _, interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector - .collect(&parsed.arena, parsed.root) - .expect("Collection failed"); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - let result = resolver.resolve(&parsed.arena, parsed.root); - - assert!(result.is_err()); - let bundle = result.unwrap_err(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED")); - } - - #[test] - fn test_local_variable_resolution() { - let source = " - fn main() { - let x = 10; - let y = x; - } - "; - let (parsed, _, interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector - .collect(&parsed.arena, parsed.root) - .expect("Collection failed"); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - let result = resolver.resolve(&parsed.arena, parsed.root); - - assert!(result.is_ok()); - } - - #[test] - fn test_visibility_error() { - let source = " - import PrivateType from \"./other.pbs\" - fn main() {} - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector - .collect(&parsed.arena, parsed.root) - .expect("Collection failed"); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct MockProvider { - other: ModuleSymbols, - } - impl ModuleProvider for MockProvider { - fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> { - if path == "./other.pbs" { Some(&self.other) } else { None } - } - } - - let mut other_ts = SymbolTable::new(); - other_ts.insert(Symbol { - name: interner.intern("PrivateType"), - kind: SymbolKind::Struct, - namespace: Namespace::Type, - visibility: Visibility::FilePrivate, - ty: None, - is_host: false, - span: Span::new(crate::common::spans::FileId(1), 0, 0), - origin: None, - }).unwrap(); - - let mock_provider = MockProvider { - other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() }, - }; - - let mut resolver = Resolver::new(&ms, &mock_provider, &interner); - let result = resolver.resolve(&parsed.arena, parsed.root); - - assert!(result.is_err()); - let bundle = result.unwrap_err(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_VISIBILITY")); - } - - #[test] - fn test_import_resolution() { - let source = " - import PubType from \"./other.pbs\" - fn main() { - let x: PubType = 10; - } - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector - .collect(&parsed.arena, parsed.root) - .expect("Collection failed"); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct MockProvider { - other: ModuleSymbols, - } - impl ModuleProvider for MockProvider { - fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> { - if path == "./other.pbs" { Some(&self.other) } else { None } - } - } - - let mut other_ts = SymbolTable::new(); - other_ts.insert(Symbol { - name: interner.intern("PubType"), - kind: SymbolKind::Struct, - namespace: Namespace::Type, - visibility: Visibility::Pub, - ty: None, - is_host: false, - span: Span::new(crate::common::spans::FileId(1), 0, 0), - origin: None, - }).unwrap(); - - let mock_provider = MockProvider { - other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() }, - }; - - let mut resolver = Resolver::new(&ms, &mock_provider, &interner); - let result = resolver.resolve(&parsed.arena, parsed.root); - - assert!(result.is_ok()); - } - - #[test] - fn test_invalid_import_module_not_found() { - let source = " - import NonExistent from \"./missing.pbs\" - fn main() {} - "; - let (parsed, _, interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector - .collect(&parsed.arena, parsed.root) - .expect("Collection failed"); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - let result = resolver.resolve(&parsed.arena, parsed.root); - - assert!(result.is_err()); - let bundle = result.unwrap_err(); - assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_INVALID_IMPORT")); - } - - #[test] - fn test_typing_literals_and_binary() { - let source = " - fn main() { - let a = 1 + 2; - let b = 1 == 2; - let c = -1; - } - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap(); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - // Ensure primitives are interned for bootstrap - for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); } - let interner_for_bootstrap = interner.clone(); - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - resolver.bootstrap_types(&interner); - resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed"); - - // Verify types in TypeFacts using format_type for easier assertion - use crate::analysis::types::format_type; - - let mut found_a = false; - let mut found_b = false; - let mut found_c = false; - - for i in 0..parsed.arena.nodes.len() { - let id = NodeId(i as u32); - let kind = parsed.arena.kind(id); - - match kind { - NodeKind::Binary(n) if n.op == "+" => { - let ty = resolver.type_facts.get_node_type(id).expect("Binary + should have type"); - assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int"); - found_a = true; - } - NodeKind::Binary(n) if n.op == "==" => { - let ty = resolver.type_facts.get_node_type(id).expect("Binary == should have type"); - assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "bool"); - found_b = true; - } - NodeKind::Unary(n) if n.op == "-" => { - let ty = resolver.type_facts.get_node_type(id).expect("Unary - should have type"); - assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int"); - found_c = true; - } - _ => {} - } - } - - assert!(found_a, "Binary + node not found"); - assert!(found_b, "Binary == node not found"); - assert!(found_c, "Unary - node not found"); - } - - #[test] - fn test_hover_let_annotated_type() { - let source = " - fn main() { - let x: int = 10; - let y = x; - } - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap(); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); } - let interner_for_bootstrap = interner.clone(); - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - resolver.bootstrap_types(&interner); - resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed"); - - use crate::analysis::types::format_type; - - let mut found_y_ref = false; - - for i in 0..parsed.arena.nodes.len() { - let id = NodeId(i as u32); - let kind = parsed.arena.kind(id); - - if let NodeKind::LetStmt(n) = kind { - if interner.resolve(n.name) == "y" { - let init_id = n.init; - if let NodeKind::Ident(ident) = parsed.arena.kind(init_id) { - if interner.resolve(ident.name) == "x" { - let ty = resolver.type_facts.get_node_type(init_id).expect("Ident x should have type"); - assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int"); - found_y_ref = true; - } - } - } - } - } - - assert!(found_y_ref, "Reference to x in let y = x not found or not typed"); - } - - #[test] - fn test_hover_param_type() { - let source = " - fn foo(a: int) { - let b = a; - } - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap(); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); } - let interner_for_bootstrap = interner.clone(); - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - resolver.bootstrap_types(&interner); - resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed"); - - use crate::analysis::types::format_type; - - let mut found_a_ref = false; - - for i in 0..parsed.arena.nodes.len() { - let id = NodeId(i as u32); - let kind = parsed.arena.kind(id); - - if let NodeKind::LetStmt(n) = kind { - if interner.resolve(n.name) == "b" { - let init_id = n.init; - if let NodeKind::Ident(ident) = parsed.arena.kind(init_id) { - if interner.resolve(ident.name) == "a" { - let ty = resolver.type_facts.get_node_type(init_id).expect("Ident a should have type"); - assert_eq!(format_type(ty, &resolver.type_arena, &interner_for_bootstrap, None), "int"); - found_a_ref = true; - } - } - } - } - } - - assert!(found_a_ref, "Reference to a in let b = a not found or not typed"); - } - - #[test] - fn test_lower_type_node_complex() { - let source = " - declare struct MyType() - fn main() { - let x: optional = 1; - let y: result = 2; - let z: array[10] = 3; - let w: MyType = 4; - } - "; - let (parsed, _, mut interner) = setup_test(source); - let mut collector = SymbolCollector::new(&interner); - let (ts, vs) = collector.collect(&parsed.arena, parsed.root).unwrap(); - let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; - - struct EmptyProvider; - impl ModuleProvider for EmptyProvider { - fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } - } - - for p in ["int", "bool", "float", "string", "bounded", "void"] { interner.intern(p); } - let interner_for_bootstrap = interner.clone(); - let mut resolver = Resolver::new(&ms, &EmptyProvider, &interner); - resolver.bootstrap_types(&interner); - resolver.resolve(&parsed.arena, parsed.root).expect("Resolution failed"); - - use crate::analysis::types::format_type; - - let mut found_x = false; - let mut found_y = false; - let mut found_z = false; - let mut found_w = false; - - for i in 0..parsed.arena.nodes.len() { - let id = NodeId(i as u32); - let kind = parsed.arena.kind(id); - - if let NodeKind::LetStmt(n) = kind { - let name = interner.resolve(n.name); - let sym_id = resolver.node_to_symbol.get(id).expect(&format!("Node {:?} should be bound to a symbol", kind)); - let ty_id = resolver.type_facts.get_symbol_type(sym_id).expect("Symbol should have type"); - - match name { - "x" => { - assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "optional"); - found_x = true; - } - "y" => { - assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "result"); - found_y = true; - } - "z" => { - assert_eq!(format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None), "array[10]"); - found_z = true; - } - "w" => { - // MyType is a struct. Since we don't pass symbols to format_type in this test, it should be struct#ID - let formatted = format_type(ty_id, &resolver.type_arena, &interner_for_bootstrap, None); - assert!(formatted.starts_with("struct#"), "Formatted type should be struct#ID, got {}", formatted); - found_w = true; - } - _ => {} - } - } - } - - assert!(found_x); - assert!(found_y); - assert!(found_z); - assert!(found_w); - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/symbols.rs deleted file mode 100644 index 00349acb..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/symbols.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::common::spans::Span; -use crate::frontends::pbs::types::PbsType; -use prometeu_analysis::NameId; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Visibility { - FilePrivate, - Mod, - Pub, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] -pub enum SymbolKind { - Function, - Service, - Struct, - Contract, - ErrorType, - Local, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Namespace { - Type, - Value, -} - -#[derive(Debug, Clone)] -pub struct Symbol { - pub name: NameId, - pub kind: SymbolKind, - pub namespace: Namespace, - pub visibility: Visibility, - pub ty: Option, - pub is_host: bool, - pub span: Span, - pub origin: Option, // e.g. "@sdk:gfx" or "./other" -} - -#[derive(Debug, Clone)] -pub struct SymbolTable { - // Allow multiple entries per name for overloaded functions (Value namespace only). - // For Type namespace, multiple entries are rejected by `insert`. - pub symbols: HashMap>, -} - -#[derive(Debug, Clone)] -pub struct ModuleSymbols { - pub type_symbols: SymbolTable, - pub value_symbols: SymbolTable, -} - -impl ModuleSymbols { - pub fn new() -> Self { - Self { - type_symbols: SymbolTable::new(), - value_symbols: SymbolTable::new(), - } - } -} - -impl SymbolTable { - pub fn new() -> Self { - Self { - symbols: HashMap::new(), - } - } - - pub fn insert(&mut self, symbol: Symbol) -> Result<(), ()> { - match self.symbols.get_mut(&symbol.name) { - Some(list) => { - // If an entry already exists: - // - Allow multiple Function symbols (overloads) - // - Reject duplicates for any other kind - let all_funcs = list.iter().all(|s| s.kind == SymbolKind::Function); - if symbol.kind == SymbolKind::Function && all_funcs { - list.push(symbol); - Ok(()) - } else { - Err(()) - } - } - None => { - self.symbols.insert(symbol.name, vec![symbol]); - Ok(()) - } - } - } - - /// Returns the first symbol for this name (primarily for non-overloaded types/services). - pub fn get(&self, name: NameId) -> Option<&Symbol> { - self.symbols.get(&name).and_then(|v| v.first()) - } - - /// Returns all symbols (e.g., all function overloads) registered under this name. - pub fn get_all(&self, name: NameId) -> Option<&[Symbol]> { - self.symbols.get(&name).map(|v| v.as_slice()) - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/token.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/token.rs deleted file mode 100644 index 12697707..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/token.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::common::spans::Span; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum TokenKind { - // Keywords - Import, - Pub, - Mod, - Service, - Fn, - Let, - Mut, - Declare, - Struct, - Contract, - Host, - Error, - Optional, - Result, - Some, - None, - Ok, - Err, - If, - Else, - When, - For, - In, - Return, - Handle, - Borrow, - Mutate, - Peek, - Take, - Alloc, - Weak, - As, - Bounded, - - // Identifiers and Literals - Identifier(String), - IntLit(i64), - FloatLit(f64), - BoundedLit(u32), - StringLit(String), - - // Punctuation - OpenParen, // ( - CloseParen, // ) - OpenBrace, // { - CloseBrace, // } - OpenBracket, // [ - CloseBracket, // ] - OpenDoubleBracket, // [[ - CloseDoubleBracket, // ]] - Comma, // , - Dot, // . - Colon, // : - Semicolon, // ; - Arrow, // -> - - // Operators - Assign, // = - Plus, // + - Minus, // - - Star, // * - Slash, // / - Percent, // % - Eq, // == - Neq, // != - Lt, // < - Gt, // > - Lte, // <= - Gte, // >= - And, // && - Or, // || - Not, // ! - - // Special - Eof, - Invalid(String), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Token { - pub kind: TokenKind, - pub span: Span, -} - -impl Token { - pub fn new(kind: TokenKind, span: Span) -> Self { - Self { kind, span } - } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/typecheck.rs deleted file mode 100644 index 65435c86..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ /dev/null @@ -1,1464 +0,0 @@ -use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; -use crate::common::spans::Span; -use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::contracts::ContractRegistry; -use crate::frontends::pbs::resolver::ModuleProvider; -use crate::frontends::pbs::symbols::*; -use crate::frontends::pbs::types::PbsType; -use prometeu_analysis::{NameId, NameInterner, NodeId}; -use std::collections::HashMap; - -pub struct TypeChecker<'a> { - module_symbols: &'a mut ModuleSymbols, - imported_symbols: &'a ModuleSymbols, - _module_provider: &'a dyn ModuleProvider, - interner: &'a NameInterner, - scopes: Vec>, - mut_bindings: Vec>, - current_return_type: Option, - struct_constructors: HashMap>, - struct_constants: HashMap>, - struct_methods: HashMap>, - // Mapa global de fields por struct: StructName -> { field_name -> PbsType } - struct_fields: HashMap>, - diagnostics: Vec, - contract_registry: ContractRegistry, - // Contexto atual de tipo (para resolver `this` e métodos/fields) - current_type_context: Option, - current_struct_fields: Option>, -} - -impl<'a> TypeChecker<'a> { - pub fn new( - module_symbols: &'a mut ModuleSymbols, - imported_symbols: &'a ModuleSymbols, - module_provider: &'a dyn ModuleProvider, - interner: &'a NameInterner, - ) -> Self { - Self { - module_symbols, - imported_symbols, - _module_provider: module_provider, - interner, - scopes: Vec::new(), - mut_bindings: Vec::new(), - current_return_type: None, - struct_constructors: HashMap::new(), - struct_constants: HashMap::new(), - struct_methods: HashMap::new(), - struct_fields: HashMap::new(), - diagnostics: Vec::new(), - contract_registry: ContractRegistry::new(), - current_type_context: None, - current_struct_fields: None, - } - } - - pub fn check(&mut self, arena: &AstArena, root: NodeId) -> Result<(), DiagnosticBundle> { - let file = match arena.kind(root) { - NodeKind::File(file) => file, - _ => { - return Err(DiagnosticBundle::error( - "E_TYPECHECK_INVALID_ROOT", - "Expected File node as root".to_string(), - arena.span(root), - )) - } - }; - - // Step 1: Resolve signatures of all top-level declarations - self.resolve_signatures(arena, file); - - // Step 2: Check bodies - for decl in &file.decls { - self.check_node(arena, *decl); - } - - if !self.diagnostics.is_empty() { - return Err(DiagnosticBundle { - diagnostics: self.diagnostics.clone(), - }); - } - - Ok(()) - } - - fn resolve_signatures(&mut self, arena: &AstArena, file: &FileNodeArena) { - for decl in &file.decls { - match arena.kind(*decl) { - NodeKind::FnDecl(n) => { - let decl_span = arena.span(*decl); - let mut params = Vec::new(); - for param in &n.params { - params.push(self.resolve_type_node(arena, param.ty)); - } - let return_type = if let Some(ret) = n.ret { - self.resolve_type_node(arena, ret) - } else { - PbsType::Void - }; - let ty = PbsType::Function { - params, - return_type: Box::new(return_type), - }; - if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&n.name) { - if let Some(sym) = list.iter_mut().find(|s| s.span == decl_span) { - sym.ty = Some(ty); - } - } - } - NodeKind::ServiceDecl(n) => { - // Tipo do próprio service - if let Some(list) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { - if let Some(sym) = list.first_mut() { - sym.ty = Some(PbsType::Service(self.interner.resolve(n.name).to_string())); - } - } - - // Atribuir tipos às assinaturas dos métodos do service (declaração e assinatura) - for member in &n.members { - match arena.kind(*member) { - NodeKind::ServiceFnDecl(method) => { - let m_span = arena.span(*member); - let mut params = Vec::new(); - for p in &method.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let ret_ty = self.resolve_type_node(arena, method.ret); - let m_ty = PbsType::Function { params, return_type: Box::new(ret_ty) }; - if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { - if let Some(sym) = list.iter_mut().find(|s| s.span == m_span) { - sym.ty = Some(m_ty); - } - } - } - NodeKind::ServiceFnSig(method) => { - let m_span = arena.span(*member); - let mut params = Vec::new(); - for p in &method.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let ret_ty = self.resolve_type_node(arena, method.ret); - let m_ty = PbsType::Function { params, return_type: Box::new(ret_ty) }; - if let Some(list) = self.module_symbols.value_symbols.symbols.get_mut(&method.name) { - if let Some(sym) = list.iter_mut().find(|s| s.span == m_span) { - sym.ty = Some(m_ty); - } - } - } - _ => {} - } - } - } - NodeKind::TypeDecl(n) => { - let type_name = self.interner.resolve(n.name).to_string(); - let ty = match n.type_kind.as_str() { - "struct" => PbsType::Struct(type_name.clone()), - "contract" => PbsType::Contract(type_name.clone()), - "error" => PbsType::ErrorType(type_name.clone()), - _ => PbsType::Void, - }; - if let Some(list) = self.module_symbols.type_symbols.symbols.get_mut(&n.name) { - if let Some(sym) = list.first_mut() { - sym.ty = Some(ty.clone()); - } - } - - // Resolve constructors - let mut ctors = HashMap::new(); - - // Default constructor: TypeName(...) - if n.type_kind == "struct" { - let mut params = Vec::new(); - for p in &n.params { - let p_ty = self.resolve_type_node(arena, p.ty); - params.push(p_ty); - } - let default_ctor_ty = PbsType::Function { - params, - return_type: Box::new(ty.clone()), - }; - ctors.insert(type_name.clone(), default_ctor_ty); - } - - for ctor in &n.constructors { - if let NodeKind::ConstructorDecl(ctor) = arena.kind(*ctor) { - let mut params = Vec::new(); - for p in &ctor.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let ctor_ty = PbsType::Function { - params, - return_type: Box::new(ty.clone()), - }; - ctors.insert(self.interner.resolve(ctor.name).to_string(), ctor_ty); - } - } - self.struct_constructors.insert(type_name.clone(), ctors); - - // Resolve methods - let mut methods = HashMap::new(); - if let Some(body_node) = n.body { - if let NodeKind::TypeBody(body) = arena.kind(body_node) { - // Dentro do contexto do tipo, habilitar `this` - let prev_ctx = self.current_type_context.clone(); - self.current_type_context = Some(type_name.clone()); - for m in &body.methods { - match arena.kind(*m) { - NodeKind::ServiceFnSig(sig) => { - let mut params = Vec::new(); - for p in &sig.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let m_ty = PbsType::Function { - params, - return_type: Box::new(self.resolve_type_node(arena, sig.ret)), - }; - methods.insert(self.interner.resolve(sig.name).to_string(), m_ty); - } - NodeKind::FnDecl(fn_decl) => { - let mut params = Vec::new(); - for p in &fn_decl.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let ret_ty = if let Some(ret) = fn_decl.ret { - self.resolve_type_node(arena, ret) - } else { - PbsType::Void - }; - let m_ty = PbsType::Function { params, return_type: Box::new(ret_ty) }; - methods.insert(self.interner.resolve(fn_decl.name).to_string(), m_ty); - } - _ => {} - } - } - self.current_type_context = prev_ctx; - } - } - self.struct_methods.insert(type_name, methods); - // Coleta fields declarados no cabeçalho do struct para acesso por instância (p.x) - if n.type_kind == "struct" { - let mut fields = HashMap::new(); - for p in &n.params { - let fty = self.resolve_type_node(arena, p.ty); - fields.insert(self.interner.resolve(p.name).to_string(), fty); - } - self.struct_fields.insert(self.interner.resolve(n.name).to_string(), fields); - } - } - _ => {} - } - } - } - - fn check_node(&mut self, arena: &AstArena, node: NodeId) -> PbsType { - match arena.kind(node) { - NodeKind::FnDecl(n) => { - self.check_fn_decl(arena, node, n); - PbsType::Void - } - NodeKind::ServiceDecl(n) => { - // Verifica membros do service, incluindo funções com corpo - for m in &n.members { - match arena.kind(*m) { - NodeKind::ServiceFnSig(sig) => { - // Registrar tipo da função no contexto do service - let mut params = Vec::new(); - for p in &sig.params { - params.push(self.resolve_type_node(arena, p.ty)); - } - let ret_ty = self.resolve_type_node(arena, sig.ret); - // Não armazenamos tabela de métodos de service ainda; apenas validação superficial - let _ = (params, ret_ty); - } - NodeKind::ServiceFnDecl(decl) => { - // Checagem semelhante a FnDecl: escopo próprio, parâmetros locais e tipo de retorno - let mut params_tys = Vec::new(); - for p in &decl.params { - params_tys.push(self.resolve_type_node(arena, p.ty)); - } - let ret_ty = self.resolve_type_node(arena, decl.ret); - - // Entra em escopo, define parâmetros e checa o corpo - self.enter_scope(); - for (i, p) in decl.params.iter().enumerate() { - let ty = params_tys.get(i).cloned().unwrap_or(PbsType::Void); - self.define_local(self.interner.resolve(p.name), ty, false); - } - let prev_ret = self.current_return_type.clone(); - self.current_return_type = Some(ret_ty.clone()); - // Corpo do método do service é um Block; percorremos seus statements - self.check_node(arena, decl.body); - self.current_return_type = prev_ret; - self.exit_scope(); - } - _ => {} - } - } - PbsType::Void - } - NodeKind::TypeDecl(n) => { - self.check_type_decl(arena, node, n); - PbsType::Void - } - NodeKind::ConstructorDecl(n) => { - self.check_constructor_decl(arena, node, n); - PbsType::Void - } - NodeKind::ConstantDecl(n) => self.check_node(arena, n.value), - NodeKind::Block(n) => self.check_block(arena, n), - NodeKind::LetStmt(n) => { - self.check_let_stmt(arena, node, n); - PbsType::Void - } - NodeKind::ExprStmt(n) => { - self.check_node(arena, n.expr); - PbsType::Void - } - NodeKind::ReturnStmt(n) => { - let ret_ty = if let Some(expr) = n.expr { - self.check_node(arena, expr) - } else { - PbsType::Void - }; - if let Some(expected) = self.current_return_type.clone() { - if !self.is_assignable(&expected, &ret_ty) { - self.error_type_mismatch(&expected, &ret_ty, arena.span(node)); - } - } - PbsType::Void - } - NodeKind::IntLit(_) => PbsType::Int, - NodeKind::FloatLit(_) => PbsType::Float, - NodeKind::BoundedLit(_) => PbsType::Bounded, - NodeKind::StringLit(_) => PbsType::String, - NodeKind::Ident(n) => self.check_identifier(arena, node, n), - NodeKind::Call(n) => self.check_call(arena, node, n), - NodeKind::Unary(n) => self.check_unary(arena, node, n), - NodeKind::Binary(n) => self.check_binary(arena, node, n), - NodeKind::Cast(n) => self.check_cast(arena, node, n), - NodeKind::IfExpr(n) => self.check_if_expr(arena, node, n), - NodeKind::WhenExpr(n) => self.check_when_expr(arena, node, n), - NodeKind::Alloc(n) => self.check_alloc(arena, node, n), - NodeKind::Mutate(n) => { - self.check_hip(arena, arena.span(node), n.target, n.binding, n.body, true) - } - NodeKind::Borrow(n) => { - self.check_hip(arena, arena.span(node), n.target, n.binding, n.body, false) - } - NodeKind::Peek(n) => { - self.check_hip(arena, arena.span(node), n.target, n.binding, n.body, false) - } - NodeKind::MemberAccess(n) => self.check_member_access(arena, node, n), - _ => PbsType::Void, - } - } - - fn check_member_access( - &mut self, - arena: &AstArena, - node: NodeId, - n: &MemberAccessNodeArena, - ) -> PbsType { - let member_str = self.interner.resolve(n.member); - if let NodeKind::Ident(id) = arena.kind(n.object) { - let name_str = self.interner.resolve(id.name); - // Check if it's a local first - let is_local = self.scopes.iter().any(|s| s.contains_key(name_str)); - - if !is_local { - // Check if it's a known host contract - let sym_opt = self.module_symbols.type_symbols.get(id.name) - .or_else(|| self.imported_symbols.type_symbols.get(id.name)); - - if let Some(sym) = sym_opt { - // Permitir consulta ao registry para contratos host (e também aceitar LogHost por nome) - if sym.kind == SymbolKind::Contract && (sym.is_host || name_str == "LogHost") { - // Check if the method exists in registry - if let Some(method) = self.contract_registry.get_method(name_str, member_str) { - return PbsType::Function { - params: method.params.clone(), - return_type: Box::new(method.return_type.clone()), - }; - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_UNDEFINED".to_string(), - message: format!("Method '{}' not found on host contract '{}'", member_str, name_str), - span: arena.span(node), - related: Vec::new(), - }); - } - return PbsType::Void; - } - - // v0: Suporte explícito às constantes de Color - if sym.kind == SymbolKind::Struct && name_str == "Color" { - match member_str { - "BLACK" | "WHITE" | "RED" | "GREEN" | "BLUE" => { - return PbsType::Struct("Color".to_string()); - } - _ => {} - } - } - } - - // Builtin Struct Associated Members (Static/Constants) - if let Some(constants) = self.struct_constants.get(name_str) { - if let Some(ty) = constants.get(member_str) { - return ty.clone(); - } - } - - // Fallback for constructors if used as Type.alias(...) - if let Some(ctors) = self.struct_constructors.get(name_str) { - if let Some(ty) = ctors.get(member_str) { - return ty.clone(); - } - } - - // Fallback for static methods if used as Type.method(...) - if let Some(methods) = self.struct_methods.get(name_str) { - if let Some(ty) = methods.get(member_str) { - return ty.clone(); - } - } - } - } - - let obj_ty = self.check_node(arena, n.object); - if let PbsType::Struct(ref name) = obj_ty { - // PR-Log+Service: dar precedência a MÉTODOS sobre fields quando há colisão de nome - // Ex.: Color possui field "raw: bounded" e método "raw(self: Color): bounded"; - // ao fazer c.raw(), deve resolver para o método. - if let Some(methods) = self.struct_methods.get(name) { - if let Some(ty) = methods.get(member_str) { - // Se for chamada de método em instância, o primeiro parâmetro (self) é implícito - if let PbsType::Function { mut params, return_type } = ty.clone() { - if !params.is_empty() { - params.remove(0); - return PbsType::Function { params, return_type }; - } - } - return ty.clone(); - } - } - // Depois, tentar resolver como acesso a field de struct conhecido no contexto atual - if let Some(fields) = &self.current_struct_fields { - if let Some(ty) = fields.get(member_str) { - return ty.clone(); - } - } - // Por fim, tabela global de fields coletados do cabeçalho do struct - if let Some(fields) = self.struct_fields.get(name) { - if let Some(ty) = fields.get(member_str) { - return ty.clone(); - } - } - } - - match obj_ty { - PbsType::Struct(ref name) => { - match name.as_str() { - "Color" => { - match member_str { - "value" => return PbsType::Bounded, - "raw" => return PbsType::Function { - params: vec![], // self is implicit - return_type: Box::new(PbsType::Bounded), - }, - _ => {} - } - } - // New `Button` mirrors legacy `ButtonState` for field typing - "Button" | "ButtonState" => { - match member_str { - "pressed" | "released" | "down" => return PbsType::Bool, - "hold_frames" => return PbsType::Bounded, - _ => {} - } - } - "Pad" => { - match member_str { - "up" | "down" | "left" | "right" | "a" | "b" | "x" | "y" | "l" | "r" | "start" | "select" => { - return PbsType::Struct("Button".to_string()); - } - "any" => { - return PbsType::Function { - params: vec![], // self is implicit - return_type: Box::new(PbsType::Bool), - }; - } - _ => {} - } - } - "Touch" => { - match member_str { - "f" => return PbsType::Struct("Button".to_string()), - "x" | "y" => return PbsType::Int, - _ => {} - } - } - _ => {} - } - } - _ => {} - } - - if obj_ty != PbsType::Void { - let msg = format!("Member '{}' not found on type {:?}", member_str, obj_ty); - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_RESOLVE_UNDEFINED".to_string(), - message: msg, - span: arena.span(node), - related: Vec::new(), - }); - } - PbsType::Void - } - - fn check_alloc(&mut self, arena: &AstArena, _node: NodeId, n: &AllocNodeArena) -> PbsType { - let ty = self.resolve_type_node(arena, n.ty); - // For v0, alloc returns something that can be used with mutate/borrow/peek. - // We'll call it a gate to the type. - PbsType::Contract(format!("Gate<{}>", ty)) // Approximation for v0 - } - - fn check_hip( - &mut self, - arena: &AstArena, - _span: Span, - target: NodeId, - binding: NameId, - body: NodeId, - is_mut: bool, - ) -> PbsType { - let target_ty = self.check_node(arena, target); - // In v0, we assume target is a gate. We bind the inner type to the binding. - let inner_ty = match target_ty { - PbsType::Contract(name) if name.starts_with("Gate<") => { - // Extract type name from Gate - let inner_name = &name[5..name.len()-1]; - match inner_name { - "int" => PbsType::Int, - "float" => PbsType::Float, - "bool" => PbsType::Bool, - "string" => PbsType::String, - _ => PbsType::Void, // Should be PbsType::Struct(inner_name) if we had better info - } - } - _ => PbsType::Void - }; - - self.enter_scope(); - let binding_str = self.interner.resolve(binding); - self.define_local(binding_str, inner_ty, is_mut); - let body_ty = self.check_node(arena, body); - self.exit_scope(); - body_ty - } - - fn check_fn_decl(&mut self, arena: &AstArena, id: NodeId, n: &FnDeclNodeArena) { - let sig = self.module_symbols.value_symbols.get(n.name).and_then(|s| s.ty.clone()); - if let Some(PbsType::Function { params, return_type }) = sig { - self.enter_scope(); - self.current_return_type = Some(*return_type.clone()); - - for (param, ty) in n.params.iter().zip(params.iter()) { - self.define_local(self.interner.resolve(param.name), ty.clone(), false); - } - - let _body_ty = self.check_node(arena, n.body); - - // Return path validation - if !self.all_paths_return(arena, n.body) { - if n.else_fallback.is_some() { - // OK - } else if matches!(*return_type, PbsType::Optional(_)) { - // Implicit return none is allowed for optional - } else if matches!(*return_type, PbsType::Void) { - // Void doesn't strictly need return - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_RETURN_PATH".to_string(), - message: format!( - "Function '{}' must return a value of type {}", - self.interner.resolve(n.name), - return_type - ), - span: arena.span(id), - related: Vec::new(), - }); - } - } - - if let Some(fallback) = n.else_fallback { - self.check_node(arena, fallback); - } - - self.current_return_type = None; - self.exit_scope(); - } - } - - fn check_block(&mut self, arena: &AstArena, n: &BlockNodeArena) -> PbsType { - self.enter_scope(); - for stmt in &n.stmts { - self.check_node(arena, *stmt); - } - let tail_ty = if let Some(tail) = n.tail { - self.check_node(arena, tail) - } else { - PbsType::Void - }; - self.exit_scope(); - tail_ty - } - - fn check_let_stmt(&mut self, arena: &AstArena, id: NodeId, n: &LetStmtNodeArena) { - let init_ty = self.check_node(arena, n.init); - let declared_ty = n.ty.map(|t| self.resolve_type_node(arena, t)); - - let final_ty = if let Some(dty) = declared_ty { - if !self.is_assignable(&dty, &init_ty) { - self.error_type_mismatch(&dty, &init_ty, arena.span(id)); - } - dty - } else { - init_ty - }; - - self.define_local(self.interner.resolve(n.name), final_ty, n.is_mut); - } - - fn check_identifier(&mut self, _arena: &AstArena, _id: NodeId, n: &IdentNodeArena) -> PbsType { - let name_str = self.interner.resolve(n.name); - // Check locals - for scope in self.scopes.iter().rev() { - if let Some(ty) = scope.get(name_str) { - return ty.clone(); - } - } - - // Check implicit struct fields in method context - if let Some(ty) = self.resolve_ident_override(name_str) { - return ty; - } - - // Check module symbols - if let Some(sym) = self.module_symbols.value_symbols.get(n.name) { - if let Some(ty) = &sym.ty { - return ty.clone(); - } - } - - // Check imported symbols - if let Some(sym) = self.imported_symbols.value_symbols.get(n.name) { - if let Some(ty) = &sym.ty { - return ty.clone(); - } - } - - // Fallback for default constructor: check if it's a struct name - if let Some(ctors) = self.struct_constructors.get(name_str) { - if let Some(ty) = ctors.get(name_str) { - return ty.clone(); - } - } - - // Built-ins (some, none, ok, err might be handled as calls or special keywords) - // For v0, let's treat none as a special literal or identifier - if name_str == "none" { - return PbsType::None; - } - if name_str == "true" || name_str == "false" { - return PbsType::Bool; - } - - // Error should have been caught by Resolver, but we return Void - PbsType::Void - } - - fn check_call(&mut self, arena: &AstArena, node: NodeId, n: &CallNodeArena) -> PbsType { - let callee_ty = self.check_node(arena, n.callee); - - // Handle special built-in "constructors" - if let NodeKind::Ident(id) = arena.kind(n.callee) { - match self.interner.resolve(id.name) { - "some" => { - if n.args.len() == 1 { - let inner_ty = self.check_node(arena, n.args[0]); - return PbsType::Optional(Box::new(inner_ty)); - } - } - "ok" => { - if n.args.len() == 1 { - let inner_ty = self.check_node(arena, n.args[0]); - return PbsType::Result(Box::new(inner_ty), Box::new(PbsType::Void)); - } - } - "err" => { - if n.args.len() == 1 { - let inner_ty = self.check_node(arena, n.args[0]); - return PbsType::Result(Box::new(PbsType::Void), Box::new(inner_ty)); - } - } - _ => {} - } - } - - match callee_ty { - PbsType::Function { params, return_type } => { - if n.args.len() != params.len() { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: format!("Expected {} arguments, found {}", params.len(), n.args.len()), - span: arena.span(node), - related: Vec::new(), - }); - } else { - for (i, arg) in n.args.iter().enumerate() { - let arg_ty = self.check_node(arena, *arg); - if !self.is_assignable(¶ms[i], &arg_ty) { - self.error_type_mismatch(¶ms[i], &arg_ty, arena.span(*arg)); - } - } - } - *return_type - } - _ => { - if callee_ty != PbsType::Void { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: format!("Type {} is not callable", callee_ty), - span: arena.span(node), - related: Vec::new(), - }); - } - PbsType::Void - } - } - } - - fn check_unary(&mut self, arena: &AstArena, node: NodeId, n: &UnaryNodeArena) -> PbsType { - let expr_ty = self.check_node(arena, n.expr); - match n.op.as_str() { - "-" => { - if expr_ty == PbsType::Int || expr_ty == PbsType::Float { - expr_ty - } else { - self.error_type_mismatch(&PbsType::Int, &expr_ty, arena.span(node)); - PbsType::Void - } - } - "!" => { - if expr_ty == PbsType::Bool { - PbsType::Bool - } else { - self.error_type_mismatch(&PbsType::Bool, &expr_ty, arena.span(node)); - PbsType::Void - } - } - _ => PbsType::Void, - } - } - - fn check_binary(&mut self, arena: &AstArena, node: NodeId, n: &BinaryNodeArena) -> PbsType { - let left_ty = self.check_node(arena, n.left); - let right_ty = self.check_node(arena, n.right); - - match n.op.as_str() { - "+" | "-" | "*" | "/" | "%" => { - if (left_ty == PbsType::Int || left_ty == PbsType::Float) && left_ty == right_ty { - left_ty - } else { - self.error_type_mismatch(&left_ty, &right_ty, arena.span(node)); - PbsType::Void - } - } - "==" | "!=" => { - if left_ty == right_ty { - PbsType::Bool - } else { - self.error_type_mismatch(&left_ty, &right_ty, arena.span(node)); - PbsType::Bool - } - } - "<" | "<=" | ">" | ">=" => { - if (left_ty == PbsType::Int || left_ty == PbsType::Float) && left_ty == right_ty { - PbsType::Bool - } else { - self.error_type_mismatch(&left_ty, &right_ty, arena.span(node)); - PbsType::Bool - } - } - "&&" | "||" => { - if left_ty == PbsType::Bool && right_ty == PbsType::Bool { - PbsType::Bool - } else { - self.error_type_mismatch(&PbsType::Bool, &left_ty, arena.span(n.left)); - self.error_type_mismatch(&PbsType::Bool, &right_ty, arena.span(n.right)); - PbsType::Bool - } - } - _ => PbsType::Void, - } - } - - fn check_cast(&mut self, arena: &AstArena, _node: NodeId, n: &CastNodeArena) -> PbsType { - let _expr_ty = self.check_node(arena, n.expr); - let target_ty = self.resolve_type_node(arena, n.ty); - // Minimal cast validation for v0 - target_ty - } - - fn check_if_expr(&mut self, arena: &AstArena, node: NodeId, n: &IfExprNodeArena) -> PbsType { - let cond_ty = self.check_node(arena, n.cond); - if cond_ty != PbsType::Bool { - self.error_type_mismatch(&PbsType::Bool, &cond_ty, arena.span(n.cond)); - } - let then_ty = self.check_node(arena, n.then_block); - if let Some(else_block) = n.else_block { - let else_ty = self.check_node(arena, else_block); - if then_ty != else_ty { - self.error_type_mismatch(&then_ty, &else_ty, arena.span(node)); - } - then_ty - } else { - PbsType::Void - } - } - - fn check_when_expr(&mut self, arena: &AstArena, _node: NodeId, n: &WhenExprNodeArena) -> PbsType { - let mut first_ty = None; - for arm in &n.arms { - if let NodeKind::WhenArm(arm_node) = arena.kind(*arm) { - let cond_ty = self.check_node(arena, arm_node.cond); - if cond_ty != PbsType::Bool { - self.error_type_mismatch(&PbsType::Bool, &cond_ty, arena.span(arm_node.cond)); - } - let body_ty = self.check_node(arena, arm_node.body); - if first_ty.is_none() { - first_ty = Some(body_ty); - } else if let Some(fty) = &first_ty { - if *fty != body_ty { - self.error_type_mismatch(fty, &body_ty, arena.span(arm_node.body)); - } - } - } - } - first_ty.unwrap_or(PbsType::Void) - } - - fn check_type_decl(&mut self, arena: &AstArena, _id: NodeId, n: &TypeDeclNodeArena) { - for constructor in &n.constructors { - if let NodeKind::ConstructorDecl(ctor) = arena.kind(*constructor) { - self.check_constructor_decl(arena, *constructor, ctor); - } - } - - let type_name = self.interner.resolve(n.name).to_string(); - let struct_ty = PbsType::Struct(type_name.clone()); - let mut constants_scope = HashMap::new(); - if let Some(ctors) = self.struct_constructors.get(&type_name) { - for (name, ty) in ctors { - constants_scope.insert(name.clone(), ty.clone()); - } - } - - let mut constants_map = HashMap::new(); - self.scopes.push(constants_scope); - for constant_id in &n.constants { - if let NodeKind::ConstantDecl(constant) = arena.kind(*constant_id) { - let val_ty = self.check_node(arena, constant.value); - if !self.is_assignable(&struct_ty, &val_ty) { - self.error_type_mismatch(&struct_ty, &val_ty, arena.span(*constant_id)); - } - constants_map.insert( - self.interner.resolve(constant.name).to_string(), - struct_ty.clone(), - ); - } - } - self.scopes.pop(); - self.struct_constants.insert(type_name, constants_map); - - if let Some(body) = n.body { - // Preparar contexto de tipo e fields para métodos - let type_name = self.interner.resolve(n.name).to_string(); - let prev_ctx = self.current_type_context.clone(); - self.current_type_context = Some(type_name.clone()); - - // Coletar fields do cabeçalho e membros do corpo - let mut fields: HashMap = HashMap::new(); - for p in &n.params { - let ty = self.resolve_type_node(arena, p.ty); - fields.insert(self.interner.resolve(p.name).to_string(), ty); - } - if let NodeKind::TypeBody(tb) = arena.kind(body) { - for m in &tb.members { - let ty = self.resolve_type_node(arena, m.ty); - fields.insert(self.interner.resolve(m.name).to_string(), ty); - } - } - let prev_fields = self.current_struct_fields.replace(fields); - - self.check_node(arena, body); - - self.current_struct_fields = prev_fields; - self.current_type_context = prev_ctx; - } - } - - fn check_constructor_decl( - &mut self, - arena: &AstArena, - _id: NodeId, - n: &ConstructorDeclNodeArena, - ) { - self.enter_scope(); - for param in &n.params { - let ty = self.resolve_type_node(arena, param.ty); - self.define_local(self.interner.resolve(param.name), ty, false); - } - for init in &n.initializers { - self.check_node(arena, *init); - } - self.check_node(arena, n.body); - self.exit_scope(); - } - - fn resolve_type_node(&mut self, arena: &AstArena, node: NodeId) -> PbsType { - match arena.kind(node) { - NodeKind::TypeName(tn) => { - let name_str = self.interner.resolve(tn.name); - match name_str { - "int" => PbsType::Int, - "float" => PbsType::Float, - "bool" => PbsType::Bool, - "string" => PbsType::String, - "void" => PbsType::Void, - "bounded" => PbsType::Bounded, - "this" => { - if let Some(ctx) = &self.current_type_context { - PbsType::Struct(ctx.clone()) - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_THIS_OUT_OF_CONTEXT".to_string(), - message: "`this` used outside of type context".to_string(), - span: arena.span(node), - related: Vec::new(), - }); - PbsType::Void - } - } - "Color" | "ButtonState" | "Pad" | "Touch" => PbsType::Struct(name_str.to_string()), - _ => { - // Look up in symbol table - if let Some(sym) = self.lookup_type(tn.name) { - match sym.kind { - SymbolKind::Struct => PbsType::Struct(name_str.to_string()), - SymbolKind::Service => PbsType::Service(name_str.to_string()), - SymbolKind::Contract => PbsType::Contract(name_str.to_string()), - SymbolKind::ErrorType => PbsType::ErrorType(name_str.to_string()), - _ => PbsType::Void, - } - } else { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_UNKNOWN_TYPE".to_string(), - message: format!("Unknown type: {}", name_str), - span: arena.span(node), - related: Vec::new(), - }); - PbsType::Void - } - } - } - } - NodeKind::TypeApp(ta) => match self.interner.resolve(ta.base) { - "optional" => { - if ta.args.len() == 1 { - PbsType::Optional(Box::new(self.resolve_type_node(arena, ta.args[0]))) - } else { - PbsType::Void - } - } - "result" => { - if ta.args.len() == 2 { - PbsType::Result( - Box::new(self.resolve_type_node(arena, ta.args[0])), - Box::new(self.resolve_type_node(arena, ta.args[1])), - ) - } else { - PbsType::Void - } - } - _ => PbsType::Void, - }, - _ => PbsType::Void, - } - } - - // Override de resolução de identificadores: permitir acesso direto a fields no contexto de método - fn resolve_ident_override(&self, name: &str) -> Option { - if let Some(fields) = &self.current_struct_fields { - if let Some(ty) = fields.get(name) { - return Some(ty.clone()); - } - } - None - } - - fn lookup_type(&self, name: NameId) -> Option<&Symbol> { - if let Some(sym) = self.module_symbols.type_symbols.get(name) { - return Some(sym); - } - if let Some(sym) = self.imported_symbols.type_symbols.get(name) { - return Some(sym); - } - None - } - - fn is_assignable(&self, expected: &PbsType, found: &PbsType) -> bool { - if expected == found { - return true; - } - - // Color is basically a bounded (u16) - if matches!(expected, PbsType::Struct(s) if s == "Color") && *found == PbsType::Bounded { - return true; - } - if *expected == PbsType::Bounded && matches!(found, PbsType::Struct(s) if s == "Color") { - return true; - } - - // Allow int as Color/bounded (for compatibility) - if (matches!(expected, PbsType::Struct(s) if s == "Color") || *expected == PbsType::Bounded) && *found == PbsType::Int { - return true; - } - - match (expected, found) { - (PbsType::Optional(_), PbsType::None) => true, - (PbsType::Optional(inner), found) => self.is_assignable(inner, found), - (PbsType::Result(ok_exp, _), PbsType::Result(ok_found, err_found)) if **err_found == PbsType::Void => { - self.is_assignable(ok_exp, ok_found) - } - (PbsType::Result(_, err_exp), PbsType::Result(ok_found, err_found)) if **ok_found == PbsType::Void => { - self.is_assignable(err_exp, err_found) - } - _ => false, - } - } - - fn all_paths_return(&self, arena: &AstArena, node: NodeId) -> bool { - match arena.kind(node) { - NodeKind::ReturnStmt(_) => true, - NodeKind::Block(n) => { - for stmt in &n.stmts { - if self.all_paths_return(arena, *stmt) { - return true; - } - } - if let Some(tail) = n.tail { - return self.all_paths_return(arena, tail); - } - false - } - NodeKind::IfExpr(n) => { - let then_returns = self.all_paths_return(arena, n.then_block); - let else_returns = n - .else_block - .map(|b| self.all_paths_return(arena, b)) - .unwrap_or(false); - then_returns && else_returns - } - // For simplicity, we don't assume When returns unless all arms do - _ => false, - } - } - - fn enter_scope(&mut self) { - self.scopes.push(HashMap::new()); - self.mut_bindings.push(HashMap::new()); - } - - fn exit_scope(&mut self) { - self.scopes.pop(); - self.mut_bindings.pop(); - } - - fn define_local(&mut self, name: &str, ty: PbsType, is_mut: bool) { - if let Some(scope) = self.scopes.last_mut() { - scope.insert(name.to_string(), ty); - } - if let Some(muts) = self.mut_bindings.last_mut() { - muts.insert(name.to_string(), is_mut); - } - } - - fn error_type_mismatch(&mut self, expected: &PbsType, found: &PbsType, span: Span) { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: "E_TYPE_MISMATCH".to_string(), - message: format!("Type mismatch: expected {}, found {}", expected, found), - span, - related: Vec::new(), - }); - } -} - -#[cfg(test)] -mod tests { - use crate::common::files::FileManager; - use crate::frontends::pbs::PbsFrontend; - use crate::frontends::Frontend; - use std::fs; - - fn check_code(code: &str) -> Result<(), String> { - let mut file_manager = FileManager::new(); - let temp_dir = tempfile::tempdir().unwrap(); - let file_path = temp_dir.path().join("test.pbs"); - - // Inject industrial base definitions for tests - let mut full_code = String::new(); - if !code.contains("struct Color") { - full_code.push_str("declare struct Color(raw: bounded) [[ BLACK: Color(0b), WHITE: Color(65535b), RED: Color(63488b), GREEN: Color(2016b), BLUE: Color(31b) ]] { fn raw(self: Color): bounded; fn rgb(r: int, g: int, b: int): Color; } \n"); - } - if !code.contains("struct ButtonState") { - full_code.push_str("declare struct ButtonState(pressed: bool, released: bool, down: bool, hold_frames: bounded) \n"); - } - if !code.contains("struct Pad") { - full_code.push_str("declare struct Pad(up: ButtonState, down: ButtonState, left: ButtonState, right: ButtonState, a: ButtonState, b: ButtonState, x: ButtonState, y: ButtonState, l: ButtonState, r: ButtonState, start: ButtonState, select: ButtonState) { fn any(self: Pad): bool; } \n"); - } - full_code.push_str(code); - - fs::write(&file_path, full_code).unwrap(); - - let frontend = PbsFrontend; - match frontend.compile_to_ir(&file_path, &mut file_manager) { - Ok(_) => Ok(()), - Err(bundle) => { - let mut errors = Vec::new(); - let full_src = fs::read_to_string(&file_path).unwrap(); - let mut arounds = Vec::new(); - for diag in bundle.diagnostics { - let code = diag.code; - let span = diag.span; - errors.push(format!("{}: {} @ {}..{}", code, diag.message, span.start, span.end)); - let s = span.start as usize; - let e = span.end as usize; - let a = s.saturating_sub(30); - let b = (e + 30).min(full_src.len()); - arounds.push(format!("[{}..{}]: >>>{}<<<", s, e, &full_src[a..b].replace('\n', "\\n"))); - } - let err_msg = errors.join(", "); - println!("Compilation failed: {}\n{}", err_msg, arounds.join("\n")); - Err(err_msg) - } - } - } - - #[test] - fn test_type_mismatch_let() { - let code = "fn main() { let x: int = \"hello\"; }"; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_type_mismatch_return() { - let code = "fn main(): int { return \"hello\"; }"; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_type_mismatch_call() { - let code = " - fn foo(a: int) {} - fn main() { - foo(\"hello\"); - } - "; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_missing_return_path() { - let code = "fn foo(): int { if (true) { return 1; } }"; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_RETURN_PATH")); - } - - #[test] - fn test_implicit_none_optional() { - let code = "fn foo(): optional { if (true) { return some(1); } }"; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); // Implicit none allowed for optional - } - - #[test] - fn test_valid_optional_assignment() { - let code = "fn main() { let x: optional = none; let y: optional = some(10); }"; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_valid_result_usage() { - let code = " - fn foo(): result { - if (true) { - return ok(10); - } else { - return err(\"error\"); - } - } - "; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_unknown_type() { - let code = "fn main() { let x: UnknownType = 10; }"; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_UNKNOWN_TYPE")); - } - - #[test] - fn test_invalid_host_method() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.invalidMethod(); - } - "; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_RESOLVE_UNDEFINED")); - } - - #[test] - fn test_valid_host_method() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.clear(Color.WHITE); - } - "; - let res = check_code(code); - assert!(res.is_ok()); - } - - #[test] - fn test_host_method_arity_mismatch() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.clear(0, 1); - } - "; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_host_method_type_mismatch() { - let code = " - declare contract Gfx host {} - fn main() { - Gfx.clear(\"red\"); - } - "; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_void_return_ok() { - let code = "fn main() { return; }"; - let res = check_code(code); - assert!(res.is_ok()); - } - - #[test] - fn test_binary_op_mismatch() { - let code = "fn main() { let x = 1 + \"hello\"; }"; - let res = check_code(code); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("E_TYPE_MISMATCH")); - } - - #[test] - fn test_struct_type_usage() { - let code = " - declare struct Point(x: int, y: int) - fn foo(p: Point) {} - fn main() { - // Struct literals not in v0, but we can have variables of struct type - } - "; - let res = check_code(code); - assert!(res.is_ok()); - } - - #[test] - fn test_service_type_usage() { - let code = " - pub service MyService { - fn hello(name: string): void - } - fn foo(s: MyService) {} - "; - let res = check_code(code); - assert!(res.is_ok()); - } - - #[test] - fn test_hip_invariant_violation_return() { - let code = " - fn test_hip(g: int) { - mutate g as x { - return; - } - } - "; - let res = check_code(code); - assert!(res.is_err()); - let err = res.unwrap_err(); - assert!(err.contains("Core IR Invariant Violation")); - assert!(err.contains("non-empty HIP stack")); - } - - #[test] - fn test_prelude_color() { - let code = " - declare contract Gfx host {} - fn main() { - let c: Color = Color.WHITE; - Gfx.clear(c); - Gfx.clear(Color.BLACK); - } - "; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_prelude_input_pad() { - let code = " - declare contract Input host {} - fn main() { - let p: Pad = Input.pad(); - if p.any() { - let b: ButtonState = p.a; - if b.down { - // ok - } - } - } - "; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_color_rgb_and_raw() { - let code = " - fn main() { - let c = Color.rgb(255, 0, 0); - let r: bounded = c.raw(); - } - "; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_bounded_literal() { - let code = " - fn main() { - let b: bounded = 255b; - } - "; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - #[test] - fn test_struct_method_with_body_and_call() { - let code = r#" - // struct Color - declare struct Vec2(x: int, y: int) - [ - (x: int, y: int): (x, y) as default - (s: int): (s, s) as square - ] - [[ - ZERO: square(0) - ]] - { - pub fn len(self: this): int { - return x * x + y * y; - } - } - - fn frame(): void { - let zero = Vec2.ZERO; - let zz = zero.len(); - } - "#; - let res = check_code(code); - if let Err(e) = &res { println!("Error: {}", e); } - assert!(res.is_ok()); - } - - // #[test] - // fn test_overload_missing_exact_match() { - // let code = r#" - // fn foo(x: int): void {} - // fn foo(x: string): void {} - // fn main() { foo(true); } - // "#; - // let res = check_code(code); - // assert!(res.is_err()); - // let err = res.unwrap_err(); - // assert!(err.contains("E_OVERLOAD_NOT_FOUND"), "unexpected diagnostics: {}", err); - // } - // - // #[test] - // fn test_overload_ambiguous_exact_match() { - // let code = r#" - // fn foo(x: int): void {} - // fn foo(x: int): void {} - // fn main() { let a: int = 1; foo(a); } - // "#; - // let res = check_code(code); - // assert!(res.is_err()); - // let err = res.unwrap_err(); - // assert!(err.contains("E_OVERLOAD_AMBIGUOUS"), "unexpected diagnostics: {}", err); - // } -} diff --git a/crates/compiler/prometeu-compiler/src/frontends/pbs/types.rs b/crates/compiler/prometeu-compiler/src/frontends/pbs/types.rs deleted file mode 100644 index 8fa7178c..00000000 --- a/crates/compiler/prometeu-compiler/src/frontends/pbs/types.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum PbsType { - Int, - Float, - Bool, - String, - Void, - None, - Bounded, - Optional(Box), - Result(Box, Box), - Struct(String), - Service(String), - Contract(String), - ErrorType(String), - Function { - params: Vec, - return_type: Box, - }, -} - -impl fmt::Display for PbsType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PbsType::Int => write!(f, "int"), - PbsType::Float => write!(f, "float"), - PbsType::Bool => write!(f, "bool"), - PbsType::String => write!(f, "string"), - PbsType::Void => write!(f, "void"), - PbsType::None => write!(f, "none"), - PbsType::Bounded => write!(f, "bounded"), - PbsType::Optional(inner) => write!(f, "optional<{}>", inner), - PbsType::Result(ok, err) => write!(f, "result<{}, {}>", ok, err), - PbsType::Struct(name) => write!(f, "{}", name), - PbsType::Service(name) => write!(f, "{}", name), - PbsType::Contract(name) => write!(f, "{}", name), - PbsType::ErrorType(name) => write!(f, "{}", name), - PbsType::Function { params, return_type } => { - write!(f, "fn(")?; - for (i, param) in params.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", param)?; - } - write!(f, ") -> {}", return_type) - } - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/block.rs b/crates/compiler/prometeu-compiler/src/ir_core/block.rs deleted file mode 100644 index 9af1f6e8..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/block.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::instr::Instr; -use super::terminator::Terminator; -use serde::{Deserialize, Serialize}; - -/// A basic block in a function's control flow graph. -/// Contains a sequence of instructions and ends with a terminator. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Block { - pub id: u32, - pub instrs: Vec, - pub terminator: Terminator, -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/const_pool.rs b/crates/compiler/prometeu-compiler/src/ir_core/const_pool.rs deleted file mode 100644 index 5c1109c2..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/const_pool.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::ids::ConstId; -use serde::{Deserialize, Serialize}; - -/// Represents a constant value that can be stored in the constant pool. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ConstantValue { - Int(i64), - Float(f64), - String(String), -} - -/// A stable constant pool that handles deduplication and provides IDs. -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -pub struct ConstPool { - pub constants: Vec, -} - -impl ConstPool { - /// Creates a new, empty constant pool. - pub fn new() -> Self { - Self::default() - } - - /// Inserts a value into the pool if it doesn't already exist. - /// Returns the corresponding `ConstId`. - pub fn insert(&mut self, value: ConstantValue) -> ConstId { - if let Some(pos) = self.constants.iter().position(|c| c == &value) { - ConstId(pos as u32) - } else { - let id = self.constants.len() as u32; - self.constants.push(value); - ConstId(id) - } - } - - /// Retrieves a value from the pool by its `ConstId`. - pub fn get(&self, id: ConstId) -> Option<&ConstantValue> { - self.constants.get(id.0 as usize) - } - - pub fn add_int(&mut self, value: i64) -> ConstId { - self.insert(ConstantValue::Int(value)) - } - - pub fn add_float(&mut self, value: f64) -> ConstId { - self.insert(ConstantValue::Float(value)) - } - - pub fn add_string(&mut self, value: String) -> ConstId { - self.insert(ConstantValue::String(value)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_core::ids::ConstId; - - #[test] - fn test_const_pool_deduplication() { - let mut pool = ConstPool::new(); - - let id1 = pool.insert(ConstantValue::Int(42)); - let id2 = pool.insert(ConstantValue::String("hello".to_string())); - let id3 = pool.insert(ConstantValue::Int(42)); - - assert_eq!(id1, id3); - assert_ne!(id1, id2); - assert_eq!(pool.constants.len(), 2); - } - - #[test] - fn test_const_pool_deterministic_assignment() { - let mut pool = ConstPool::new(); - - let id0 = pool.insert(ConstantValue::Int(10)); - let id1 = pool.insert(ConstantValue::Int(20)); - let id2 = pool.insert(ConstantValue::Int(30)); - - assert_eq!(id0, ConstId(0)); - assert_eq!(id1, ConstId(1)); - assert_eq!(id2, ConstId(2)); - } - - #[test] - fn test_const_pool_serialization() { - let mut pool = ConstPool::new(); - pool.insert(ConstantValue::Int(42)); - pool.insert(ConstantValue::String("test".to_string())); - pool.insert(ConstantValue::Float(3.14)); - - let json = serde_json::to_string_pretty(&pool).unwrap(); - - assert!(json.contains("\"Int\": 42")); - assert!(json.contains("\"String\": \"test\"")); - assert!(json.contains("\"Float\": 3.14")); - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/function.rs b/crates/compiler/prometeu-compiler/src/ir_core/function.rs deleted file mode 100644 index 795ee8d2..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/function.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::block::Block; -use super::ids::{FunctionId, SigId}; -use super::types::Type; -use serde::{Deserialize, Serialize}; - -use std::collections::HashMap; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Param { - pub name: String, - pub ty: Type, -} - -/// A function within a module, composed of basic blocks forming a CFG. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Function { - pub id: FunctionId, - pub name: String, - #[serde(skip)] - pub sig: SigId, - pub params: Vec, - pub return_type: Type, - pub blocks: Vec, - #[serde(default)] - pub local_types: HashMap, - - pub param_slots: u16, - pub local_slots: u16, - pub return_slots: u16, -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/ids.rs b/crates/compiler/prometeu-compiler/src/ir_core/ids.rs deleted file mode 100644 index df78783e..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/ids.rs +++ /dev/null @@ -1,31 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Unique identifier for a function within a program. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct FunctionId(pub u32); - -/// Unique identifier for a constant value. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct ConstId(pub u32); - -/// Unique identifier for a type. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct TypeId(pub u32); - -/// Unique identifier for a function signature (params + return type). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct SigId(pub u32); - -/// Unique identifier for a value (usually a local slot). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct ValueId(pub u32); - -/// Unique identifier for a field within a HIP object. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct FieldId(pub u32); diff --git a/crates/compiler/prometeu-compiler/src/ir_core/instr.rs b/crates/compiler/prometeu-compiler/src/ir_core/instr.rs deleted file mode 100644 index 8c7e836a..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/instr.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId, SigId}; -use crate::common::spans::Span; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Instr { - pub kind: InstrKind, - pub span: Option, -} - -impl Instr { - pub fn new(kind: InstrKind, span: Option) -> Self { - Self { kind, span } - } -} - -/// Instructions within a basic block. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum InstrKind { - /// Placeholder for constant loading. - PushConst(ConstId), - /// Push a bounded value (0..0xFFFF). - PushBounded(u32), - /// Placeholder for function calls. - Call(FunctionId, u32), - /// External calls (imports). - /// Carries dependency alias, module path, optional owner (service), base function name, - /// precise signature id, and arg count. - ImportCall { - dep_alias: String, - module_path: String, - owner: Option, - base_name: String, - sig: SigId, - arg_count: u32, - }, - /// Host calls (syscalls). (id, return_slots) - HostCall(u32, u32), - /// Variable access. - GetLocal(u32), - SetLocal(u32), - /// Stack operations. - Pop, - Dup, - /// Arithmetic. - Add, - Sub, - Mul, - Div, - Neg, - /// Logical/Comparison. - Eq, - Neq, - Lt, - Lte, - Gt, - Gte, - And, - Or, - Not, - /// HIP operations. - Alloc { ty: TypeId, slots: u32 }, - BeginPeek { gate: ValueId }, - BeginBorrow { gate: ValueId }, - BeginMutate { gate: ValueId }, - EndPeek, - EndBorrow, - EndMutate, - /// Reads from heap at gate + field. Pops gate, pushes value. - GateLoadField { gate: ValueId, field: FieldId }, - /// Writes to heap at gate + field. Pops gate and value. - GateStoreField { gate: ValueId, field: FieldId, value: ValueId }, - /// Reads from heap at gate + index. - GateLoadIndex { gate: ValueId, index: ValueId }, - /// Writes to heap at gate + index. - GateStoreIndex { gate: ValueId, index: ValueId, value: ValueId }, - Free, -} - -impl From for Instr { - fn from(kind: InstrKind) -> Self { - Self::new(kind, None) - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/mod.rs b/crates/compiler/prometeu-compiler/src/ir_core/mod.rs deleted file mode 100644 index 50b89825..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -pub mod ids; -pub mod const_pool; -pub mod types; -pub mod program; -pub mod module; -pub mod function; -pub mod block; -pub mod instr; -pub mod terminator; -pub mod validate; -pub mod signature; - -pub use block::*; -pub use const_pool::*; -pub use function::*; -pub use ids::*; -pub use instr::*; -pub use module::*; -pub use program::*; -pub use terminator::*; -pub use types::*; -pub use validate::*; -pub use signature::*; - -#[cfg(test)] -mod tests { - use super::*; - use serde_json; - - #[test] - fn test_ir_core_manual_construction() { - let mut const_pool = ConstPool::new(); - const_pool.insert(ConstantValue::String("hello".to_string())); - - let program = Program { - const_pool, - modules: vec![Module { - name: "main".to_string(), - functions: vec![Function { - id: FunctionId(10), - name: "entry".to_string(), - sig: { - let mut i = global_signature_interner().lock().unwrap(); - i.intern(Signature { params: vec![], return_type: Type::Void }) - }, - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::PushConst(ConstId(0))), - Instr::from(InstrKind::Call(FunctionId(11), 0)), - ], - terminator: Terminator::Return, - }], - local_types: std::collections::HashMap::new(), - }], - }], - field_offsets: std::collections::HashMap::new(), - field_types: std::collections::HashMap::new(), - }; - - let json = serde_json::to_string_pretty(&program).unwrap(); - - let expected = r#"{ - "const_pool": { - "constants": [ - { - "String": "hello" - } - ] - }, - "modules": [ - { - "name": "main", - "functions": [ - { - "id": 10, - "name": "entry", - "params": [], - "return_type": "Void", - "blocks": [ - { - "id": 0, - "instrs": [ - { - "kind": { - "PushConst": 0 - }, - "span": null - }, - { - "kind": { - "Call": [ - 11, - 0 - ] - }, - "span": null - } - ], - "terminator": "Return" - } - ], - "local_types": {}, - "param_slots": 0, - "local_slots": 0, - "return_slots": 0 - } - ] - } - ], - "field_offsets": {}, - "field_types": {} -}"#; - assert_eq!(json, expected); - } - - #[test] - fn test_ir_core_ids() { - assert_eq!(serde_json::to_string(&FunctionId(1)).unwrap(), "1"); - assert_eq!(serde_json::to_string(&ConstId(2)).unwrap(), "2"); - assert_eq!(serde_json::to_string(&TypeId(3)).unwrap(), "3"); - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/module.rs b/crates/compiler/prometeu-compiler/src/ir_core/module.rs deleted file mode 100644 index 97b97d29..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/module.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::function::Function; -use serde::{Deserialize, Serialize}; - -/// A module within a program, containing functions and other declarations. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Module { - pub name: String, - pub functions: Vec, -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/program.rs b/crates/compiler/prometeu-compiler/src/ir_core/program.rs deleted file mode 100644 index 011a3aa8..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/program.rs +++ /dev/null @@ -1,16 +0,0 @@ -use super::const_pool::ConstPool; -use super::ids::FieldId; -use super::module::Module; -use super::types::Type; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Program { - pub const_pool: ConstPool, - pub modules: Vec, - #[serde(default)] - pub field_offsets: HashMap, - #[serde(default)] - pub field_types: HashMap, -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/signature.rs b/crates/compiler/prometeu-compiler/src/ir_core/signature.rs deleted file mode 100644 index 52df8c01..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/signature.rs +++ /dev/null @@ -1,366 +0,0 @@ -use crate::ir_core::ids::SigId; -use crate::ir_core::types::Type; -use std::collections::HashMap; -use std::sync::{Mutex, OnceLock}; - -/// Canonical function signature: params + return type. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Signature { - pub params: Vec, - pub return_type: Type, -} - -impl Signature { - /// Stable, deterministic descriptor. Example: - /// fn(int;string)->void - /// fn(array{int;4};optional{string})->result{void;error{E}} - pub fn descriptor(&self) -> String { - let mut s = String::new(); - s.push_str("fn("); - for (i, p) in self.params.iter().enumerate() { - if i > 0 { - s.push(';'); - } - encode_type(p, &mut s); - } - s.push(')'); - s.push_str("->"); - encode_type(&self.return_type, &mut s); - s - } - - /// Parse a descriptor previously produced by `descriptor()` - pub fn from_descriptor(desc: &str) -> Result { - // Expect prefix: fn( ... )-> ... - if !desc.starts_with("fn(") { - return Err("Invalid descriptor: missing fn(".to_string()); - } - let rest = &desc[3..]; - let close = rest.find(')').ok_or_else(|| "Invalid descriptor: missing ')'".to_string())?; - let params_blob = &rest[..close]; - let after = &rest[close + 1..]; - let arrow = after.strip_prefix("->").ok_or_else(|| "Invalid descriptor: missing '->'".to_string())?; - - let params = if params_blob.is_empty() { - Vec::new() - } else { - let mut v = Vec::new(); - for part in split_top_level(params_blob, ';')? { - let (ty, consumed) = decode_type(part)?; - if consumed != part.len() { - return Err("Trailing garbage in parameter".to_string()); - } - v.push(ty); - } - v - }; - - let (return_type, consumed) = decode_type(arrow)?; - if consumed != arrow.len() { - return Err("Trailing garbage after return type".to_string()); - } - - Ok(Signature { params, return_type }) - } -} - -/// Global signature interner. Thread-safe and process-wide for this compiler instance. -pub struct SignatureInterner { - map: HashMap, - rev: Vec, -} - -impl SignatureInterner { - pub fn new() -> Self { - Self { map: HashMap::new(), rev: Vec::new() } - } - - pub fn intern(&mut self, sig: Signature) -> SigId { - if let Some(id) = self.map.get(&sig) { - return *id; - } - let id = SigId(self.rev.len() as u32); - self.rev.push(sig.clone()); - self.map.insert(sig, id); - id - } - - pub fn resolve(&self, id: SigId) -> Option<&Signature> { - self.rev.get(id.0 as usize) - } -} - -static GLOBAL_INTERNER: OnceLock> = OnceLock::new(); - -pub fn global_signature_interner() -> &'static Mutex { - GLOBAL_INTERNER.get_or_init(|| Mutex::new(SignatureInterner::new())) -} - -// ============== -// Encoding/Decoding helpers for `Type` -// Canonical grammar (EBNF-ish): -// Type := "void" | "int" | "bounded" | "float" | "bool" | "string" -// | "struct{" Name "}" | "service{" Name "}" | "contract{" Name "}" -// | "error{" Name "}" | "array{" Type ";" UInt "}" -// | "optional{" Type "}" | "result{" Type ";" Type "}" -// | "fn(" [Type { ";" Type }] ")" "->" Type -// Name := escaped UTF-8 without '}' and ';' (escape via '\') - -fn encode_type(ty: &Type, out: &mut String) { - match ty { - Type::Void => out.push_str("void"), - Type::Int => out.push_str("int"), - Type::Bounded => out.push_str("bounded"), - Type::Float => out.push_str("float"), - Type::Bool => out.push_str("bool"), - Type::String => out.push_str("string"), - Type::Optional(inner) => { - out.push_str("optional{"); - encode_type(inner, out); - out.push('}'); - } - Type::Result(ok, err) => { - out.push_str("result{"); - encode_type(ok, out); - out.push(';'); - encode_type(err, out); - out.push('}'); - } - Type::Struct(name) => { - out.push_str("struct{"); - encode_name(name, out); - out.push('}'); - } - Type::Service(name) => { - out.push_str("service{"); - encode_name(name, out); - out.push('}'); - } - Type::Contract(name) => { - out.push_str("contract{"); - encode_name(name, out); - out.push('}'); - } - Type::ErrorType(name) => { - out.push_str("error{"); - encode_name(name, out); - out.push('}'); - } - Type::Array(inner, n) => { - out.push_str("array{"); - encode_type(inner, out); - out.push(';'); - out.push_str(&n.to_string()); - out.push('}'); - } - Type::Function { params, return_type } => { - out.push_str("fn("); - for (i, p) in params.iter().enumerate() { - if i > 0 { out.push(';'); } - encode_type(p, out); - } - out.push(')'); - out.push_str("->"); - encode_type(return_type, out); - } - } -} - -fn encode_name(name: &str, out: &mut String) { - for ch in name.chars() { - match ch { - '\\' => out.push_str("\\\\"), - '}' => out.push_str("\\}"), - ';' => out.push_str("\\;"), - _ => out.push(ch), - } - } -} - -fn decode_name(s: &str) -> Result<(String, usize), String> { - let mut out = String::new(); - let mut chars = s.chars().peekable(); - let mut consumed = 0; - while let Some(&c) = chars.peek() { - if c == '}' { break; } - consumed += 1; - chars.next(); - if c == '\\' { - let next = chars.next().ok_or_else(|| "Invalid escape in name".to_string())?; - consumed += 1; - out.push(next); - } else { - out.push(c); - } - } - Ok((out, consumed)) -} - -fn split_top_level(input: &str, sep: char) -> Result, String> { - let mut parts = Vec::new(); - let mut depth_brace = 0i32; - let mut depth_fn = 0i32; // counts '(' nesting for nested fn types - let mut start = 0usize; - for (i, ch) in input.char_indices() { - match ch { - '{' => depth_brace += 1, - '}' => depth_brace -= 1, - '(' => depth_fn += 1, - ')' => depth_fn -= 1, - _ => {} - } - if ch == sep && depth_brace == 0 && depth_fn == 0 { - parts.push(&input[start..i]); - start = i + ch.len_utf8(); - } - } - parts.push(&input[start..]); - if depth_brace != 0 || depth_fn != 0 { - return Err("Unbalanced delimiters".to_string()); - } - Ok(parts) -} - -fn decode_type(s: &str) -> Result<(Type, usize), String> { - // Order matters; check longer keywords first to avoid prefix issues - let keywords = [ - "optional{", - "result{", - "struct{", - "service{", - "contract{", - "error{", - "array{", - "fn(", - "void", - "int", - "bounded", - "float", - "bool", - "string", - ]; - - for kw in keywords { - if s.starts_with(kw) { - match kw { - "void" => return Ok((Type::Void, 4)), - "int" => return Ok((Type::Int, 3)), - "bounded" => return Ok((Type::Bounded, 7)), - "float" => return Ok((Type::Float, 5)), - "bool" => return Ok((Type::Bool, 4)), - "string" => return Ok((Type::String, 6)), - "optional{" => { - let (inner, used) = decode_type(&s[9..])?; - let rest = &s[9 + used..]; - if !rest.starts_with('}') { return Err("Missing '}' for optional".to_string()); } - return Ok((Type::Optional(Box::new(inner)), 9 + used + 1)); - } - "result{" => { - let (ok, used_ok) = decode_type(&s[7..])?; - let rest = &s[7 + used_ok..]; - if !rest.starts_with(';') { return Err("Missing ';' in result".to_string()); } - let (err, used_err) = decode_type(&rest[1..])?; - let rest2 = &rest[1 + used_err..]; - if !rest2.starts_with('}') { return Err("Missing '}' for result".to_string()); } - return Ok((Type::Result(Box::new(ok), Box::new(err)), 7 + used_ok + 1 + used_err + 1)); - } - "struct{" => { - let (name, used) = decode_name(&s[7..])?; - let rest = &s[7 + used..]; - if !rest.starts_with('}') { return Err("Missing '}' for struct".to_string()); } - return Ok((Type::Struct(name), 7 + used + 1)); - } - "service{" => { - let (name, used) = decode_name(&s[8..])?; - let rest = &s[8 + used..]; - if !rest.starts_with('}') { return Err("Missing '}' for service".to_string()); } - return Ok((Type::Service(name), 8 + used + 1)); - } - "contract{" => { - let (name, used) = decode_name(&s[9..])?; - let rest = &s[9 + used..]; - if !rest.starts_with('}') { return Err("Missing '}' for contract".to_string()); } - return Ok((Type::Contract(name), 9 + used + 1)); - } - "error{" => { - let (name, used) = decode_name(&s[6..])?; - let rest = &s[6 + used..]; - if !rest.starts_with('}') { return Err("Missing '}' for error".to_string()); } - return Ok((Type::ErrorType(name), 6 + used + 1)); - } - "array{" => { - let (inner, used) = decode_type(&s[6..])?; - let rest = &s[6 + used..]; - if !rest.starts_with(';') { return Err("Missing ';' in array".to_string()); } - // parse UInt - let rest_num = &rest[1..]; - let mut n_str = String::new(); - let mut consumed = 0usize; - for ch in rest_num.chars() { - if ch.is_ascii_digit() { n_str.push(ch); consumed += 1; } else { break; } - } - if n_str.is_empty() { return Err("Missing array size".to_string()); } - let n: u32 = n_str.parse().map_err(|_| "Invalid array size".to_string())?; - let rest2 = &rest_num[consumed..]; - if !rest2.starts_with('}') { return Err("Missing '}' for array".to_string()); } - return Ok((Type::Array(Box::new(inner), n), 6 + used + 1 + consumed + 1)); - } - "fn(" => { - // parse params until ')' - let after = &s[3..]; - let close = after.find(')').ok_or_else(|| "Missing ')' in fn type".to_string())?; - let params_blob = &after[..close]; - let mut params = Vec::new(); - if !params_blob.is_empty() { - for part in split_top_level(params_blob, ';')? { - let (p, used) = decode_type(part)?; - if used != part.len() { return Err("Trailing data in fn param".to_string()); } - params.push(p); - } - } - let rest = &after[close + 1..]; - if !rest.starts_with("->") { return Err("Missing '->' in fn type".to_string()); } - let (ret, used_ret) = decode_type(&rest[2..])?; - return Ok((Type::Function { params, return_type: Box::new(ret) }, 3 + close + 1 + 2 + used_ret)); - } - _ => {} - } - } - } - Err("Unknown type in descriptor".to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn descriptors_are_different_for_overloads() { - let sig_i = Signature { - params: vec![Type::Int], - return_type: Type::Void, - }; - let sig_s = Signature { - params: vec![Type::String], - return_type: Type::Void, - }; - assert_ne!(sig_i.descriptor(), sig_s.descriptor()); - } - - #[test] - fn descriptor_round_trip_stable() { - let sig = Signature { - params: vec![ - Type::Array(Box::new(Type::Int), 4), - Type::Optional(Box::new(Type::String)), - Type::Result(Box::new(Type::Int), Box::new(Type::ErrorType("E42".into()))), - ], - return_type: Type::Void, - }; - let d1 = sig.descriptor(); - let parsed = Signature::from_descriptor(&d1).expect("parse ok"); - let d2 = parsed.descriptor(); - assert_eq!(d1, d2); - assert_eq!(sig, parsed); - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/terminator.rs b/crates/compiler/prometeu-compiler/src/ir_core/terminator.rs deleted file mode 100644 index 1dcbe342..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/terminator.rs +++ /dev/null @@ -1,16 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Terminators that end a basic block and handle control flow. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum Terminator { - /// Returns from the current function. - Return, - /// Unconditional jump to another block (by index/ID). - Jump(u32), - /// Conditional jump: pops a bool, if false jumps to target, else continues to next block? - /// Actually, in a CFG, we usually have two targets for a conditional jump. - JumpIfFalse { - target: u32, - else_target: u32, - }, -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/types.rs b/crates/compiler/prometeu-compiler/src/ir_core/types.rs deleted file mode 100644 index 053dc43b..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/types.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Type { - Void, - Int, - Bounded, - Float, - Bool, - String, - Optional(Box), - Result(Box, Box), - Struct(String), - Service(String), - Contract(String), - ErrorType(String), - Array(Box, u32), - Function { - params: Vec, - return_type: Box, - }, -} - -impl fmt::Display for Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Type::Void => write!(f, "void"), - Type::Int => write!(f, "int"), - Type::Bounded => write!(f, "bounded"), - Type::Float => write!(f, "float"), - Type::Bool => write!(f, "bool"), - Type::String => write!(f, "string"), - Type::Optional(inner) => write!(f, "optional<{}>", inner), - Type::Result(ok, err) => write!(f, "result<{}, {}>", ok, err), - Type::Struct(name) => write!(f, "{}", name), - Type::Service(name) => write!(f, "{}", name), - Type::Contract(name) => write!(f, "{}", name), - Type::ErrorType(name) => write!(f, "{}", name), - Type::Array(inner, size) => write!(f, "array<{}>[{}]", inner, size), - Type::Function { params, return_type } => { - write!(f, "fn(")?; - for (i, param) in params.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", param)?; - } - write!(f, ") -> {}", return_type) - } - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_core/validate.rs b/crates/compiler/prometeu-compiler/src/ir_core/validate.rs deleted file mode 100644 index 0e4a8e5a..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_core/validate.rs +++ /dev/null @@ -1,362 +0,0 @@ -use super::ids::ValueId; -use super::instr::InstrKind; -use super::program::Program; -use super::terminator::Terminator; -use std::collections::{HashMap, VecDeque}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum HipOpKind { - Peek, - Borrow, - Mutate, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct HipOp { - pub kind: HipOpKind, - pub gate: ValueId, -} - -pub fn validate_program(program: &Program) -> Result<(), String> { - for module in &program.modules { - for func in &module.functions { - validate_function(func)?; - } - } - Ok(()) -} - -fn validate_function(func: &super::function::Function) -> Result<(), String> { - let mut block_entry_stacks: HashMap> = HashMap::new(); - let mut worklist: VecDeque = VecDeque::new(); - - if func.blocks.is_empty() { - return Ok(()); - } - - // Assume the first block is the entry block (usually ID 0) - let entry_block_id = func.blocks[0].id; - block_entry_stacks.insert(entry_block_id, Vec::new()); - worklist.push_back(entry_block_id); - - let blocks_by_id: HashMap = func.blocks.iter().map(|b| (b.id, b)).collect(); - let mut visited_with_stack: HashMap> = HashMap::new(); - - while let Some(block_id) = worklist.pop_front() { - let block = blocks_by_id.get(&block_id).ok_or_else(|| format!("Invalid block ID: {}", block_id))?; - let mut current_stack = block_entry_stacks.get(&block_id).unwrap().clone(); - - // If we've already visited this block with the same stack, skip it to avoid infinite loops - if let Some(prev_stack) = visited_with_stack.get(&block_id) { - if prev_stack == ¤t_stack { - continue; - } else { - return Err(format!("Block {} reached with inconsistent HIP stacks: {:?} vs {:?}", block_id, prev_stack, current_stack)); - } - } - visited_with_stack.insert(block_id, current_stack.clone()); - - for instr in &block.instrs { - match &instr.kind { - InstrKind::BeginPeek { gate } => { - current_stack.push(HipOp { kind: HipOpKind::Peek, gate: *gate }); - } - InstrKind::BeginBorrow { gate } => { - current_stack.push(HipOp { kind: HipOpKind::Borrow, gate: *gate }); - } - InstrKind::BeginMutate { gate } => { - current_stack.push(HipOp { kind: HipOpKind::Mutate, gate: *gate }); - } - InstrKind::EndPeek => { - match current_stack.pop() { - Some(op) if op.kind == HipOpKind::Peek => {}, - Some(op) => return Err(format!("EndPeek doesn't match current HIP op: {:?}", op)), - None => return Err("EndPeek without matching BeginPeek".to_string()), - } - } - InstrKind::EndBorrow => { - match current_stack.pop() { - Some(op) if op.kind == HipOpKind::Borrow => {}, - Some(op) => return Err(format!("EndBorrow doesn't match current HIP op: {:?}", op)), - None => return Err("EndBorrow without matching BeginBorrow".to_string()), - } - } - InstrKind::EndMutate => { - match current_stack.pop() { - Some(op) if op.kind == HipOpKind::Mutate => {}, - Some(op) => return Err(format!("EndMutate doesn't match current HIP op: {:?}", op)), - None => return Err("EndMutate without matching BeginMutate".to_string()), - } - } - InstrKind::GateLoadField { .. } | InstrKind::GateLoadIndex { .. } => { - if current_stack.is_empty() { - return Err("GateLoad outside of HIP operation".to_string()); - } - } - InstrKind::GateStoreField { .. } | InstrKind::GateStoreIndex { .. } => { - match current_stack.last() { - Some(op) if op.kind == HipOpKind::Mutate => {}, - _ => return Err("GateStore outside of BeginMutate".to_string()), - } - } - InstrKind::Call(id, _) => { - if id.0 == 0 { - return Err("Call to FunctionId(0)".to_string()); - } - } - InstrKind::Alloc { ty, .. } => { - if ty.0 == 0 { - return Err("Alloc with TypeId(0)".to_string()); - } - } - _ => {} - } - } - - match &block.terminator { - Terminator::Return => { - if !current_stack.is_empty() { - return Err(format!("Function returns with non-empty HIP stack: {:?}", current_stack)); - } - } - Terminator::Jump(target) => { - propagate_stack(&mut block_entry_stacks, &mut worklist, *target, ¤t_stack)?; - } - Terminator::JumpIfFalse { target, else_target } => { - propagate_stack(&mut block_entry_stacks, &mut worklist, *target, ¤t_stack)?; - propagate_stack(&mut block_entry_stacks, &mut worklist, *else_target, ¤t_stack)?; - } - } - } - - Ok(()) -} - -fn propagate_stack( - entry_stacks: &mut HashMap>, - worklist: &mut VecDeque, - target: u32, - stack: &Vec -) -> Result<(), String> { - if let Some(existing) = entry_stacks.get(&target) { - if existing != stack { - return Err(format!("Control flow merge at block {} with inconsistent HIP stacks: {:?} vs {:?}", target, existing, stack)); - } - } else { - entry_stacks.insert(target, stack.clone()); - worklist.push_back(target); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_core::*; - - fn create_dummy_function(blocks: Vec) -> Function { - Function { - id: FunctionId(1), - name: "test".to_string(), - sig: { - let mut i = global_signature_interner().lock().unwrap(); - i.intern(Signature { params: vec![], return_type: Type::Void }) - }, - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: Type::Void, - blocks, - local_types: HashMap::new(), - } - } - - fn create_dummy_program(func: Function) -> Program { - Program { - const_pool: ConstPool::new(), - modules: vec![Module { - name: "test".to_string(), - functions: vec![func], - }], - field_offsets: HashMap::new(), - field_types: HashMap::new(), - } - } - - #[test] - fn test_valid_hip_nesting() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }), - Instr::from(InstrKind::BeginMutate { gate: ValueId(1) }), - Instr::from(InstrKind::GateStoreField { gate: ValueId(1), field: FieldId(0), value: ValueId(2) }), - Instr::from(InstrKind::EndMutate), - Instr::from(InstrKind::EndPeek), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - assert!(validate_program(&prog).is_ok()); - } - - #[test] - fn test_invalid_hip_unbalanced() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - let res = validate_program(&prog); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("non-empty HIP stack")); - } - - #[test] - fn test_invalid_hip_wrong_end() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - Instr::from(InstrKind::EndMutate), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - let res = validate_program(&prog); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("EndMutate doesn't match")); - } - - #[test] - fn test_invalid_store_outside_mutate() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginBorrow { gate: ValueId(0) }), - Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) }), - Instr::from(InstrKind::EndBorrow), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - let res = validate_program(&prog); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("GateStore outside of BeginMutate")); - } - - #[test] - fn test_valid_store_in_mutate() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginMutate { gate: ValueId(0) }), - Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) }), - Instr::from(InstrKind::EndMutate), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - assert!(validate_program(&prog).is_ok()); - } - - #[test] - fn test_invalid_load_outside_hip() { - let block = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block])); - let res = validate_program(&prog); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("GateLoad outside of HIP operation")); - } - - #[test] - fn test_valid_hip_across_blocks() { - let block0 = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - ], - terminator: Terminator::Jump(1), - }; - let block1 = Block { - id: 1, - instrs: vec![ - Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }), - Instr::from(InstrKind::EndPeek), - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block0, block1])); - assert!(validate_program(&prog).is_ok()); - } - - #[test] - fn test_invalid_hip_across_blocks_inconsistent() { - let block0 = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::PushConst(ConstId(0))), // cond - ], - terminator: Terminator::JumpIfFalse { target: 2, else_target: 1 }, - }; - let block1 = Block { - id: 1, - instrs: vec![ - Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }), - ], - terminator: Terminator::Jump(3), - }; - let block2 = Block { - id: 2, - instrs: vec![ - // No BeginPeek here - ], - terminator: Terminator::Jump(3), - }; - let block3 = Block { - id: 3, - instrs: vec![ - Instr::from(InstrKind::EndPeek), // ERROR: block 2 reaches here with empty stack - ], - terminator: Terminator::Return, - }; - let prog = create_dummy_program(create_dummy_function(vec![block0, block1, block2, block3])); - let res = validate_program(&prog); - assert!(res.is_err()); - assert!(res.unwrap_err().contains("Control flow merge at block 3")); - } - - #[test] - fn test_silent_fallback_checks() { - let block_func0 = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::Call(FunctionId(0), 0)), - ], - terminator: Terminator::Return, - }; - let prog_func0 = create_dummy_program(create_dummy_function(vec![block_func0])); - assert!(validate_program(&prog_func0).is_err()); - - let block_ty0 = Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::Alloc { ty: TypeId(0), slots: 1 }), - ], - terminator: Terminator::Return, - }; - let prog_ty0 = create_dummy_program(create_dummy_function(vec![block_ty0])); - assert!(validate_program(&prog_ty0).is_err()); - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_lang/instr.rs b/crates/compiler/prometeu-compiler/src/ir_lang/instr.rs deleted file mode 100644 index 542752da..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_lang/instr.rs +++ /dev/null @@ -1,418 +0,0 @@ -//! # IR Instructions -//! -//! This module defines the set of instructions used in the Intermediate Representation (IR). -//! These instructions are designed to be easy to generate from a high-level AST and -//! easy to lower into VM-specific bytecode. - -use crate::common::spans::Span; -use crate::ir_core::ids::{FunctionId, SigId}; -use crate::ir_lang::types::{ConstId, TypeId}; - -/// An `Instruction` combines an instruction's behavior (`kind`) with its -/// source code location (`span`) for debugging and error reporting. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct Instruction { - pub kind: InstrKind, - /// The location in the original source code that generated this instruction. - pub span: Option, -} - -impl Instruction { - /// Creates a new instruction with an optional source span. - pub fn new(kind: InstrKind, span: Option) -> Self { - Self { kind, span } - } -} - -/// A `Label` represents a destination for a jump instruction. -/// During the assembly phase, labels are resolved into actual memory offsets. -#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] -pub struct Label(pub String); - -/// The various types of operations that can be performed in the IR. -/// -/// The IR uses a stack-based model, similar to the final Prometeu ByteCode. -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum InstrKind { - /// Does nothing. - Nop, - /// Terminates program execution. - Halt, - - // --- Literals --- - // These instructions push a constant value from the pool onto the stack. - - /// Pushes a constant from the pool onto the stack. - PushConst(ConstId), - /// Pushes a bounded value (0..0xFFFF) onto the stack. - PushBounded(u32), - /// Pushes a boolean onto the stack. - PushBool(bool), - /// Pushes a `null` value onto the stack. - PushNull, - - // --- Stack Operations --- - - /// Removes the top value from the stack. - Pop, - /// Duplicates the top value on the stack. - Dup, - /// Swaps the top two values on the stack. - Swap, - - // --- Arithmetic --- - // These take two values from the stack and push the result. - - /// Addition: `a + b` - Add, - /// Subtraction: `a - b` - Sub, - /// Multiplication: `a * b` - Mul, - /// Division: `a / b` - Div, - /// Negation: `-a` (takes one value) - Neg, - - // --- Logical/Comparison --- - - /// Equality: `a == b` - Eq, - /// Inequality: `a != b` - Neq, - /// Less than: `a < b` - Lt, - /// Greater than: `a > b` - Gt, - /// Less than or equal: `a <= b` - Lte, - /// Greater than or equal: `a >= b` - Gte, - /// Logical AND: `a && b` - And, - /// Logical OR: `a || b` - Or, - /// Logical NOT: `!a` - Not, - - // --- Bitwise Operations --- - - /// Bitwise AND: `a & b` - BitAnd, - /// Bitwise OR: `a | b` - BitOr, - /// Bitwise XOR: `a ^ b` - BitXor, - /// Shift Left: `a << b` - Shl, - /// Shift Right: `a >> b` - Shr, - - // --- Variable Access --- - - /// Retrieves a value from a local variable slot and pushes it onto the stack. - LocalLoad { slot: u32 }, - /// Pops a value from the stack and stores it in a local variable slot. - LocalStore { slot: u32 }, - /// Retrieves a value from a global variable slot and pushes it onto the stack. - GetGlobal(u32), - /// Pops a value from the stack and stores it in a global variable slot. - SetGlobal(u32), - - // --- Control Flow --- - - /// Unconditionally jumps to the specified label. - Jmp(Label), - /// Pops a boolean from the stack. If false, jumps to the specified label. - JmpIfFalse(Label), - /// Defines a location that can be jumped to. Does not emit code by itself. - Label(Label), - /// Calls a function by ID with the specified number of arguments. - /// Arguments should be pushed onto the stack before calling. - Call { func_id: FunctionId, arg_count: u32 }, - /// Calls a function from another project. - ImportCall { - dep_alias: String, - module_path: String, - /// Optional service/type owner for methods (e.g., "Log"). `None` for free functions. - owner: Option, - /// Unqualified function/method name (e.g., "debug"). - base_name: String, - /// Exact signature id selected by the frontend. - sig: SigId, - arg_count: u32, - }, - /// Returns from the current function. The return value (if any) should be on top of the stack. - Ret, - - // --- OS / System --- - - /// Triggers a system call (e.g., drawing to the screen, reading input). - Syscall(u32), - /// Special instruction to synchronize with the hardware frame clock. - FrameSync, - - // --- HIP / Memory --- - - /// Allocates memory on the heap. - Alloc { type_id: TypeId, slots: u32 }, - /// Reads from heap at gate + offset. Pops gate, pushes value. - GateLoad { offset: u32 }, - /// Writes to heap at gate + offset. Pops gate and value. - GateStore { offset: u32 }, - - // --- Scope Markers --- - GateBeginPeek, - GateEndPeek, - GateBeginBorrow, - GateEndBorrow, - GateBeginMutate, - GateEndMutate, - - // --- Reference Counting --- - - /// Increments the reference count of a gate handle on the stack. - /// Stack: [..., Gate(g)] -> [..., Gate(g)] - GateRetain, - /// Decrements the reference count of a gate handle and pops it from the stack. - /// Stack: [..., Gate(g)] -> [...] - GateRelease, -} - -/// List of instructions that are sensitive to Reference Counting (RC). -/// These instructions must trigger retain/release operations on gate handles. -pub const RC_SENSITIVE_OPS: &[&str] = &[ - "LocalStore", - "GateStore", - "GateLoad", - "Pop", - "Ret", - "FrameSync", -]; - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_lang::types::{ConstId, TypeId}; - - #[test] - fn test_instr_kind_is_cloneable() { - let instr = InstrKind::Alloc { type_id: TypeId(1), slots: 2 }; - let cloned = instr.clone(); - match cloned { - InstrKind::Alloc { type_id, slots } => { - assert_eq!(type_id, TypeId(1)); - assert_eq!(slots, 2); - } - _ => panic!("Clone failed"), - } - } - - #[test] - fn test_isa_surface_snapshot() { - // This test ensures that the instruction set surface remains stable. - // If you add/remove/change instructions, this test will fail, - // prompting an explicit review of the ISA change. - let instructions = vec![ - InstrKind::Nop, - InstrKind::Halt, - InstrKind::PushConst(ConstId(0)), - InstrKind::PushBounded(0), - InstrKind::PushBool(true), - InstrKind::PushNull, - InstrKind::Pop, - InstrKind::Dup, - InstrKind::Swap, - InstrKind::Add, - InstrKind::Sub, - InstrKind::Mul, - InstrKind::Div, - InstrKind::Neg, - InstrKind::Eq, - InstrKind::Neq, - InstrKind::Lt, - InstrKind::Gt, - InstrKind::Lte, - InstrKind::Gte, - InstrKind::And, - InstrKind::Or, - InstrKind::Not, - InstrKind::BitAnd, - InstrKind::BitOr, - InstrKind::BitXor, - InstrKind::Shl, - InstrKind::Shr, - InstrKind::LocalLoad { slot: 0 }, - InstrKind::LocalStore { slot: 0 }, - InstrKind::GetGlobal(0), - InstrKind::SetGlobal(0), - InstrKind::Jmp(Label("target".to_string())), - InstrKind::JmpIfFalse(Label("target".to_string())), - InstrKind::Label(Label("target".to_string())), - InstrKind::Call { func_id: FunctionId(0), arg_count: 0 }, - InstrKind::ImportCall { - dep_alias: "std".to_string(), - module_path: "math".to_string(), - owner: None, - base_name: "abs".to_string(), - sig: SigId(1), - arg_count: 1, - }, - InstrKind::Ret, - InstrKind::Syscall(0), - InstrKind::FrameSync, - InstrKind::Alloc { type_id: TypeId(0), slots: 0 }, - InstrKind::GateLoad { offset: 0 }, - InstrKind::GateStore { offset: 0 }, - InstrKind::GateBeginPeek, - InstrKind::GateEndPeek, - InstrKind::GateBeginBorrow, - InstrKind::GateEndBorrow, - InstrKind::GateBeginMutate, - InstrKind::GateEndMutate, - InstrKind::GateRetain, - InstrKind::GateRelease, - ]; - - let serialized = serde_json::to_string_pretty(&instructions).unwrap(); - - // This is a "lock" on the ISA surface. - // If the structure of InstrKind changes, the serialization will change. - let expected_json = r#"[ - "Nop", - "Halt", - { - "PushConst": 0 - }, - { - "PushBounded": 0 - }, - { - "PushBool": true - }, - "PushNull", - "Pop", - "Dup", - "Swap", - "Add", - "Sub", - "Mul", - "Div", - "Neg", - "Eq", - "Neq", - "Lt", - "Gt", - "Lte", - "Gte", - "And", - "Or", - "Not", - "BitAnd", - "BitOr", - "BitXor", - "Shl", - "Shr", - { - "LocalLoad": { - "slot": 0 - } - }, - { - "LocalStore": { - "slot": 0 - } - }, - { - "GetGlobal": 0 - }, - { - "SetGlobal": 0 - }, - { - "Jmp": "target" - }, - { - "JmpIfFalse": "target" - }, - { - "Label": "target" - }, - { - "Call": { - "func_id": 0, - "arg_count": 0 - } - }, - { - "ImportCall": { - "dep_alias": "std", - "module_path": "math", - "owner": null, - "base_name": "abs", - "sig": 1, - "arg_count": 1 - } - }, - "Ret", - { - "Syscall": 0 - }, - "FrameSync", - { - "Alloc": { - "type_id": 0, - "slots": 0 - } - }, - { - "GateLoad": { - "offset": 0 - } - }, - { - "GateStore": { - "offset": 0 - } - }, - "GateBeginPeek", - "GateEndPeek", - "GateBeginBorrow", - "GateEndBorrow", - "GateBeginMutate", - "GateEndMutate", - "GateRetain", - "GateRelease" -]"#; - assert_eq!(serialized, expected_json); - } - - #[test] - fn test_no_ref_leakage_in_instr_names() { - // Enforce the rule that "Ref" must never refer to HIP memory in ir_lang. - // The snapshot test above already locks the names, but this test - // explicitly asserts the absence of the "Ref" substring in HIP-related instructions. - let instructions = [ - "GateLoad", "GateStore", "Alloc", - "GateBeginPeek", "GateEndPeek", - "GateBeginBorrow", "GateEndBorrow", - "GateBeginMutate", "GateEndMutate", - "GateRetain", "GateRelease" - ]; - - for name in instructions { - assert!(!name.contains("Ref"), "Instruction {} contains forbidden 'Ref' terminology", name); - } - } - - #[test] - fn test_rc_sensitive_list_exists() { - // Required by PR-06: Documentation test or unit assertion that the RC-sensitive list exists - assert!(!RC_SENSITIVE_OPS.is_empty(), "RC-sensitive instructions list must not be empty"); - - let expected = ["LocalStore", "GateStore", "GateLoad", "Pop", "Ret", "FrameSync"]; - for op in expected { - assert!(RC_SENSITIVE_OPS.contains(&op), "RC-sensitive list must contain {}", op); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_lang/mod.rs b/crates/compiler/prometeu-compiler/src/ir_lang/mod.rs deleted file mode 100644 index ba9d7602..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_lang/mod.rs +++ /dev/null @@ -1,177 +0,0 @@ -//! # VM Intermediate Representation (ir_lang) -//! -//! This module defines the Intermediate Representation for the Prometeu VM. -//! -//! ## Memory Model -//! -//! * Heap is never directly addressable. -//! * All HIP (Heap) access is mediated via Gate Pool resolution. -//! * `Gate(GateId)` is the only HIP pointer form in `ir_lang`. -//! -//! ## Reference Counting (RC) -//! -//! The VM uses Reference Counting to manage HIP memory. -//! -//! ### RC Rules: -//! * **Retain**: Increment `strong_rc` when a gate handle is copied. -//! * **Release**: Decrement `strong_rc` when a gate handle is overwritten or dropped. -//! -//! ### RC-Sensitive Instructions: -//! The following instructions are RC-sensitive and must trigger RC updates: -//! * `LocalStore`: Release old value, retain new value. -//! * `GateStore`: Release old value, retain new value. -//! * `Pop`: Release the popped value. -//! * `Ret`: Release all live locals in the frame. -//! * `FrameSync`: Safe point; reclamation occurs after this point. - -pub mod types; -pub mod module; -pub mod instr; -pub mod validate; - -pub use instr::{InstrKind, Instruction, Label}; -pub use module::{Function, Global, Module, Param}; -pub use types::{ConstId, GateId, Type, TypeId, Value}; - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_core::const_pool::{ConstPool, ConstantValue}; - use crate::ir_core::ids::{ConstId, FunctionId}; - use serde_json; - - #[test] - fn test_vm_ir_serialization() { - let mut const_pool = ConstPool::new(); - const_pool.insert(ConstantValue::String("Hello VM".to_string())); - - let module = Module { - name: "test_module".to_string(), - const_pool, - functions: vec![Function { - id: FunctionId(1), - name: "main".to_string(), - sig: crate::ir_core::SigId(0), - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: Type::Null, - body: vec![ - Instruction::new(InstrKind::PushConst(types::ConstId(0)), None), - Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None), - Instruction::new(InstrKind::Ret, None), - ], - }], - globals: vec![], - }; - - let json = serde_json::to_string_pretty(&module).unwrap(); - - let expected = r#"{ - "name": "test_module", - "const_pool": { - "constants": [ - { - "String": "Hello VM" - } - ] - }, - "functions": [ - { - "id": 1, - "name": "main", - "params": [], - "return_type": "Null", - "body": [ - { - "kind": { - "PushConst": 0 - }, - "span": null - }, - { - "kind": { - "Call": { - "func_id": 2, - "arg_count": 1 - } - }, - "span": null - }, - { - "kind": "Ret", - "span": null - } - ], - "param_slots": 0, - "local_slots": 0, - "return_slots": 0 - } - ], - "globals": [] -}"#; - assert_eq!(json, expected); - } - - #[test] - fn test_lowering_smoke() { - use crate::ir_core; - use crate::lowering::lower_program; - - let mut const_pool = ir_core::ConstPool::new(); - const_pool.insert(ir_core::ConstantValue::Int(42)); - - let program = ir_core::Program { - const_pool, - modules: vec![ir_core::Module { - name: "test_core".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(10), - name: "start".to_string(), - sig: { - let mut i = crate::ir_core::global_signature_interner().lock().unwrap(); - i.intern(crate::ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![ir_core::Block { - id: 0, - instrs: vec![ - ir_core::Instr::from(ir_core::InstrKind::PushConst(ConstId(0))), - ], - terminator: ir_core::Terminator::Return, - }], - local_types: std::collections::HashMap::new(), - }], - }], - field_offsets: std::collections::HashMap::new(), - field_types: std::collections::HashMap::new(), - }; - - let vm_module = lower_program(&program).expect("Lowering failed"); - - assert_eq!(vm_module.name, "test_core"); - assert_eq!(vm_module.functions.len(), 1); - let func = &vm_module.functions[0]; - assert_eq!(func.name, "start"); - assert_eq!(func.id, FunctionId(10)); - - assert_eq!(func.body.len(), 3); - match &func.body[0].kind { - InstrKind::Label(Label(l)) => assert!(l.contains("block_0")), - _ => panic!("Expected label"), - } - match &func.body[1].kind { - InstrKind::PushConst(id) => assert_eq!(id.0, 0), - _ => panic!("Expected PushConst"), - } - match &func.body[2].kind { - InstrKind::Ret => (), - _ => panic!("Expected Ret"), - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_lang/module.rs b/crates/compiler/prometeu-compiler/src/ir_lang/module.rs deleted file mode 100644 index 257873b7..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_lang/module.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! # IR Module Structure -//! -//! This module defines the structure of the Intermediate Representation (IR). -//! The IR is a higher-level representation of the program than bytecode, but lower -//! than the source code AST. It is organized into Modules, Functions, and Globals. - -use crate::ir_core::const_pool::ConstPool; -use crate::ir_core::ids::{FunctionId, SigId}; -use crate::ir_lang::instr::Instruction; -use crate::ir_lang::types::Type; -use serde::{Deserialize, Serialize}; - -/// A `Module` is the top-level container for a compiled program or library. -/// It contains a collection of global variables, functions, and a constant pool. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Module { - /// The name of the module (usually derived from the project name). - pub name: String, - /// Shared constant pool for this module. - pub const_pool: ConstPool, - /// List of all functions defined in this module. - pub functions: Vec, - /// List of all global variables available in this module. - pub globals: Vec, -} - -/// Represents a function in the IR. -/// -/// Functions consist of a signature (name, parameters, return type) and a body -/// which is a flat list of IR instructions. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Function { - /// The unique identifier of the function. - pub id: FunctionId, - /// The unique name of the function. - pub name: String, - /// Canonical signature id (params + return type). Not serialized yet. - #[serde(skip)] - pub sig: SigId, - /// The list of input parameters. - pub params: Vec, - /// The type of value this function returns. - pub return_type: Type, - /// The sequence of instructions that make up the function's logic. - pub body: Vec, - - pub param_slots: u16, - pub local_slots: u16, - pub return_slots: u16, -} - -/// A parameter passed to a function. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Param { - /// The name of the parameter (useful for debugging and symbols). - pub name: String, - /// The data type of the parameter. - pub r#type: Type, -} - -/// A global variable accessible by any function in the module. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Global { - /// The name of the global variable. - pub name: String, - /// The data type of the variable. - pub r#type: Type, - /// The unique memory slot index assigned to this global variable. - pub slot: u32, -} - -impl Module { - /// Creates a new, empty module with the given name. - pub fn new(name: String) -> Self { - Self { - name, - const_pool: ConstPool::new(), - functions: Vec::new(), - globals: Vec::new(), - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_lang/types.rs b/crates/compiler/prometeu-compiler/src/ir_lang/types.rs deleted file mode 100644 index 07d35620..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_lang/types.rs +++ /dev/null @@ -1,88 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct GateId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct ConstId(pub u32); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] -pub struct TypeId(pub u32); - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Value { - Int(i64), - Float(f64), - Bounded(u32), - Bool(bool), - Unit, - Const(ConstId), - Gate(GateId), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Type { - Any, - Null, - Bool, - Int, - Bounded, - Float, - String, - Color, - Array(Box), - Object, - Function, - Void, -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashSet; - - #[test] - fn test_ids_implement_required_traits() { - fn assert_copy() {} - fn assert_eq_hash() {} - - assert_copy::(); - assert_eq_hash::(); - - assert_copy::(); - assert_eq_hash::(); - - assert_copy::(); - assert_eq_hash::(); - } - - #[test] - fn test_gate_id_usage() { - let id1 = GateId(1); - let id2 = GateId(1); - let id3 = GateId(2); - - assert_eq!(id1, id2); - assert_ne!(id1, id3); - - let mut set = HashSet::new(); - set.insert(id1); - assert!(set.contains(&id2)); - } - - #[test] - fn test_value_gate_exists_and_is_clonable() { - let gate_id = GateId(42); - let val = Value::Gate(gate_id); - - let cloned_val = val.clone(); - if let Value::Gate(id) = cloned_val { - assert_eq!(id, gate_id); - } else { - panic!("Expected Value::Gate"); - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/ir_lang/validate.rs b/crates/compiler/prometeu-compiler/src/ir_lang/validate.rs deleted file mode 100644 index 64824d10..00000000 --- a/crates/compiler/prometeu-compiler/src/ir_lang/validate.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; -use crate::ir_lang::module::Module; - -pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> { - // TODO: Implement common IR validations: - // - Type checking rules - // - HostCall signatures - // - VM invariants - Ok(()) -} diff --git a/crates/compiler/prometeu-compiler/src/lib.rs b/crates/compiler/prometeu-compiler/src/lib.rs deleted file mode 100644 index f2f010eb..00000000 --- a/crates/compiler/prometeu-compiler/src/lib.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! # Prometeu Compiler -//! -//! This crate provides the official compiler for the Prometeu ecosystem. -//! It translates high-level source code (primarily Prometeu Base Script - PBS) into -//! Prometeu ByteCode (.pbc), which runs on the Prometeu Virtual Machine. -//! -//! ## Architecture Overview: -//! -//! The compiler follows a multi-stage pipeline: -//! -//! 1. **Frontend (Parsing & Analysis)**: -//! - Uses the PBS parser to generate an Abstract Syntax Tree (AST). -//! - Performs semantic analysis and validation. -//! - Lowers the AST into the **Intermediate Representation (IR)**. -//! - *Example*: Converting a `a + b` expression into IR instructions like `Push(a)`, `Push(b)`, `Add`. -//! -//! 2. **IR Optimization (Optional/Planned)**: -//! - Simplifies the IR to improve performance or reduce bytecode size. -//! -//! 3. **Backend (Code Generation)**: -//! - **Lowering**: Converts the high-level IR into a flat list of ByteCode instructions. -//! - **Assembly**: Resolves branch labels into actual memory offsets. -//! - **Serialization**: Encodes the instructions into the binary PBC format. -//! -//! 4. **Artifact Export**: -//! - Generates the `.pbc` binary file. -//! - Optionally produces `.disasm` (disassembly for debugging) and `.json` (symbol maps). -//! -//! ## Example Usage (CLI): -//! -//! ```bash -//! # Build a project from a directory -//! prometeu-compiler build ./my-game --entry ./src/main.pbs --out ./game.pbc -//! ``` -//! -//! ## Programmatic Entry Point: -//! -//! See the [`compiler`] module for the main entry point to trigger a compilation programmatically. - -pub mod common; -pub mod ir_lang; -pub mod ir_core; -pub mod lowering; -pub mod backend; -pub mod frontends; -pub mod compiler; -pub mod manifest; -pub mod deps; -pub mod sources; -pub mod building; -pub mod semantics; -pub mod analysis; - -use anyhow::Result; -use clap::{Parser, Subcommand}; -use std::path::PathBuf; - -/// Command line interface for the Prometeu Compiler. -#[derive(Parser)] -#[command(name = "prometeu-compiler")] -#[command(version, about = "Official compiler for the PROMETEU Virtual Machine", long_about = None)] -pub struct Cli { - /// The action to perform (build or verify). - #[command(subcommand)] - pub command: Commands, -} - -/// Available subcommands for the compiler. -#[derive(Subcommand)] -pub enum Commands { - /// Builds a Prometeu project by compiling source code into a PBC file. - Build { - /// Path to the project root directory. - project_dir: PathBuf, - - /// Explicit path to the entry file (defaults to src/main.pbs). - #[arg(short, long)] - entry: Option, - - /// Path to save the compiled .pbc file. - #[arg(short, long)] - out: Option, - - /// Whether to generate a .json symbols file for source mapping. - #[arg(long, default_value_t = true)] - emit_symbols: bool, - - /// Disable symbol generation. - #[arg(long)] - no_symbols: bool, - - /// Whether to generate a .disasm file for debugging. - #[arg(long, default_value_t = true)] - emit_disasm: bool, - - /// Disable disassembly generation. - #[arg(long)] - no_disasm: bool, - - /// Whether to explain the dependency resolution process. - #[arg(long)] - explain_deps: bool, - }, - /// Verifies if a Prometeu project is syntactically and semantically valid without emitting code. - Verify { - /// Path to the project root directory. - project_dir: PathBuf, - - /// Whether to explain the dependency resolution process. - #[arg(long)] - explain_deps: bool, - }, -} - -/// Main entry point for the compiler library's execution logic. -/// Parses CLI arguments and dispatches to the appropriate compiler functions. -pub fn run() -> Result<()> { - let cli = Cli::parse(); - - match cli.command { - Commands::Build { - project_dir, - out, - emit_disasm, - no_disasm, - emit_symbols, - no_symbols, - explain_deps, - .. - } => { - let build_dir = project_dir.join("../../../../build"); - let out = out.unwrap_or_else(|| build_dir.join("program.pbc")); - - let emit_symbols = emit_symbols && !no_symbols; - let emit_disasm = emit_disasm && !no_disasm; - - if !build_dir.exists() { - std::fs::create_dir_all(&build_dir)?; - } - - println!("Building project at {:?}", project_dir); - println!("Output: {:?}", out); - - let compilation_unit = compiler::compile_ext(&project_dir, explain_deps)?; - compilation_unit.export(&out, emit_disasm, emit_symbols)?; - } - Commands::Verify { project_dir, explain_deps } => { - println!("Verifying project at {:?}", project_dir); - - compiler::compile_ext(&project_dir, explain_deps)?; - println!("Project is valid!"); - } - } - - Ok(()) -} diff --git a/crates/compiler/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/compiler/prometeu-compiler/src/lowering/core_to_vm.rs deleted file mode 100644 index f1a3e003..00000000 --- a/crates/compiler/prometeu-compiler/src/lowering/core_to_vm.rs +++ /dev/null @@ -1,760 +0,0 @@ -use crate::ir_core; -use crate::ir_lang; -use anyhow::Result; -use std::collections::HashMap; - -/// Lowers a Core IR program into a VM IR module. -pub fn lower_program(program: &ir_core::Program) -> Result { - // Build a map of function return types for type tracking - let mut function_returns = HashMap::new(); - for module in &program.modules { - for func in &module.functions { - function_returns.insert(func.id, func.return_type.clone()); - } - } - - // For now, we assume a single module program or lower the first one. - if let Some(core_module) = program.modules.first() { - lower_module(core_module, program, &function_returns) - } else { - anyhow::bail!("No modules in core program") - } -} - -/// Lowers a single Core IR module into a VM IR module. -pub fn lower_module( - core_module: &ir_core::Module, - program: &ir_core::Program, - function_returns: &HashMap, -) -> Result { - let mut vm_module = ir_lang::Module::new(core_module.name.clone()); - vm_module.const_pool = program.const_pool.clone(); - - for core_func in &core_module.functions { - // Detect the PBS entry point heuristically by (function name + signature) - // This matches fn frame(): void anywhere. In practice for v0 tests, only main.pbs defines it. - let is_entry_point = core_func.name == "frame" - && core_func.params.is_empty() - && matches!(core_func.return_type, ir_core::Type::Void); - - vm_module - .functions - .push(lower_function(core_func, program, function_returns, is_entry_point)?); - } - - Ok(vm_module) -} - -/// Lowers a Core IR function into a VM IR function. -pub fn lower_function( - core_func: &ir_core::Function, - program: &ir_core::Program, - function_returns: &HashMap, - is_entry_point: bool, -) -> Result { - let mut vm_func = ir_lang::Function { - id: core_func.id, - name: core_func.name.clone(), - sig: core_func.sig, - params: core_func.params.iter().map(|p| ir_lang::Param { - name: p.name.clone(), - r#type: lower_type(&p.ty), - }).collect(), - return_type: lower_type(&core_func.return_type), - body: vec![], - param_slots: core_func.param_slots, - local_slots: core_func.local_slots, - return_slots: core_func.return_slots, - }; - - // Type tracking for RC insertion - let mut local_types = HashMap::new(); - // Populate with parameter types - for (i, param) in core_func.params.iter().enumerate() { - local_types.insert(i as u32, param.ty.clone()); - } - // Also use the pre-computed local types from ir_core if available - for (slot, ty) in &core_func.local_types { - local_types.insert(*slot, ty.clone()); - } - - for block in &core_func.blocks { - // Core blocks map to labels in the flat VM IR instruction list. - vm_func.body.push(ir_lang::Instruction::new( - ir_lang::InstrKind::Label(ir_lang::Label(format!("block_{}", block.id))), - None, - )); - - // Note: For multi-block functions, we should ideally track stack types across blocks. - // For v0, we assume each block starts with an empty stack in terms of types, - // which matches how PBS frontend generates code for now. - let mut stack_types = Vec::new(); - - for instr in &block.instrs { - let span = instr.span.clone(); - match &instr.kind { - ir_core::InstrKind::PushConst(id) => { - let ty = if let Some(val) = program.const_pool.get(ir_core::ConstId(id.0)) { - match val { - ir_core::ConstantValue::Int(_) => ir_core::Type::Int, - ir_core::ConstantValue::Float(_) => ir_core::Type::Float, - ir_core::ConstantValue::String(_) => ir_core::Type::String, - } - } else { - ir_core::Type::Void - }; - stack_types.push(ty); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::PushConst(ir_lang::ConstId(id.0)), span.clone())); - } - ir_core::InstrKind::PushBounded(val) => { - stack_types.push(ir_core::Type::Bounded); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::PushBounded(*val), span.clone())); - } - ir_core::InstrKind::Call(func_id, arg_count) => { - // Pop arguments from type stack - for _ in 0..*arg_count { - stack_types.pop(); - } - // Push return type - let ret_ty = function_returns.get(func_id).cloned().unwrap_or(ir_core::Type::Void); - stack_types.push(ret_ty); - - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Call { - func_id: *func_id, - arg_count: *arg_count - }, None)); - } - ir_core::InstrKind::ImportCall { dep_alias, module_path, owner, base_name, sig, arg_count } => { - // Pop arguments from type stack - for _ in 0..*arg_count { - stack_types.pop(); - } - // Do not assume a return type here; VM semantics should be verified elsewhere. - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::ImportCall { - dep_alias: dep_alias.clone(), - module_path: module_path.clone(), - owner: owner.clone(), - base_name: base_name.clone(), - sig: *sig, - arg_count: *arg_count, - }, None)); - } - ir_core::InstrKind::HostCall(id, slots) => { - // HostCall return types are not easily known without a registry, - // but we now pass the number of slots. - for _ in 0..*slots { - stack_types.push(ir_core::Type::Int); - } - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Syscall(*id), span.clone())); - } - ir_core::InstrKind::GetLocal(slot) => { - let ty = local_types.get(slot).cloned().unwrap_or(ir_core::Type::Void); - stack_types.push(ty.clone()); - - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: *slot }, span.clone())); - - // If it's a gate, we should retain it if we just pushed it onto stack? - // "on assigning a gate to a local/global" - // "on overwriting a local/global holding a gate" - // "on popping/dropping gate temporaries" - - // Wait, if I Load it, I have a new handle on the stack. I should Retain it. - if is_gate_type(&ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - } - } - ir_core::InstrKind::SetLocal(slot) => { - let new_ty = stack_types.pop().unwrap_or(ir_core::Type::Void); - let old_ty = local_types.get(slot).cloned(); - - // 1. Release old value if it was a gate - if let Some(old_ty) = old_ty { - if is_gate_type(&old_ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: *slot }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } - } - - // 2. The new value is already on stack. - // We don't need to Retain it here because it was either just created (Alloc) - // or just Loaded (which already did a Retain). - // Wait, if it was just Loaded, it has +1. If we store it, it stays +1. - // If it was just Alocated, it has +1. If we store it, it stays +1. - - // Actually, if we Pop it later, we Release it. - - local_types.insert(*slot, new_ty); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalStore { slot: *slot }, span.clone())); - } - ir_core::InstrKind::Pop => { - let ty = stack_types.pop().unwrap_or(ir_core::Type::Void); - if is_gate_type(&ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } else { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Pop, span.clone())); - } - } - ir_core::InstrKind::Dup => { - let ty = stack_types.last().cloned().unwrap_or(ir_core::Type::Void); - stack_types.push(ty.clone()); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Dup, span.clone())); - if is_gate_type(&ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - } - } - ir_core::InstrKind::Add | ir_core::InstrKind::Sub | ir_core::InstrKind::Mul | ir_core::InstrKind::Div => { - stack_types.pop(); - stack_types.pop(); - stack_types.push(ir_core::Type::Int); // Assume Int for arithmetic - let kind = match &instr.kind { - ir_core::InstrKind::Add => ir_lang::InstrKind::Add, - ir_core::InstrKind::Sub => ir_lang::InstrKind::Sub, - ir_core::InstrKind::Mul => ir_lang::InstrKind::Mul, - ir_core::InstrKind::Div => ir_lang::InstrKind::Div, - _ => unreachable!(), - }; - vm_func.body.push(ir_lang::Instruction::new(kind, span.clone())); - } - ir_core::InstrKind::Neg => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Neg, span.clone())); - } - ir_core::InstrKind::Eq | ir_core::InstrKind::Neq | ir_core::InstrKind::Lt | ir_core::InstrKind::Lte | ir_core::InstrKind::Gt | ir_core::InstrKind::Gte => { - stack_types.pop(); - stack_types.pop(); - stack_types.push(ir_core::Type::Bool); - let kind = match &instr.kind { - ir_core::InstrKind::Eq => ir_lang::InstrKind::Eq, - ir_core::InstrKind::Neq => ir_lang::InstrKind::Neq, - ir_core::InstrKind::Lt => ir_lang::InstrKind::Lt, - ir_core::InstrKind::Lte => ir_lang::InstrKind::Lte, - ir_core::InstrKind::Gt => ir_lang::InstrKind::Gt, - ir_core::InstrKind::Gte => ir_lang::InstrKind::Gte, - _ => unreachable!(), - }; - vm_func.body.push(ir_lang::Instruction::new(kind, span.clone())); - } - ir_core::InstrKind::And | ir_core::InstrKind::Or => { - stack_types.pop(); - stack_types.pop(); - stack_types.push(ir_core::Type::Bool); - let kind = match &instr.kind { - ir_core::InstrKind::And => ir_lang::InstrKind::And, - ir_core::InstrKind::Or => ir_lang::InstrKind::Or, - _ => unreachable!(), - }; - vm_func.body.push(ir_lang::Instruction::new(kind, span.clone())); - } - ir_core::InstrKind::Not => { - stack_types.pop(); - stack_types.push(ir_core::Type::Bool); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Not, span.clone())); - } - ir_core::InstrKind::Alloc { ty, slots } => { - stack_types.push(ir_core::Type::Contract(format!("Gate<{}>", ty.0))); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Alloc { - type_id: ir_lang::TypeId(ty.0), - slots: *slots - }, None)); - } - ir_core::InstrKind::BeginPeek { gate } => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateBeginPeek, span.clone())); - } - ir_core::InstrKind::BeginBorrow { gate } => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateBeginBorrow, span.clone())); - } - ir_core::InstrKind::BeginMutate { gate } => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateBeginMutate, span.clone())); - } - ir_core::InstrKind::EndPeek => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateEndPeek, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } - ir_core::InstrKind::EndBorrow => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateEndBorrow, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } - ir_core::InstrKind::EndMutate => { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateEndMutate, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } - ir_core::InstrKind::GateLoadField { gate, field } => { - let offset = program.field_offsets.get(field) - .ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?; - - let field_ty = program.field_types.get(field).cloned().unwrap_or(ir_core::Type::Int); - stack_types.push(field_ty.clone()); - - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateLoad { offset: *offset }, span.clone())); - - if is_gate_type(&field_ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - } - } - ir_core::InstrKind::GateStoreField { gate, field, value } => { - let offset = program.field_offsets.get(field) - .ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?; - - let field_ty = program.field_types.get(field).cloned().unwrap_or(ir_core::Type::Int); - - // 1. Release old value in HIP if it was a gate - if is_gate_type(&field_ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateLoad { offset: *offset }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, span.clone())); - } - - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: gate.0 }, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: value.0 }, span.clone())); - - // 2. Retain new value if it's a gate - if let Some(val_ty) = local_types.get(&value.0) { - if is_gate_type(val_ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRetain, span.clone())); - } - } - - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateStore { offset: *offset }, span.clone())); - } - ir_core::InstrKind::GateLoadIndex { .. } => { - anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering"); - } - ir_core::InstrKind::GateStoreIndex { .. } => { - anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering"); - } - ir_core::InstrKind::Free => anyhow::bail!("Instruction 'Free' cannot be represented in ir_lang v0"), - } - } - - match &block.terminator { - ir_core::Terminator::Return => { - // Release all live locals that hold gates - let mut sorted_slots: Vec<_> = local_types.keys().collect(); - sorted_slots.sort(); - - for slot in sorted_slots { - let ty = &local_types[slot]; - if is_gate_type(ty) { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::LocalLoad { slot: *slot }, None)); - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::GateRelease, None)); - } - } - - // Inject FRAME_SYNC immediately before RET only for the entry point. - // This is a signal-only safe point; no GC opcodes should be emitted here. - if is_entry_point { - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::FrameSync, None)); - } - - // If the function is Void, we don't need to push anything. - // The VM's Ret opcode handles zero return slots correctly. - vm_func.body.push(ir_lang::Instruction::new(ir_lang::InstrKind::Ret, None)); - } - ir_core::Terminator::Jump(target) => { - vm_func.body.push(ir_lang::Instruction::new( - ir_lang::InstrKind::Jmp(ir_lang::Label(format!("block_{}", target))), - None, - )); - } - ir_core::Terminator::JumpIfFalse { target, else_target } => { - stack_types.pop(); - vm_func.body.push(ir_lang::Instruction::new( - ir_lang::InstrKind::JmpIfFalse(ir_lang::Label(format!("block_{}", target))), - None, - )); - vm_func.body.push(ir_lang::Instruction::new( - ir_lang::InstrKind::Jmp(ir_lang::Label(format!("block_{}", else_target))), - None, - )); - } - } - } - - Ok(vm_func) -} - -// Note: Unit tests for full lowering already exist below. End-to-end tests for -// FRAME_SYNC injection are provided in the orchestrator tests. - -fn is_gate_type(ty: &ir_core::Type) -> bool { - match ty { - ir_core::Type::Contract(name) => name.starts_with("Gate<"), - _ => false, - } -} - -fn lower_type(ty: &ir_core::Type) -> ir_lang::Type { - match ty { - ir_core::Type::Void => ir_lang::Type::Void, - ir_core::Type::Int => ir_lang::Type::Int, - ir_core::Type::Float => ir_lang::Type::Float, - ir_core::Type::Bool => ir_lang::Type::Bool, - ir_core::Type::String => ir_lang::Type::String, - ir_core::Type::Bounded => ir_lang::Type::Bounded, - ir_core::Type::Optional(inner) => ir_lang::Type::Array(Box::new(lower_type(inner))), - ir_core::Type::Result(ok, _) => lower_type(ok), - ir_core::Type::Struct(_) - | ir_core::Type::Service(_) - | ir_core::Type::Contract(_) - | ir_core::Type::ErrorType(_) => ir_lang::Type::Object, - ir_core::Type::Function { .. } => ir_lang::Type::Function, - ir_core::Type::Array(inner, _) => ir_lang::Type::Array(Box::new(lower_type(inner))), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ir_core; - use crate::ir_core::ids::{ConstId as CoreConstId, FunctionId}; - use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, InstrKind, Program, Terminator}; - use crate::ir_lang::{InstrKind as VmInstrKind, Label}; - - #[test] - fn test_full_lowering() { - let mut const_pool = ConstPool::new(); - const_pool.insert(ConstantValue::Int(100)); // ConstId(0) - - let program = Program { - const_pool, - modules: vec![ir_core::Module { - name: "test_mod".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "main".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![ - Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::PushConst(CoreConstId(0))), - Instr::from(InstrKind::Call(FunctionId(2), 1)), - ], - terminator: Terminator::Jump(1), - }, - Block { - id: 1, - instrs: vec![ - Instr::from(InstrKind::HostCall(42, 1)), - ], - terminator: Terminator::Return, - }, - ], - local_types: HashMap::new(), - param_slots: 0, - local_slots: 0, - return_slots: 0, - }], - }], - field_offsets: std::collections::HashMap::new(), - field_types: std::collections::HashMap::new(), - }; - - let vm_module = lower_program(&program).expect("Lowering failed"); - - assert_eq!(vm_module.name, "test_mod"); - let func = &vm_module.functions[0]; - assert_eq!(func.name, "main"); - - assert_eq!(func.body.len(), 7); - - match &func.body[0].kind { - VmInstrKind::Label(Label(l)) => assert_eq!(l, "block_0"), - _ => panic!("Expected label block_0"), - } - match &func.body[1].kind { - VmInstrKind::PushConst(id) => assert_eq!(id.0, 0), - _ => panic!("Expected PushConst 0"), - } - match &func.body[2].kind { - VmInstrKind::Call { func_id, arg_count } => { - assert_eq!(func_id.0, 2); - assert_eq!(*arg_count, 1); - } - _ => panic!("Expected Call"), - } - match &func.body[3].kind { - VmInstrKind::Jmp(Label(l)) => assert_eq!(l, "block_1"), - _ => panic!("Expected Jmp block_1"), - } - match &func.body[4].kind { - VmInstrKind::Label(Label(l)) => assert_eq!(l, "block_1"), - _ => panic!("Expected label block_1"), - } - match &func.body[5].kind { - VmInstrKind::Syscall(id) => assert_eq!(*id, 42), - _ => panic!("Expected HostCall 42"), - } - match &func.body[6].kind { - VmInstrKind::Ret => (), - _ => panic!("Expected Ret"), - } - } - - #[test] - fn test_field_access_lowering_golden() { - let const_pool = ConstPool::new(); - let mut field_offsets = std::collections::HashMap::new(); - let field_id = ir_core::FieldId(42); - field_offsets.insert(field_id, 100); - - let program = Program { - const_pool, - modules: vec![ir_core::Module { - name: "test".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "test_fields".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::GateLoadField { gate: ir_core::ValueId(0), field: field_id }), - Instr::from(InstrKind::GateStoreField { gate: ir_core::ValueId(0), field: field_id, value: ir_core::ValueId(1) }), - ], - terminator: Terminator::Return, - }], - local_types: HashMap::new(), - param_slots: 0, - local_slots: 0, - return_slots: 0, - }], - }], - field_offsets, - field_types: HashMap::new(), - }; - - let vm_module = lower_program(&program).expect("Lowering failed"); - let func = &vm_module.functions[0]; - - // Expected VM IR: - // Label block_0 - // LocalLoad 0 (gate) - // GateLoad 100 (offset) - // LocalLoad 0 (gate) - // LocalLoad 1 (value) - // GateStore 100 (offset) - // Ret - - assert_eq!(func.body.len(), 9); - match &func.body[1].kind { - VmInstrKind::LocalLoad { slot } => assert_eq!(*slot, 0), - _ => panic!("Expected LocalLoad 0"), - } - match &func.body[2].kind { - VmInstrKind::GateRetain => (), - _ => panic!("Expected GateRetain"), - } - match &func.body[3].kind { - VmInstrKind::GateLoad { offset } => assert_eq!(*offset, 100), - _ => panic!("Expected GateLoad 100"), - } - match &func.body[7].kind { - VmInstrKind::GateStore { offset } => assert_eq!(*offset, 100), - _ => panic!("Expected GateStore 100"), - } - match &func.body[8].kind { - VmInstrKind::Ret => (), - _ => panic!("Expected Ret"), - } - } - - #[test] - fn test_missing_field_offset_fails() { - let program = Program { - const_pool: ConstPool::new(), - modules: vec![ir_core::Module { - name: "test".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "fail".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::GateLoadField { gate: ir_core::ValueId(0), field: ir_core::FieldId(999) }), - ], - terminator: Terminator::Return, - }], - local_types: HashMap::new(), - param_slots: 0, - local_slots: 0, - return_slots: 0, - }], - }], - field_offsets: std::collections::HashMap::new(), - field_types: HashMap::new(), - }; - - let result = lower_program(&program); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("E_LOWER_UNRESOLVED_OFFSET")); - } - - #[test] - fn test_rc_trace_lowering_golden() { - let mut const_pool = ConstPool::new(); - const_pool.insert(ConstantValue::Int(0)); // ConstId(0) - - let type_id = ir_core::TypeId(1); - - let program = Program { - const_pool, - modules: vec![ir_core::Module { - name: "test".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "main".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - // 1. allocates a gate - Instr::from(InstrKind::Alloc { ty: type_id, slots: 1 }), - Instr::from(InstrKind::SetLocal(0)), // x = alloc - - // 2. copies it - Instr::from(InstrKind::GetLocal(0)), - Instr::from(InstrKind::SetLocal(1)), // y = x - - // 3. overwrites one copy - Instr::from(InstrKind::PushConst(CoreConstId(0))), - Instr::from(InstrKind::SetLocal(0)), // x = 0 (overwrites gate) - ], - terminator: Terminator::Return, - }], - local_types: HashMap::new(), - param_slots: 0, - local_slots: 0, - return_slots: 0, - }], - }], - field_offsets: HashMap::new(), - field_types: HashMap::new(), - }; - - let vm_module = lower_program(&program).expect("Lowering failed"); - let func = &vm_module.functions[0]; - - let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); - - assert!(kinds.contains(&&VmInstrKind::GateRetain)); - assert!(kinds.contains(&&VmInstrKind::GateRelease)); - - // Check specific sequence for overwrite: - // LocalLoad 0, GateRelease, LocalStore 0 - let mut found_overwrite = false; - for i in 0..kinds.len() - 2 { - if let (VmInstrKind::LocalLoad { slot: 0 }, VmInstrKind::GateRelease, VmInstrKind::LocalStore { slot: 0 }) = (kinds[i], kinds[i+1], kinds[i+2]) { - found_overwrite = true; - break; - } - } - assert!(found_overwrite, "Should have emitted release-then-store sequence for overwrite"); - - // Check Ret cleanup: - // LocalLoad 1, GateRelease, Ret - let mut found_cleanup = false; - for i in 0..kinds.len() - 2 { - if let (VmInstrKind::LocalLoad { slot: 1 }, VmInstrKind::GateRelease, VmInstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2]) { - found_cleanup = true; - break; - } - } - assert!(found_cleanup, "Should have emitted cleanup for local y at return"); - } - - #[test] - fn test_no_silent_rc() { - let mut const_pool = ConstPool::new(); - const_pool.insert(ConstantValue::Int(42)); - - let program = Program { - const_pool, - modules: vec![ir_core::Module { - name: "test".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "main".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - params: vec![], - return_type: ir_core::Type::Void, - blocks: vec![Block { - id: 0, - instrs: vec![ - Instr::from(InstrKind::PushConst(CoreConstId(0))), - Instr::from(InstrKind::SetLocal(0)), // x = 42 - Instr::from(InstrKind::GetLocal(0)), - Instr::from(InstrKind::Pop), - ], - terminator: Terminator::Return, - }], - local_types: HashMap::new(), - param_slots: 0, - local_slots: 0, - return_slots: 0, - }], - }], - field_offsets: HashMap::new(), - field_types: HashMap::new(), - }; - - let vm_module = lower_program(&program).expect("Lowering failed"); - let func = &vm_module.functions[0]; - - for instr in &func.body { - match &instr.kind { - VmInstrKind::GateRetain | VmInstrKind::GateRelease => { - panic!("Non-gate program should not contain RC instructions: {:?}", instr); - } - _ => {} - } - } - } - - #[test] - fn test_no_implicit_offsets_in_vm_ir() { - // This test ensures that GateLoad and GateStore in VM IR always have explicit offsets. - // Since we are using struct variants with mandatory 'offset' field, this is - // enforced by the type system, but we can also check the serialized form. - let instructions = vec![ - VmInstrKind::GateLoad { offset: 123 }, - VmInstrKind::GateStore { offset: 456 }, - ]; - let json = serde_json::to_string(&instructions).unwrap(); - assert!(json.contains("\"GateLoad\":{\"offset\":123}")); - assert!(json.contains("\"GateStore\":{\"offset\":456}")); - } -} diff --git a/crates/compiler/prometeu-compiler/src/lowering/mod.rs b/crates/compiler/prometeu-compiler/src/lowering/mod.rs deleted file mode 100644 index c69ffd0e..00000000 --- a/crates/compiler/prometeu-compiler/src/lowering/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod core_to_vm; - -pub use core_to_vm::lower_program; diff --git a/crates/compiler/prometeu-compiler/src/manifest.rs b/crates/compiler/prometeu-compiler/src/manifest.rs deleted file mode 100644 index 1d2b3e21..00000000 --- a/crates/compiler/prometeu-compiler/src/manifest.rs +++ /dev/null @@ -1,404 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::fs; -use std::path::{Path, PathBuf}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum ManifestKind { - App, - Lib, - System, -} - -impl Default for ManifestKind { - fn default() -> Self { - Self::App - } -} - -pub type Alias = String; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(untagged)] -pub enum DependencySpec { - Path(String), - Full(FullDependencySpec), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct FullDependencySpec { - pub path: Option, - pub git: Option, - pub version: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Manifest { - pub name: String, - pub version: String, - #[serde(default)] - pub kind: ManifestKind, - #[serde(default)] - pub dependencies: BTreeMap, -} - -#[derive(Debug)] -pub enum ManifestError { - Io(std::io::Error), - Json { - path: PathBuf, - error: serde_json::Error, - }, - Validation { - path: PathBuf, - message: String, - pointer: Option, - }, -} - -impl std::fmt::Display for ManifestError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ManifestError::Io(e) => write!(f, "IO error: {}", e), - ManifestError::Json { path, error } => { - write!(f, "JSON error in {}: {}", path.display(), error) - } - ManifestError::Validation { path, message, pointer } => { - write!(f, "Validation error in {}: {}", path.display(), message)?; - if let Some(p) = pointer { - write!(f, " (at {})", p)?; - } - Ok(()) - } - } - } -} - -impl std::error::Error for ManifestError {} - -pub fn load_manifest(project_root: &Path) -> Result { - let manifest_path = project_root.join("prometeu.json"); - let content = fs::read_to_string(&manifest_path).map_err(ManifestError::Io)?; - let manifest: Manifest = serde_json::from_str(&content).map_err(|e| ManifestError::Json { - path: manifest_path.clone(), - error: e, - })?; - - validate_manifest(&manifest, &manifest_path)?; - - Ok(manifest) -} - -fn validate_manifest(manifest: &Manifest, path: &Path) -> Result<(), ManifestError> { - // Validate name - if manifest.name.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: "Project name cannot be empty".into(), - pointer: Some("/name".into()), - }); - } - if manifest.name.chars().any(|c| c.is_whitespace()) { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: "Project name cannot contain whitespace".into(), - pointer: Some("/name".into()), - }); - } - - // Validate version (basic check, could be more thorough if we want to enforce semver now) - if manifest.version.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: "Project version cannot be empty".into(), - pointer: Some("/version".into()), - }); - } - - // Validate dependencies - for (alias, spec) in &manifest.dependencies { - if alias.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: "Dependency alias cannot be empty".into(), - pointer: Some("/dependencies".into()), // Best effort pointer - }); - } - if alias.chars().any(|c| c.is_whitespace()) { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Dependency alias '{}' cannot contain whitespace", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - - match spec { - DependencySpec::Path(p) => { - if p.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Path for dependency '{}' cannot be empty", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - } - DependencySpec::Full(full) => { - match (full.path.as_ref(), full.git.as_ref()) { - (Some(_), Some(_)) => { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Dependency '{}' must specify exactly one source (path or git), but both were found", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - (None, None) => { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Dependency '{}' must specify exactly one source (path or git), but none were found", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - (Some(p), None) => { - if p.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Path for dependency '{}' cannot be empty", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - } - (None, Some(g)) => { - if g.trim().is_empty() { - return Err(ManifestError::Validation { - path: path.to_path_buf(), - message: format!("Git URL for dependency '{}' cannot be empty", alias), - pointer: Some(format!("/dependencies/{}", alias)), - }); - } - } - } - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_parse_minimal_manifest() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "my_project", - "version": "0.1.0" - }"#, - ) - .unwrap(); - - let manifest = load_manifest(dir.path()).unwrap(); - assert_eq!(manifest.name, "my_project"); - assert_eq!(manifest.version, "0.1.0"); - assert_eq!(manifest.kind, ManifestKind::App); - assert!(manifest.dependencies.is_empty()); - } - - #[test] - fn test_parse_full_manifest() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "full_project", - "version": "1.2.3", - "kind": "lib", - "dependencies": { - "std": "../std", - "core": { - "git": "https://github.com/prometeu/core", - "version": "v1.0" - } - } - }"#, - ) - .unwrap(); - - let manifest = load_manifest(dir.path()).unwrap(); - assert_eq!(manifest.name, "full_project"); - assert_eq!(manifest.version, "1.2.3"); - assert_eq!(manifest.kind, ManifestKind::Lib); - assert_eq!(manifest.dependencies.len(), 2); - - match manifest.dependencies.get("std").unwrap() { - DependencySpec::Path(p) => assert_eq!(p, "../std"), - _ => panic!("Expected path dependency"), - } - - match manifest.dependencies.get("core").unwrap() { - DependencySpec::Full(full) => { - assert_eq!(full.git.as_ref().unwrap(), "https://github.com/prometeu/core"); - assert_eq!(full.version.as_ref().unwrap(), "v1.0"); - assert!(full.path.is_none()); - } - _ => panic!("Expected full dependency"), - } - } - - #[test] - fn test_missing_name_error() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "version": "0.1.0" - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Json { .. }) => {} - _ => panic!("Expected JSON error due to missing name, got {:?}", result), - } - } - - #[test] - fn test_invalid_name_error() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "my project", - "version": "0.1.0" - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Validation { message, pointer, .. }) => { - assert!(message.contains("whitespace")); - assert_eq!(pointer.unwrap(), "/name"); - } - _ => panic!("Expected validation error due to invalid name, got {:?}", result), - } - } - - #[test] - fn test_invalid_dependency_shape_both_sources() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "test", - "version": "0.1.0", - "dependencies": { - "bad": { - "path": "./here", - "git": "https://there" - } - } - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Validation { message, pointer, .. }) => { - assert!(message.contains("exactly one source")); - assert_eq!(pointer.unwrap(), "/dependencies/bad"); - } - _ => panic!("Expected validation error due to both sources, got {:?}", result), - } - } - - #[test] - fn test_invalid_dependency_shape_no_source() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "test", - "version": "0.1.0", - "dependencies": { - "bad": { - "version": "1.0.0" - } - } - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Validation { message, pointer, .. }) => { - assert!(message.contains("exactly one source")); - assert_eq!(pointer.unwrap(), "/dependencies/bad"); - } - _ => panic!("Expected validation error due to no source, got {:?}", result), - } - } - - #[test] - fn test_invalid_dependency_empty_path() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "test", - "version": "0.1.0", - "dependencies": { - "empty": "" - } - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Validation { message, .. }) => { - assert!(message.contains("cannot be empty")); - } - _ => panic!("Expected validation error due to empty path, got {:?}", result), - } - } - - #[test] - fn test_invalid_dependency_alias_whitespace() { - let dir = tempdir().unwrap(); - let manifest_path = dir.path().join("prometeu.json"); - fs::write( - &manifest_path, - r#"{ - "name": "test", - "version": "0.1.0", - "dependencies": { - "bad alias": "../std" - } - }"#, - ) - .unwrap(); - - let result = load_manifest(dir.path()); - match result { - Err(ManifestError::Validation { message, .. }) => { - assert!(message.contains("whitespace")); - } - _ => panic!("Expected validation error due to whitespace in alias, got {:?}", result), - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/semantics/export_surface.rs b/crates/compiler/prometeu-compiler/src/semantics/export_surface.rs deleted file mode 100644 index 5d8121ce..00000000 --- a/crates/compiler/prometeu-compiler/src/semantics/export_surface.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::frontends::pbs::symbols::{SymbolKind, Visibility}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum ExportSurfaceKind { - Service, - DeclareType, // struct, storage struct, type alias - Function, // funções públicas (ex.: métodos de service expostos pelo SDK) -} - -impl ExportSurfaceKind { - pub fn from_symbol_kind(kind: SymbolKind) -> Option { - match kind { - SymbolKind::Service => Some(ExportSurfaceKind::Service), - SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => { - Some(ExportSurfaceKind::DeclareType) - } - // Em v0, permitimos exportar funções públicas — usado sobretudo para métodos de `service` - SymbolKind::Function => Some(ExportSurfaceKind::Function), - SymbolKind::Local => None, - } - } - - pub fn validate_visibility(kind: SymbolKind, vis: Visibility) -> Result<(), String> { - if vis == Visibility::Pub { - if Self::from_symbol_kind(kind).is_none() { - let kind_str = match kind { - SymbolKind::Function => "Functions", - _ => "This declaration", - }; - return Err(format!("{} are not exportable in this version.", kind_str)); - } - } - Ok(()) - } - - pub fn namespace(&self) -> crate::frontends::pbs::symbols::Namespace { - match self { - ExportSurfaceKind::Service => crate::frontends::pbs::symbols::Namespace::Type, - ExportSurfaceKind::DeclareType => crate::frontends::pbs::symbols::Namespace::Type, - ExportSurfaceKind::Function => crate::frontends::pbs::symbols::Namespace::Value, - } - } -} diff --git a/crates/compiler/prometeu-compiler/src/semantics/mod.rs b/crates/compiler/prometeu-compiler/src/semantics/mod.rs deleted file mode 100644 index 51e988b2..00000000 --- a/crates/compiler/prometeu-compiler/src/semantics/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod export_surface; diff --git a/crates/compiler/prometeu-compiler/src/sources.rs b/crates/compiler/prometeu-compiler/src/sources.rs deleted file mode 100644 index a6e266e8..00000000 --- a/crates/compiler/prometeu-compiler/src/sources.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::common::diagnostics::DiagnosticBundle; -use crate::manifest::{load_manifest, ManifestKind}; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::{Path, PathBuf}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ProjectSources { - pub main: Option, - pub files: Vec, - pub test_files: Vec, -} - -#[derive(Debug)] -pub enum SourceError { - Io(std::io::Error), - Manifest(crate::manifest::ManifestError), - MissingMain(PathBuf), - Diagnostics(DiagnosticBundle), -} - -impl std::fmt::Display for SourceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SourceError::Io(e) => write!(f, "IO error: {}", e), - SourceError::Manifest(e) => write!(f, "Manifest error: {}", e), - SourceError::MissingMain(path) => write!(f, "Missing entry point: {}", path.display()), - SourceError::Diagnostics(d) => write!(f, "Source diagnostics: {:?}", d), - } - } -} - -impl std::error::Error for SourceError {} - -impl From for SourceError { - fn from(e: std::io::Error) -> Self { - SourceError::Io(e) - } -} - -impl From for SourceError { - fn from(e: crate::manifest::ManifestError) -> Self { - SourceError::Manifest(e) - } -} - -impl From for SourceError { - fn from(d: DiagnosticBundle) -> Self { - SourceError::Diagnostics(d) - } -} - -// NOTE: Export surface discovery is a Frontend responsibility now. -// This module is intentionally discovery-only (file listing + main rules). - -pub fn discover(project_dir: &Path) -> Result { - let project_dir = project_dir.canonicalize()?; - let manifest = load_manifest(&project_dir)?; - - let main_modules_dir = project_dir.join("src/main/modules"); - let test_modules_dir = project_dir.join("src/test/modules"); - - let mut production_files = Vec::new(); - if main_modules_dir.exists() && main_modules_dir.is_dir() { - discover_recursive(&main_modules_dir, &mut production_files)?; - } - - let mut test_files = Vec::new(); - if test_modules_dir.exists() && test_modules_dir.is_dir() { - discover_recursive(&test_modules_dir, &mut test_files)?; - } - - // Sort files for determinism - production_files.sort(); - test_files.sort(); - - // Recommended main: src/main/modules/main.pbs - let main_path = main_modules_dir.join("main.pbs"); - let has_main = production_files.iter().any(|p| p == &main_path); - - let main = if has_main { Some(main_path) } else { None }; - - if manifest.kind == ManifestKind::App && main.is_none() { - return Err(SourceError::MissingMain(main_modules_dir.join("main.pbs"))); - } - - Ok(ProjectSources { - main, - files: production_files, - test_files, - }) -} - -fn discover_recursive(dir: &Path, files: &mut Vec) -> std::io::Result<()> { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - discover_recursive(&path, files)?; - } else if path.extension().map_or(false, |ext| ext == "pbs") { - files.push(path); - } - } - Ok(()) -} - -// build_exports removed: export collection belongs to Frontend (frontend-api contract). - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use tempfile::tempdir; - - #[test] - fn test_discover_app_with_main() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().canonicalize().unwrap(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "app", - "version": "0.1.0", - "kind": "app" - }"#, - ) - .unwrap(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - let main_pbs = project_dir.join("src/main/modules/main.pbs"); - fs::write(&main_pbs, "").unwrap(); - - let other_pbs = project_dir.join("src/main/modules/other.pbs"); - fs::write(&other_pbs, "").unwrap(); - - let sources = discover(&project_dir).unwrap(); - assert_eq!(sources.main, Some(main_pbs)); - assert_eq!(sources.files.len(), 2); - } - - #[test] - fn test_discover_app_missing_main() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().canonicalize().unwrap(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "app", - "version": "0.1.0", - "kind": "app" - }"#, - ) - .unwrap(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - fs::write(project_dir.join("src/main/modules/not_main.pbs"), "").unwrap(); - - let result = discover(&project_dir); - assert!(matches!(result, Err(SourceError::MissingMain(_)))); - } - - #[test] - fn test_discover_lib_without_main() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().canonicalize().unwrap(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "lib", - "version": "0.1.0", - "kind": "lib" - }"#, - ) - .unwrap(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - let lib_pbs = project_dir.join("src/main/modules/lib.pbs"); - fs::write(&lib_pbs, "").unwrap(); - - let sources = discover(&project_dir).unwrap(); - assert_eq!(sources.main, None); - assert_eq!(sources.files, vec![lib_pbs]); - } - - #[test] - fn test_discover_recursive() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().canonicalize().unwrap(); - - fs::write( - project_dir.join("prometeu.json"), - r#"{ - "name": "lib", - "version": "0.1.0", - "kind": "lib" - }"#, - ) - .unwrap(); - - fs::create_dir_all(project_dir.join("src/main/modules/utils")).unwrap(); - let main_pbs = project_dir.join("src/main/modules/main.pbs"); - let util_pbs = project_dir.join("src/main/modules/utils/util.pbs"); - fs::write(&main_pbs, "").unwrap(); - fs::write(&util_pbs, "").unwrap(); - - let sources = discover(&project_dir).unwrap(); - assert_eq!(sources.files.len(), 2); - assert!(sources.files.contains(&main_pbs)); - assert!(sources.files.contains(&util_pbs)); - } - - // No export-surface tests here; handled by Frontend implementations. -} diff --git a/crates/compiler/prometeu-compiler/tests/be_no_pbs_imports.rs b/crates/compiler/prometeu-compiler/tests/be_no_pbs_imports.rs deleted file mode 100644 index 1b6f99d0..00000000 --- a/crates/compiler/prometeu-compiler/tests/be_no_pbs_imports.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; - -fn collect_rs_files(dir: &Path, out: &mut Vec) { - if let Ok(entries) = fs::read_dir(dir) { - for e in entries.flatten() { - let path = e.path(); - if path.is_dir() { - collect_rs_files(&path, out); - } else if path.extension().and_then(|s| s.to_str()) == Some("rs") { - out.push(path); - } - } - } -} - -#[test] -fn backend_must_not_import_pbs() { - let crate_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let backend_dir = crate_root.join("src").join("backend"); - if !backend_dir.exists() { return; } - - let mut files = Vec::new(); - collect_rs_files(&backend_dir, &mut files); - - let mut offenders = Vec::new(); - for f in files { - if let Ok(src) = fs::read_to_string(&f) { - if src.contains("frontends::pbs") || src.contains("crate::frontends::pbs") { - offenders.push(f); - } - } - } - - if !offenders.is_empty() { - let list = offenders - .iter() - .map(|p| p.strip_prefix(&crate_root).unwrap_or(p).display().to_string()) - .collect::>() - .join("\n - "); - panic!( - "Backend must not import PBS modules (frontends::pbs). Offending files:\n - {}", - list - ); - } -} diff --git a/crates/compiler/prometeu-compiler/tests/diagnostics_span.rs b/crates/compiler/prometeu-compiler/tests/diagnostics_span.rs deleted file mode 100644 index b6f5c14c..00000000 --- a/crates/compiler/prometeu-compiler/tests/diagnostics_span.rs +++ /dev/null @@ -1,26 +0,0 @@ -use prometeu_analysis::{ids::FileId, span::Span}; -use prometeu_compiler::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity}; - -#[test] -fn diagnostic_span_is_valid_for_file() { - // Fixture simples - let text = "let x = 10;"; // len = 11 - let file = FileId(1); - - // Cria um diagnóstico com span válido: end exclusivo e <= len - let span = Span::new(file, 0, text.len() as u32); - assert!(span.end >= span.start); - assert!(span.end <= text.len() as u32); - - let diag = Diagnostic { - severity: Severity::Error, - code: "E_TEST".to_string(), - message: "testing".to_string(), - span, - related: vec![], - }; - let bundle = DiagnosticBundle { diagnostics: vec![diag] }; - - // Serialize para garantir que o span passa pelo pipeline sem panics - let _json = serde_json::to_string(&bundle).expect("must serialize diagnostics"); -} diff --git a/crates/compiler/prometeu-compiler/tests/export_conflicts.rs b/crates/compiler/prometeu-compiler/tests/export_conflicts.rs deleted file mode 100644 index 159aca19..00000000 --- a/crates/compiler/prometeu-compiler/tests/export_conflicts.rs +++ /dev/null @@ -1,292 +0,0 @@ -use prometeu_compiler::building::output::CompiledModule; -use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata}; -use prometeu_compiler::building::plan::{BuildStep, BuildTarget}; -use prometeu_compiler::common::files::FileManager; -use prometeu_compiler::deps::resolver::ProjectKey; -use language_api::types::{ExportItem, ItemName}; -use prometeu_compiler::frontends::pbs::adapter::PbsFrontendAdapter; -use prometeu_analysis::ids::ProjectId; -use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; -use tempfile::tempdir; - -use std::fs; - -#[test] -fn test_local_vs_dependency_conflict() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - // Dependency: sdk - let dep_key = ProjectKey { name: "sdk-impl".to_string(), version: "1.0.0".to_string() }; - let dep_id = ProjectId(0); - let mut dep_exports = BTreeMap::new(); - dep_exports.insert(ExportKey { - module_path: "math".to_string(), // normalized path - item: ExportItem::Type { name: ItemName::new("Vector").unwrap() }, - }, ExportMetadata { - func_idx: None, - is_host: false, - ty: None, - }); - - let dep_module = CompiledModule { - project_id: dep_id, - project_key: dep_key.clone(), - target: BuildTarget::Main, - exports: dep_exports, - imports: vec![], - const_pool: vec![], - code: vec![], - function_metas: vec![], - debug_info: None, - symbols: vec![], - }; - - let mut dep_modules: HashMap = HashMap::new(); - dep_modules.insert(dep_id, dep_module); - - // Main project has a LOCAL module named "sdk/math" - // By creating a file in src/main/modules/sdk/math/, the module path becomes "sdk/math" - fs::create_dir_all(project_dir.join("src/main/modules/sdk/math")).unwrap(); - fs::write(project_dir.join("src/main/modules/sdk/math/local.pbs"), "pub declare struct Vector(x: int)").unwrap(); - - let main_key = ProjectKey { name: "main".to_string(), version: "0.1.0".to_string() }; - let main_id = ProjectId(1); - let mut deps: BTreeMap = BTreeMap::new(); - deps.insert("sdk".to_string(), ProjectId(0)); - - let step = BuildStep { - project_id: main_id, - project_key: main_key, - project_dir, - target: BuildTarget::Main, - sources: vec![PathBuf::from("src/main/modules/sdk/math/local.pbs")], - deps, - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let result = compile_project(step, &dep_modules, &fe, &mut file_manager); - - match result { - Err(CompileError::DuplicateExport { symbol, .. }) => { - assert_eq!(symbol, "Vector"); - }, - _ => panic!("Expected DuplicateExport error, got {:?}", result), - } -} - -#[test] -fn test_aliased_dependency_conflict() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - // Dependency 1: exports "b/c:Vector" - let dep1_key = ProjectKey { name: "p1".to_string(), version: "1.0.0".to_string() }; - let dep1_id = ProjectId(0); - let mut dep1_exports = BTreeMap::new(); - dep1_exports.insert(ExportKey { - module_path: "b/c".to_string(), - item: ExportItem::Type { name: ItemName::new("Vector").unwrap() }, - }, ExportMetadata { - func_idx: None, - is_host: false, - ty: None, - }); - let dep1_module = CompiledModule { - project_id: dep1_id, - project_key: dep1_key.clone(), - target: BuildTarget::Main, - exports: dep1_exports, - imports: vec![], - const_pool: vec![], - code: vec![], - function_metas: vec![], - debug_info: None, - symbols: vec![], - }; - - // Dependency 2: exports "c:Vector" - let dep2_key = ProjectKey { name: "p2".to_string(), version: "1.0.0".to_string() }; - let dep2_id = ProjectId(1); - let mut dep2_exports = BTreeMap::new(); - dep2_exports.insert(ExportKey { - module_path: "c".to_string(), - item: ExportItem::Type { name: ItemName::new("Vector").unwrap() }, - }, ExportMetadata { - func_idx: None, - is_host: false, - ty: None, - }); - let dep2_module = CompiledModule { - project_id: dep2_id, - project_key: dep2_key.clone(), - target: BuildTarget::Main, - exports: dep2_exports, - imports: vec![], - const_pool: vec![], - code: vec![], - function_metas: vec![], - debug_info: None, - symbols: vec![], - }; - - let mut dep_modules: HashMap = HashMap::new(); - dep_modules.insert(dep1_id, dep1_module); - dep_modules.insert(dep2_id, dep2_module); - - let main_key = ProjectKey { name: "main".to_string(), version: "0.1.0".to_string() }; - let main_id = ProjectId(2); - let mut deps: BTreeMap = BTreeMap::new(); - deps.insert("a".to_string(), ProjectId(0)); - deps.insert("a/b".to_string(), ProjectId(1)); - - let step = BuildStep { - project_id: main_id, - project_key: main_key, - project_dir, - target: BuildTarget::Main, - sources: vec![], - deps, - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let result = compile_project(step, &dep_modules, &fe, &mut file_manager); - - match result { - Err(CompileError::DuplicateExport { symbol, .. }) => { - assert_eq!(symbol, "Vector"); - }, - _ => panic!("Expected DuplicateExport error, got {:?}", result), - } -} - -#[test] -fn test_mixed_main_test_modules() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules/math")).unwrap(); - fs::write(project_dir.join("src/main/modules/math/Vector.pbs"), "pub declare struct Vector(x: int)").unwrap(); - - fs::create_dir_all(project_dir.join("src/test/modules/foo")).unwrap(); - fs::write(project_dir.join("src/test/modules/foo/Test.pbs"), "pub declare struct Test(x: int)").unwrap(); - - let project_key = ProjectKey { name: "mixed".to_string(), version: "0.1.0".to_string() }; - let project_id = ProjectId(0); - let step = BuildStep { - project_id, - project_key, - project_dir, - target: BuildTarget::Main, - sources: vec![ - PathBuf::from("src/main/modules/math/Vector.pbs"), - PathBuf::from("src/test/modules/foo/Test.pbs"), - ], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap(); - - // Both should be in exports with normalized paths - assert!(compiled.exports.keys().any(|k| k.module_path == "math")); - assert!(compiled.exports.keys().any(|k| k.module_path == "foo")); -} - -#[test] -fn test_module_merging_same_directory() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap(); - fs::write(project_dir.join("src/main/modules/gfx/api.pbs"), "pub declare struct Gfx(id: int)").unwrap(); - fs::write(project_dir.join("src/main/modules/gfx/colors.pbs"), "pub declare struct Color(r: int)").unwrap(); - - let project_key = ProjectKey { name: "merge".to_string(), version: "0.1.0".to_string() }; - let project_id = ProjectId(0); - let step = BuildStep { - project_id, - project_key, - project_dir, - target: BuildTarget::Main, - sources: vec![ - PathBuf::from("src/main/modules/gfx/api.pbs"), - PathBuf::from("src/main/modules/gfx/colors.pbs"), - ], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap(); - - // Both should be in the same module "gfx" - assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Gfx"))); - assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Color"))); -} - -#[test] -fn test_duplicate_symbol_in_same_module_different_files() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap(); - fs::write(project_dir.join("src/main/modules/gfx/a.pbs"), "pub declare struct Gfx(id: int)").unwrap(); - fs::write(project_dir.join("src/main/modules/gfx/b.pbs"), "pub declare struct Gfx(id: int)").unwrap(); - - let project_key = ProjectKey { name: "dup".to_string(), version: "0.1.0".to_string() }; - let project_id = ProjectId(0); - let step = BuildStep { - project_id, - project_key, - project_dir, - target: BuildTarget::Main, - sources: vec![ - PathBuf::from("src/main/modules/gfx/a.pbs"), - PathBuf::from("src/main/modules/gfx/b.pbs"), - ], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let result = compile_project(step, &HashMap::new(), &fe, &mut file_manager); - assert!(result.is_err()); - // Should be a frontend error (duplicate symbol) -} - -#[test] -fn test_root_module_merging() { - let dir = tempdir().unwrap(); - let project_dir = dir.path().to_path_buf(); - - fs::create_dir_all(project_dir.join("src/main/modules")).unwrap(); - fs::write(project_dir.join("src/main/modules/main.pbs"), "pub declare struct Main(id: int)").unwrap(); - fs::write(project_dir.join("src/main/modules/utils.pbs"), "pub declare struct Utils(id: int)").unwrap(); - - let project_key = ProjectKey { name: "root-merge".to_string(), version: "0.1.0".to_string() }; - let project_id = ProjectId(0); - let step = BuildStep { - project_id, - project_key, - project_dir, - target: BuildTarget::Main, - sources: vec![ - PathBuf::from("src/main/modules/main.pbs"), - PathBuf::from("src/main/modules/utils.pbs"), - ], - deps: BTreeMap::new(), - }; - - let mut file_manager = FileManager::new(); - let fe = PbsFrontendAdapter; - let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap(); - - // Both should be in the root module "" - assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Main"))); - assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Utils"))); -} diff --git a/crates/compiler/prometeu-compiler/tests/generate_canonical_goldens.rs b/crates/compiler/prometeu-compiler/tests/generate_canonical_goldens.rs deleted file mode 100644 index f9d8c563..00000000 --- a/crates/compiler/prometeu-compiler/tests/generate_canonical_goldens.rs +++ /dev/null @@ -1,67 +0,0 @@ -// use prometeu_bytecode::disasm::disasm; -// use prometeu_bytecode::BytecodeLoader; -// use prometeu_compiler::compiler::compile; -// use prometeu_compiler::frontends::pbs::parser::Parser; -// use prometeu_compiler::common::spans::FileId; -// use prometeu_analysis::NameInterner; -// use std::fs; -// use std::path::Path; -// -// #[test] -// fn generate_canonical_goldens() { -// println!("CWD: {:?}", std::env::current_dir().unwrap()); -// let project_dir = Path::new("../../test-cartridges/canonical"); -// if !project_dir.exists() { -// // Fallback for when running from project root (some IDEs/environments) -// let project_dir = Path::new("../../../../test-cartridges/canonical"); -// if !project_dir.exists() { -// panic!("Could not find project directory at ../../test-cartridges/canonical or test-cartridges/canonical"); -// } -// } -// -// // We need a stable path for the actual compilation which might use relative paths internally -// let project_dir = if Path::new("../../test-cartridges/canonical").exists() { -// Path::new("../../test-cartridges/canonical") -// } else { -// Path::new("../../../../test-cartridges/canonical") -// }; -// -// let unit = compile(project_dir).map_err(|e| { -// println!("Compilation Error: {}", e); -// e -// }).expect("Failed to compile canonical cartridge"); -// -// let golden_dir = project_dir.join("golden"); -// fs::create_dir_all(&golden_dir).unwrap(); -// -// // 1. Bytecode (.pbc) -// fs::write(golden_dir.join("program.pbc"), &unit.rom).unwrap(); -// -// // 2. Disassembly -// let module = BytecodeLoader::load(&unit.rom).expect("Failed to load BytecodeModule"); -// let instrs = disasm(&module.code).expect("Failed to disassemble"); -// let mut disasm_text = String::new(); -// for instr in instrs { -// let operands_str = instr.operands.iter() -// .map(|o| format!("{:?}", o)) -// .collect::>() -// .join(" "); -// let line = if operands_str.is_empty() { -// format!("{:04X} {:?}\n", instr.pc, instr.opcode) -// } else { -// format!("{:04X} {:?} {}\n", instr.pc, instr.opcode, operands_str.trim()) -// }; -// disasm_text.push_str(&line); -// } -// fs::write(golden_dir.join("program.disasm.txt"), disasm_text).unwrap(); -// -// // 3. AST JSON -// let source = fs::read_to_string(project_dir.join("src/main/modules/main.pbs")).unwrap(); -// let mut interner = NameInterner::new(); -// let mut parser = Parser::new(&source, FileId(0), &mut interner); -// let parsed = parser.parse_file().expect("Failed to parse AST"); -// let ast_json = serde_json::to_string_pretty(parsed.arena.kind(parsed.root)).unwrap(); -// fs::write(golden_dir.join("ast.json"), ast_json).unwrap(); -// -// println!("Golden artifacts generated in test-cartridges/canonical/golden/"); -// } diff --git a/crates/compiler/prometeu-compiler/tests/hip_conformance.rs b/crates/compiler/prometeu-compiler/tests/hip_conformance.rs deleted file mode 100644 index f047554e..00000000 --- a/crates/compiler/prometeu-compiler/tests/hip_conformance.rs +++ /dev/null @@ -1,107 +0,0 @@ -use prometeu_compiler::backend::emit_bytecode::emit_module; -use prometeu_compiler::ir_core::ids::{ConstId as CoreConstId, FieldId, FunctionId, TypeId as CoreTypeId, ValueId}; -use prometeu_compiler::ir_core::{self, Block, ConstPool, ConstantValue, Instr, InstrKind as CoreInstrKind, Program, Terminator}; -use prometeu_compiler::ir_lang::InstrKind; -use prometeu_compiler::lowering::lower_program; -use std::collections::HashMap; - -#[test] -fn test_hip_conformance_core_to_vm_to_bytecode() { - // 1. Setup Core IR Program - let mut const_pool = ConstPool::new(); - let _val_const = const_pool.insert(ConstantValue::Int(42)); - - let type_id = CoreTypeId(10); - let field_id = FieldId(1); - - let mut field_offsets = HashMap::new(); - field_offsets.insert(field_id, 0); // Field at offset 0 - - let mut field_types = HashMap::new(); - field_types.insert(field_id, ir_core::Type::Int); - - let program = Program { - const_pool, - modules: vec![ir_core::Module { - name: "conformance".to_string(), - functions: vec![ir_core::Function { - id: FunctionId(1), - name: "main".to_string(), - sig: { - let mut i = ir_core::global_signature_interner().lock().unwrap(); - i.intern(ir_core::Signature { params: vec![], return_type: ir_core::Type::Void }) - }, - param_slots: 0, - local_slots: 0, - return_slots: 0, - params: vec![], - return_type: ir_core::Type::Void, - local_types: HashMap::new(), - blocks: vec![Block { - id: 0, - instrs: vec![ - // allocates a storage struct - Instr::from(CoreInstrKind::Alloc { ty: type_id, slots: 2 }), - Instr::from(CoreInstrKind::SetLocal(0)), // x = alloc - - // mutates a field - Instr::from(CoreInstrKind::BeginMutate { gate: ValueId(0) }), - Instr::from(CoreInstrKind::PushConst(CoreConstId(0))), - Instr::from(CoreInstrKind::SetLocal(1)), // v = 42 - Instr::from(CoreInstrKind::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) }), - Instr::from(CoreInstrKind::EndMutate), - - // peeks value - Instr::from(CoreInstrKind::BeginPeek { gate: ValueId(0) }), - Instr::from(CoreInstrKind::GateLoadField { gate: ValueId(0), field: field_id }), - Instr::from(CoreInstrKind::EndPeek), - - Instr::from(CoreInstrKind::Pop), // clean up the peeked value - ], - terminator: Terminator::Return, - }], - }], - }], - field_offsets, - field_types, - }; - - // 2. Lower to VM IR - let vm_module = lower_program(&program).expect("Lowering failed"); - let func = &vm_module.functions[0]; - - // Assert VM IR contains required instructions - let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); - - assert!(kinds.iter().any(|k| matches!(k, InstrKind::Alloc { type_id: tid, slots: 2 } if tid.0 == 10)), "Missing correct Alloc"); - assert!(kinds.contains(&&InstrKind::GateBeginMutate), "Missing GateBeginMutate"); - assert!(kinds.contains(&&InstrKind::GateEndMutate), "Missing GateEndMutate"); - assert!(kinds.iter().any(|k| matches!(k, InstrKind::GateStore { offset: 0 })), "Missing correct GateStore"); - assert!(kinds.contains(&&InstrKind::GateBeginPeek), "Missing GateBeginPeek"); - assert!(kinds.contains(&&InstrKind::GateEndPeek), "Missing GateEndPeek"); - assert!(kinds.iter().any(|k| matches!(k, InstrKind::GateLoad { offset: 0 })), "Missing correct GateLoad"); - - // RC ops - assert!(kinds.contains(&&InstrKind::GateRetain), "Missing GateRetain"); - assert!(kinds.contains(&&InstrKind::GateRelease), "Missing GateRelease"); - - // 3. Emit Bytecode - let emit_result = emit_module(&vm_module).expect("Emission failed"); - let bytecode = emit_result.rom; - - // 4. Assert industrial PBS\0 format - use prometeu_bytecode::BytecodeLoader; - let module = BytecodeLoader::load(&bytecode).expect("Failed to parse industrial PBC"); - assert_eq!(&bytecode[0..4], b"PBS\0"); - - // 5. Verify a few key instructions in the code section to ensure ABI stability - // We don't do a full byte-for-byte check of the entire file here as the section - // table offsets vary, but we check the instruction stream. - let instrs = module.code; - - // Alloc { tid: 10, slots: 2 } -> 0x60 0x00, 0x0a 0x00 0x00 0x00, 0x02 0x00 0x00 0x00 - assert!(instrs.windows(10).any(|w| w == &[0x60, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00])); - - // PushConst 1 (42) -> 0x10 0x00, 0x01 0x00, 0x00, 0x00 - assert!(instrs.windows(6).any(|w| w == &[0x10, 0x00, 0x01, 0x00, 0x00, 0x00])); -} diff --git a/crates/compiler/prometeu-compiler/tests/link_integration.rs b/crates/compiler/prometeu-compiler/tests/link_integration.rs deleted file mode 100644 index a9095197..00000000 --- a/crates/compiler/prometeu-compiler/tests/link_integration.rs +++ /dev/null @@ -1,83 +0,0 @@ -// use prometeu_compiler::compiler::compile; -// use std::path::PathBuf; -// use std::sync::Arc; -// use prometeu_compiler::ir_vm::Value; -// use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, HostContext, HostReturn, NativeInterface, PadBridge, TouchBridge}; -// -// struct SimpleNative; -// impl NativeInterface for SimpleNative { -// fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { -// Ok(()) -// } -// } -// -// struct SimpleHardware { -// gfx: Gfx, -// audio: Audio, -// pad: Pad, -// touch: Touch, -// assets: AssetManager, -// } -// -// impl SimpleHardware { -// fn new() -> Self { -// let banks = Arc::new(MemoryBanks::new()); -// Self { -// gfx: Gfx::new(320, 240, banks.clone()), -// audio: Audio::new(banks.clone()), -// pad: Pad::default(), -// touch: Touch::default(), -// assets: AssetManager::new(vec![], vec![], banks.clone(), banks.clone()), -// } -// } -// } -// -// impl HardwareBridge for SimpleHardware { -// fn gfx(&self) -> &dyn GfxBridge { &self.gfx } -// fn gfx_mut(&mut self) -> &mut dyn GfxBridge { &mut self.gfx } -// fn audio(&self) -> &dyn AudioBridge { &self.audio } -// fn audio_mut(&mut self) -> &mut dyn AudioBridge { &mut self.audio } -// fn pad(&self) -> &dyn PadBridge { &self.pad } -// fn pad_mut(&mut self) -> &mut dyn PadBridge { &mut self.pad } -// fn touch(&self) -> &dyn TouchBridge { &self.touch } -// fn touch_mut(&mut self) -> &mut dyn TouchBridge { &mut self.touch } -// fn assets(&self) -> &dyn AssetBridge { &self.assets } -// fn assets_mut(&mut self) -> &mut dyn AssetBridge { &mut self.assets } -// } -// -// #[test] -// fn test_integration_test01_link() { -// let project_dir = PathBuf::from("../../test-cartridges/test01"); -// // Since the test runs from crates/prometeu-compiler, we need to adjust path if necessary. -// // Actually, usually tests run from the workspace root if using cargo test --workspace, -// // but if running from the crate dir, it's different. -// -// // Let's try absolute path or relative to project root. -// let mut root_dir = std::env::current_dir().unwrap(); -// while !root_dir.join("test-cartridges").exists() { -// if let Some(parent) = root_dir.parent() { -// root_dir = parent.to_path_buf(); -// } else { -// break; -// } -// } -// let _project_dir = root_dir.join("test-cartridges/test01"); -// -// let unit = compile(&project_dir).expect("Failed to compile and link"); -// -// let mut vm = VirtualMachine::default(); -// // Use initialize to load the ROM; entrypoint must be numeric or empty (defaults to 0) -// vm.initialize(unit.rom, "frame").expect("Failed to initialize VM"); -// -// let mut native = SimpleNative; -// let mut hw = SimpleHardware::new(); -// let mut ctx = HostContext::new(Some(&mut hw)); -// -// // Run for a bit -// let report = vm.run_budget(1000, &mut native, &mut ctx).expect("VM execution failed"); -// -// // It should not trap. test01 might loop or return. -// if let LogicalFrameEndingReason::Trap(t) = report.reason { -// panic!("VM trapped: {:?}", t); -// } -// } diff --git a/crates/compiler/prometeu-abi/Cargo.toml b/crates/compiler/prometeu-core/Cargo.toml similarity index 90% rename from crates/compiler/prometeu-abi/Cargo.toml rename to crates/compiler/prometeu-core/Cargo.toml index 0fda9a3d..da0e0cf8 100644 --- a/crates/compiler/prometeu-abi/Cargo.toml +++ b/crates/compiler/prometeu-core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "prometeu-abi" +name = "prometeu-core" version = "0.1.0" edition = "2024" license.workspace = true diff --git a/crates/compiler/prometeu-core/src/lib.rs b/crates/compiler/prometeu-core/src/lib.rs new file mode 100644 index 00000000..07f283b9 --- /dev/null +++ b/crates/compiler/prometeu-core/src/lib.rs @@ -0,0 +1,3 @@ +mod source; + +pub use source::*; \ No newline at end of file diff --git a/crates/compiler/prometeu-core/src/source/diagnostics.rs b/crates/compiler/prometeu-core/src/source/diagnostics.rs new file mode 100644 index 00000000..fcd73ef6 --- /dev/null +++ b/crates/compiler/prometeu-core/src/source/diagnostics.rs @@ -0,0 +1,72 @@ +use serde::{Serialize, Serializer}; +use crate::Span; + +#[derive(Debug, Clone, PartialEq)] +pub enum Severity { + Error, + Warning, +} + +impl Serialize for Severity { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Severity::Error => serializer.serialize_str("error"), + Severity::Warning => serializer.serialize_str("warning"), + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct Diagnostic { + pub severity: Severity, + pub code: String, + pub message: String, + pub span: Span, + pub related: Vec<(String, Span)>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DiagnosticBundle { + pub diagnostics: Vec, +} + +impl DiagnosticBundle { + pub fn new() -> Self { + Self { + diagnostics: Vec::new(), + } + } + + pub fn push(&mut self, diagnostic: Diagnostic) { + self.diagnostics.push(diagnostic); + } + + pub fn error(code: &str, message: String, span: Span) -> Self { + let mut bundle = Self::new(); + bundle.push(Diagnostic { + severity: Severity::Error, + code: code.to_string(), + message, + span, + related: Vec::new(), + }); + bundle + } + + pub fn has_errors(&self) -> bool { + self.diagnostics + .iter() + .any(|d| matches!(d.severity, Severity::Error)) + } +} + +impl From for DiagnosticBundle { + fn from(diagnostic: Diagnostic) -> Self { + let mut bundle = Self::new(); + bundle.push(diagnostic); + bundle + } +} diff --git a/crates/compiler/prometeu-analysis/src/file_db.rs b/crates/compiler/prometeu-core/src/source/file_db.rs similarity index 57% rename from crates/compiler/prometeu-analysis/src/file_db.rs rename to crates/compiler/prometeu-core/src/source/file_db.rs index a6485aac..fb8b93c5 100644 --- a/crates/compiler/prometeu-analysis/src/file_db.rs +++ b/crates/compiler/prometeu-core/src/source/file_db.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; -use crate::ids::FileId; +use crate::FileId; +use crate::LineIndex; #[derive(Default)] pub struct FileDB { @@ -13,11 +14,6 @@ struct FileData { line_index: LineIndex, } -pub struct LineIndex { - line_starts: Vec, - total_len: u32, -} - impl FileDB { pub fn new() -> Self { Self { @@ -70,38 +66,3 @@ impl FileDB { } } -impl LineIndex { - pub fn new(text: &str) -> Self { - let mut line_starts = vec![0]; - for (offset, c) in text.char_indices() { - if c == '\n' { - line_starts.push((offset + 1) as u32); - } - } - Self { - line_starts, - total_len: text.len() as u32, - } - } - - pub fn offset_to_line_col(&self, offset: u32) -> (u32, u32) { - let line = match self.line_starts.binary_search(&offset) { - Ok(line) => line as u32, - Err(line) => (line - 1) as u32, - }; - let col = offset - self.line_starts[line as usize]; - (line, col) - } - - pub fn line_col_to_offset(&self, line: u32, col: u32) -> Option { - let start = *self.line_starts.get(line as usize)?; - let offset = start + col; - - let next_start = self.line_starts.get(line as usize + 1).copied().unwrap_or(self.total_len); - if offset < next_start || (offset == next_start && offset == self.total_len) { - Some(offset) - } else { - None - } - } -} diff --git a/crates/compiler/prometeu-analysis/src/ids.rs b/crates/compiler/prometeu-core/src/source/ids.rs similarity index 92% rename from crates/compiler/prometeu-analysis/src/ids.rs rename to crates/compiler/prometeu-core/src/source/ids.rs index 35aef095..680a78db 100644 --- a/crates/compiler/prometeu-analysis/src/ids.rs +++ b/crates/compiler/prometeu-core/src/source/ids.rs @@ -1,6 +1,3 @@ -//! Canonical ID newtypes used across the Prometeu workspace. -//! Keep this crate low-level and independent from higher layers. - macro_rules! define_id { ($name:ident) => { #[repr(transparent)] diff --git a/crates/compiler/prometeu-core/src/source/line_index.rs b/crates/compiler/prometeu-core/src/source/line_index.rs new file mode 100644 index 00000000..0cc01271 --- /dev/null +++ b/crates/compiler/prometeu-core/src/source/line_index.rs @@ -0,0 +1,40 @@ +pub struct LineIndex { + line_starts: Vec, + total_len: u32, +} + +impl LineIndex { + pub fn new(text: &str) -> Self { + let mut line_starts = vec![0]; + for (offset, c) in text.char_indices() { + if c == '\n' { + line_starts.push((offset + 1) as u32); + } + } + Self { + line_starts, + total_len: text.len() as u32, + } + } + + pub fn offset_to_line_col(&self, offset: u32) -> (u32, u32) { + let line = match self.line_starts.binary_search(&offset) { + Ok(line) => line as u32, + Err(line) => (line - 1) as u32, + }; + let col = offset - self.line_starts[line as usize]; + (line, col) + } + + pub fn line_col_to_offset(&self, line: u32, col: u32) -> Option { + let start = *self.line_starts.get(line as usize)?; + let offset = start + col; + + let next_start = self.line_starts.get(line as usize + 1).copied().unwrap_or(self.total_len); + if offset < next_start || (offset == next_start && offset == self.total_len) { + Some(offset) + } else { + None + } + } +} diff --git a/crates/compiler/prometeu-core/src/source/mod.rs b/crates/compiler/prometeu-core/src/source/mod.rs new file mode 100644 index 00000000..69551f4a --- /dev/null +++ b/crates/compiler/prometeu-core/src/source/mod.rs @@ -0,0 +1,13 @@ +mod ids; +mod span; +mod file_db; +mod name_interner; +mod diagnostics; +mod line_index; + +pub use ids::*; +pub use span::Span; +pub use file_db::FileDB; +pub use line_index::LineIndex; +pub use name_interner::NameInterner; +pub use diagnostics::*; \ No newline at end of file diff --git a/crates/compiler/prometeu-analysis/src/interner.rs b/crates/compiler/prometeu-core/src/source/name_interner.rs similarity index 98% rename from crates/compiler/prometeu-analysis/src/interner.rs rename to crates/compiler/prometeu-core/src/source/name_interner.rs index 2122124a..1381ee2a 100644 --- a/crates/compiler/prometeu-analysis/src/interner.rs +++ b/crates/compiler/prometeu-core/src/source/name_interner.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use crate::ids::NameId; +use crate::NameId; #[derive(Debug, Default, Clone)] pub struct NameInterner { diff --git a/crates/compiler/prometeu-analysis/src/span.rs b/crates/compiler/prometeu-core/src/source/span.rs similarity index 95% rename from crates/compiler/prometeu-analysis/src/span.rs rename to crates/compiler/prometeu-core/src/source/span.rs index 5d8042b9..b42d3c27 100644 --- a/crates/compiler/prometeu-analysis/src/span.rs +++ b/crates/compiler/prometeu-core/src/source/span.rs @@ -1,4 +1,4 @@ -use crate::ids::FileId; +use crate::FileId; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Span { diff --git a/crates/compiler/prometeu-analysis/tests/file_db_line_index.rs b/crates/compiler/prometeu-core/tests/source/file_db_line_index.rs similarity index 98% rename from crates/compiler/prometeu-analysis/tests/file_db_line_index.rs rename to crates/compiler/prometeu-core/tests/source/file_db_line_index.rs index db505f9b..df7e6893 100644 --- a/crates/compiler/prometeu-analysis/tests/file_db_line_index.rs +++ b/crates/compiler/prometeu-core/tests/source/file_db_line_index.rs @@ -1,4 +1,4 @@ -use prometeu_analysis::{FileDB, LineIndex}; +use prometeu_core::{FileDB, LineIndex}; #[test] fn test_line_index_roundtrip() { diff --git a/crates/compiler/prometeu-analysis/tests/span_tests.rs b/crates/compiler/prometeu-core/tests/source/span_tests.rs similarity index 85% rename from crates/compiler/prometeu-analysis/tests/span_tests.rs rename to crates/compiler/prometeu-core/tests/source/span_tests.rs index b58ee7c9..657a975b 100644 --- a/crates/compiler/prometeu-analysis/tests/span_tests.rs +++ b/crates/compiler/prometeu-core/tests/source/span_tests.rs @@ -1,4 +1,4 @@ -use prometeu_analysis::{ids::FileId, span::Span}; +use prometeu_core::{FileId, Span}; #[test] fn span_end_is_exclusive() { diff --git a/crates/compiler/prometeu-deps/Cargo.toml b/crates/compiler/prometeu-deps/Cargo.toml new file mode 100644 index 00000000..af9a5d9c --- /dev/null +++ b/crates/compiler/prometeu-deps/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "prometeu-deps" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +prometeu-core = { path = "../prometeu-core" } + +[features] +default = [] diff --git a/crates/compiler/prometeu-deps/src/lib.rs b/crates/compiler/prometeu-deps/src/lib.rs new file mode 100644 index 00000000..991124a5 --- /dev/null +++ b/crates/compiler/prometeu-deps/src/lib.rs @@ -0,0 +1 @@ +mod project_registry; \ No newline at end of file diff --git a/crates/compiler/prometeu-compiler/src/analysis/project_registry.rs b/crates/compiler/prometeu-deps/src/project_registry.rs similarity index 98% rename from crates/compiler/prometeu-compiler/src/analysis/project_registry.rs rename to crates/compiler/prometeu-deps/src/project_registry.rs index 55c29862..36fc07cf 100644 --- a/crates/compiler/prometeu-compiler/src/analysis/project_registry.rs +++ b/crates/compiler/prometeu-deps/src/project_registry.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; - -use prometeu_analysis::ids::ProjectId; +use prometeu_core::ProjectId; #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct ProjectKey { diff --git a/crates/language-api/Cargo.toml b/crates/compiler/prometeu-language-api/Cargo.toml similarity index 92% rename from crates/language-api/Cargo.toml rename to crates/compiler/prometeu-language-api/Cargo.toml index c1241411..64020d67 100644 --- a/crates/language-api/Cargo.toml +++ b/crates/compiler/prometeu-language-api/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "language-api" +name = "prometeu-language-api" version = "0.1.0" edition = "2021" license = "MIT" diff --git a/crates/compiler/prometeu-language-api/src/lib.rs b/crates/compiler/prometeu-language-api/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/crates/compiler/prometeu-language-api/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/compiler/prometeu-lowering/Cargo.toml b/crates/compiler/prometeu-lowering/Cargo.toml new file mode 100644 index 00000000..60035bed --- /dev/null +++ b/crates/compiler/prometeu-lowering/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "prometeu-lowering" +version = "0.1.0" +edition = "2021" +license.workspace = true +repository.workspace = true + +[dependencies] +prometeu-bytecode = { path = "../prometeu-bytecode" } +prometeu-core = { path = "../prometeu-core" } +prometeu-language-api = { path = "../prometeu-language-api" } +clap = { version = "4.5.54", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +anyhow = "1.0.100" +pathdiff = "0.2.1" + +[dev-dependencies] +tempfile = "3.10.1" diff --git a/crates/compiler/prometeu-lowering/src/lib.rs b/crates/compiler/prometeu-lowering/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/console/prometeu-drivers/Cargo.toml b/crates/console/prometeu-drivers/Cargo.toml index c7388913..7fc1aa9a 100644 --- a/crates/console/prometeu-drivers/Cargo.toml +++ b/crates/console/prometeu-drivers/Cargo.toml @@ -7,5 +7,5 @@ license.workspace = true [dependencies] serde_json = "1.0.149" prometeu-vm = { path = "../prometeu-vm" } -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-hal = { path = "../prometeu-hal" } diff --git a/crates/console/prometeu-drivers/tests/heartbeat.rs b/crates/console/prometeu-drivers/tests/heartbeat.rs index a3e3f8b2..e56e95a9 100644 --- a/crates/console/prometeu-drivers/tests/heartbeat.rs +++ b/crates/console/prometeu-drivers/tests/heartbeat.rs @@ -1,6 +1,6 @@ // use std::fs; // use std::path::Path; -// use prometeu_abi::{Value, VmFault}; +// use prometeu_core::{Value, VmFault}; // use prometeu_drivers::hardware::Hardware; // use prometeu_hal::{HostContext, HostReturn, NativeInterface}; // use prometeu_vm::{LogicalFrameEndingReason, VirtualMachine}; diff --git a/crates/console/prometeu-firmware/Cargo.toml b/crates/console/prometeu-firmware/Cargo.toml index 50e569d9..7df91e12 100644 --- a/crates/console/prometeu-firmware/Cargo.toml +++ b/crates/console/prometeu-firmware/Cargo.toml @@ -9,7 +9,7 @@ prometeu-drivers = { path = "../prometeu-drivers" } prometeu-vm = { path = "../prometeu-vm" } prometeu-system = { path = "../prometeu-system" } prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-hal = { path = "../prometeu-hal" } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/crates/console/prometeu-hal/Cargo.toml b/crates/console/prometeu-hal/Cargo.toml index b6726385..8f218611 100644 --- a/crates/console/prometeu-hal/Cargo.toml +++ b/crates/console/prometeu-hal/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" license.workspace = true [dependencies] -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" \ No newline at end of file diff --git a/crates/console/prometeu-hal/src/debugger_protocol.rs b/crates/console/prometeu-hal/src/debugger_protocol.rs index df80c143..e9779149 100644 --- a/crates/console/prometeu-hal/src/debugger_protocol.rs +++ b/crates/console/prometeu-hal/src/debugger_protocol.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use prometeu_abi::Value; +use prometeu_bytecode::Value; use crate::cartridge::AppMode; pub const DEVTOOLS_PROTOCOL_VERSION: u32 = 1; diff --git a/crates/console/prometeu-hal/src/host_context.rs b/crates/console/prometeu-hal/src/host_context.rs index 22c9ae53..714d44df 100644 --- a/crates/console/prometeu-hal/src/host_context.rs +++ b/crates/console/prometeu-hal/src/host_context.rs @@ -1,5 +1,5 @@ -use prometeu_abi::VmFault; use crate::hardware_bridge::HardwareBridge; +use crate::vm_fault::VmFault; pub struct HostContext<'a> { pub hw: Option<&'a mut dyn HardwareBridge>, diff --git a/crates/console/prometeu-hal/src/host_return.rs b/crates/console/prometeu-hal/src/host_return.rs index 780bd8be..41e32b6f 100644 --- a/crates/console/prometeu-hal/src/host_return.rs +++ b/crates/console/prometeu-hal/src/host_return.rs @@ -1,5 +1,5 @@ -use prometeu_abi::Value; -use prometeu_abi::VmFault; +use prometeu_bytecode::Value; +use crate::vm_fault::VmFault; pub struct HostReturn<'a> { stack: &'a mut Vec diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index f36f3780..9b926251 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -25,6 +25,7 @@ pub mod window; pub mod syscalls; pub mod telemetry; pub mod log; +pub mod vm_fault; pub use asset_bridge::AssetBridge; pub use audio_bridge::{AudioBridge, LoopMode}; diff --git a/crates/console/prometeu-hal/src/native_helpers.rs b/crates/console/prometeu-hal/src/native_helpers.rs index bef54d8e..df0154ab 100644 --- a/crates/console/prometeu-hal/src/native_helpers.rs +++ b/crates/console/prometeu-hal/src/native_helpers.rs @@ -1,5 +1,5 @@ -use prometeu_abi::Value; -use prometeu_abi::VmFault; +use prometeu_bytecode::Value; +use crate::vm_fault::VmFault; pub fn expect_bounded(args: &[Value], idx: usize) -> Result { args.get(idx) diff --git a/crates/console/prometeu-hal/src/native_interface.rs b/crates/console/prometeu-hal/src/native_interface.rs index fa93be91..0259d679 100644 --- a/crates/console/prometeu-hal/src/native_interface.rs +++ b/crates/console/prometeu-hal/src/native_interface.rs @@ -1,6 +1,7 @@ -use prometeu_abi::{Value, VmFault}; +use prometeu_bytecode::Value; use crate::host_context::HostContext; use crate::host_return::HostReturn; +use crate::vm_fault::VmFault; pub type SyscallId = u32; diff --git a/crates/compiler/prometeu-abi/src/vm_fault.rs b/crates/console/prometeu-hal/src/vm_fault.rs similarity index 100% rename from crates/compiler/prometeu-abi/src/vm_fault.rs rename to crates/console/prometeu-hal/src/vm_fault.rs diff --git a/crates/console/prometeu-system/Cargo.toml b/crates/console/prometeu-system/Cargo.toml index 0c8bacac..0b6f2485 100644 --- a/crates/console/prometeu-system/Cargo.toml +++ b/crates/console/prometeu-system/Cargo.toml @@ -8,7 +8,7 @@ license.workspace = true serde_json = "1.0.149" prometeu-vm = { path = "../prometeu-vm" } prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-hal = { path = "../prometeu-hal" } [dev-dependencies] diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime.rs b/crates/console/prometeu-system/src/virtual_machine_runtime.rs index b2514fc8..f24b8f34 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime.rs @@ -5,7 +5,7 @@ use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; use std::collections::HashMap; use std::time::Instant; -use prometeu_abi::{Value, VmFault}; +use prometeu_bytecode::Value; use prometeu_hal::{expect_bool, expect_int, HostContext, HostReturn, NativeInterface, SyscallId}; use prometeu_hal::asset::{BankType, LoadStatus, SlotRef}; use prometeu_hal::button::Button; @@ -13,6 +13,7 @@ use prometeu_hal::cartridge::{AppMode, Cartridge}; use prometeu_hal::color::Color; use prometeu_hal::sprite::Sprite; use prometeu_hal::tile::Tile; +use prometeu_hal::vm_fault::VmFault; use prometeu_vm::{LogicalFrameEndingReason, VirtualMachine}; @@ -423,8 +424,8 @@ impl VirtualMachineRuntime { #[cfg(test)] mod tests { + use prometeu_bytecode::Value; use prometeu_drivers::hardware::Hardware; -use prometeu_abi::Value; use prometeu_hal::{HostReturn, InputSignals}; use super::*; diff --git a/crates/console/prometeu-vm/Cargo.toml b/crates/console/prometeu-vm/Cargo.toml index b20ac3db..330702f9 100644 --- a/crates/console/prometeu-vm/Cargo.toml +++ b/crates/console/prometeu-vm/Cargo.toml @@ -7,5 +7,5 @@ license.workspace = true [dependencies] serde = { version = "1.0.228", features = ["derive"] } prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-hal = { path = "../prometeu-hal" } diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index 7f3683f1..6221140c 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -3,14 +3,11 @@ mod call_frame; mod scope_frame; pub mod local_addressing; pub mod verifier; +pub mod vm_init_error; -pub use prometeu_abi::ProgramImage; -pub use prometeu_abi::{Value, VmFault}; pub use prometeu_bytecode::abi::TrapInfo; pub use prometeu_bytecode::opcode::OpCode; pub use prometeu_hal::{ HostContext, HostReturn, NativeInterface, SyscallId, }; -pub type VmInitError = prometeu_abi::VmInitError; -pub use verifier::VerifierError; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 7469cfdb..2cce3b97 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -1,12 +1,16 @@ use crate::call_frame::CallFrame; use crate::scope_frame::ScopeFrame; -use crate::Value; -use crate::{HostContext, NativeInterface, ProgramImage, VmInitError}; +use crate::{HostContext, NativeInterface}; use prometeu_bytecode::abi::{ TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_GATE, TRAP_OOB, TRAP_TYPE, }; use prometeu_bytecode::opcode::OpCode; +use prometeu_bytecode::program_image::ProgramImage; +use prometeu_bytecode::Value; +use prometeu_hal::vm_fault::VmFault; +use crate::verifier::Verifier; +use crate::vm_init_error::VmInitError; /// Reason why the Virtual Machine stopped execution during a specific run. /// This allows the system to decide if it should continue execution in the next tick @@ -142,14 +146,14 @@ impl VirtualMachine { self.heap.clear(); self.gate_pool.clear(); self.cycles = 0; - self.halted = true; // execution is impossible until successful load + self.halted = true; // execution is impossible until a successful load // Only recognized format is loadable: PBS v0 industrial format let program = if program_bytes.starts_with(b"PBS\0") { match prometeu_bytecode::BytecodeLoader::load(&program_bytes) { Ok(module) => { // Run verifier on the module - let max_stacks = crate::verifier::Verifier::verify(&module.code, &module.functions) + let max_stacks = Verifier::verify(&module.code, &module.functions) .map_err(VmInitError::VerificationFailed)?; let mut program = ProgramImage::from(module); @@ -164,7 +168,7 @@ impl VirtualMachine { } Err(prometeu_bytecode::LoadError::InvalidVersion) => return Err(VmInitError::UnsupportedFormat), Err(e) => { - return Err(VmInitError::PbsV0LoadFailed(e)); + return Err(VmInitError::ImageLoadFailed(e)); } } } else { @@ -904,9 +908,9 @@ impl VirtualMachine { let stack_height_before = self.operand_stack.len(); let mut ret = crate::HostReturn::new(&mut self.operand_stack); native.syscall(id, &args, &mut ret, ctx).map_err(|fault| match fault { - crate::VmFault::Trap(code, msg) => self.trap(code, OpCode::Syscall as u16, msg, pc_at_syscall), - crate::VmFault::Panic(msg) => LogicalFrameEndingReason::Panic(msg), - crate::VmFault::Unavailable => LogicalFrameEndingReason::Panic("Host feature unavailable".into()), + VmFault::Trap(code, msg) => self.trap(code, OpCode::Syscall as u16, msg, pc_at_syscall), + VmFault::Panic(msg) => LogicalFrameEndingReason::Panic(msg), + VmFault::Unavailable => LogicalFrameEndingReason::Panic("Host feature unavailable".into()), })?; let stack_height_after = self.operand_stack.len(); @@ -1010,7 +1014,7 @@ mod tests { }]); vm } - use crate::{HostReturn, Value, VmFault}; + use crate::{HostReturn}; use prometeu_bytecode::abi::SourceSpan; use prometeu_bytecode::FunctionMeta; use prometeu_hal::expect_int; @@ -2000,7 +2004,7 @@ mod tests { let res = ret.push_bounded(65536); assert!(res.is_err()); match res.err().unwrap() { - crate::VmFault::Trap(code, _) => { + VmFault::Trap(code, _) => { assert_eq!(code, prometeu_bytecode::abi::TRAP_OOB); } _ => panic!("Expected Trap"), @@ -2036,7 +2040,7 @@ mod tests { let res = vm.initialize(header, ""); match res { - Err(VmInitError::PbsV0LoadFailed(prometeu_bytecode::LoadError::UnexpectedEof)) => {}, + Err(VmInitError::ImageLoadFailed(prometeu_bytecode::LoadError::UnexpectedEof)) => {}, _ => panic!("Expected PbsV0LoadFailed(UnexpectedEof), got {:?}", res), } } diff --git a/crates/console/prometeu-vm/src/vm_init_error.rs b/crates/console/prometeu-vm/src/vm_init_error.rs new file mode 100644 index 00000000..7dabdf28 --- /dev/null +++ b/crates/console/prometeu-vm/src/vm_init_error.rs @@ -0,0 +1,10 @@ +use crate::verifier::VerifierError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VmInitError { + InvalidFormat, + UnsupportedFormat, + ImageLoadFailed(prometeu_bytecode::LoadError), + EntrypointNotFound, + VerificationFailed(VerifierError), +} diff --git a/crates/host/prometeu-host-desktop-winit/Cargo.toml b/crates/host/prometeu-host-desktop-winit/Cargo.toml index e1347381..8d901f89 100644 --- a/crates/host/prometeu-host-desktop-winit/Cargo.toml +++ b/crates/host/prometeu-host-desktop-winit/Cargo.toml @@ -14,7 +14,7 @@ dist = true include = ["../../VERSION.txt"] [dependencies] -prometeu-abi = { path = "../../compiler/prometeu-abi" } +prometeu-core = { path = "../../compiler/prometeu-core" } prometeu-firmware = { path = "../../console/prometeu-firmware" } prometeu-system = { path = "../../console/prometeu-system" } prometeu-drivers = { path = "../../console/prometeu-drivers" } diff --git a/crates/language-api/src/lib.rs b/crates/language-api/src/lib.rs deleted file mode 100644 index 6f528757..00000000 --- a/crates/language-api/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Frontend API (canonical contract between Frontend and Backend) -//! -//! Policy: no strings-as-protocol. All identifiers use canonical newtypes with -//! clear invariants and normalization helpers. No PBS- (or any FE-) specific -//! types are allowed in this crate. - -pub mod types; -pub mod traits; - -pub use crate::types::*; -pub use crate::traits::*; diff --git a/crates/language-api/src/traits.rs b/crates/language-api/src/traits.rs deleted file mode 100644 index d85e4cdb..00000000 --- a/crates/language-api/src/traits.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Strict frontend contract owned by the Backend. -//! -//! Implementations must not expose FE-specific types through this boundary. - -use crate::{Diagnostic, ExportItem, ImportRef, LoweredIr}; - -#[derive(Debug, Default)] -pub struct FrontendUnit { - pub diagnostics: Vec, - pub imports: Vec, - pub exports: Vec, - pub lowered_ir: LoweredIr, -} - -/// Frontend entrypoint that parses and analyzes a single compilation unit -/// and produces a `FrontendUnit` for the Backend. -pub trait Frontend { - /// Parse and analyze the provided sources according to the FE's language, - /// returning only canonical artifacts required by the Backend. - /// - /// No strings-as-protocol allowed in the output; use canonical types. - fn parse_and_analyze(&self, entry_path: &str) -> FrontendUnit; -} diff --git a/crates/language-api/src/types.rs b/crates/language-api/src/types.rs deleted file mode 100644 index 7b05a84f..00000000 --- a/crates/language-api/src/types.rs +++ /dev/null @@ -1,341 +0,0 @@ -use core::fmt; -use std::borrow::Cow; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use thiserror::Error; - -/// A project alias (canonical lowercase name). -/// Invariants: -/// - lowercase ASCII -/// - must start with [a-z] -/// - remaining chars: [a-z0-9_-] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ProjectAlias(String); - -impl ProjectAlias { - pub fn new>(s: S) -> Result { - let s = s.as_ref().trim(); - if s.is_empty() { - return Err(CanonError::Empty("ProjectAlias")); - } - if !s.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') { - return Err(CanonError::InvalidChars("ProjectAlias")); - } - let mut chars = s.chars(); - match chars.next() { - Some(c) if c.is_ascii_lowercase() => {} - _ => return Err(CanonError::InvalidStart("ProjectAlias")), - } - Ok(Self(s.to_string())) - } - - pub fn as_str(&self) -> &str { &self.0 } -} - -impl fmt::Display for ProjectAlias { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } -} - -/// Canonical module path with '/' separators and lowercase segments. -/// Invariants: -/// - uses '/' only; '\\' normalized to '/' -/// - no leading/trailing '/' -/// - no empty segments, no duplicate '//' -/// - segments are lowercase ASCII [a-z0-9_-] -/// - '.' segments are removed; '..' is forbidden -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ModulePath(String); - -impl ModulePath { - pub fn parse>(s: S) -> Result { - let raw = s.as_ref().trim(); - if raw.is_empty() { - return Err(CanonError::Empty("ModulePath")); - } - let raw = raw.replace('\\', "/"); - let raw = raw.trim_matches('/'); - if raw.is_empty() { - return Err(CanonError::Empty("ModulePath")); - } - let mut out: Vec<&str> = Vec::new(); - for seg in raw.split('/') { - if seg.is_empty() { return Err(CanonError::EmptySegment("ModulePath")); } - if seg == "." { continue; } - if seg == ".." { return Err(CanonError::ParentSegmentsForbidden); } - let seg_lc = seg.to_ascii_lowercase(); - if !seg_lc.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') { - return Err(CanonError::InvalidChars("ModulePath")); - } - out.push(Box::leak(seg_lc.into_boxed_str())); - } - if out.is_empty() { return Err(CanonError::Empty("ModulePath")); } - Ok(Self(out.join("/"))) - } - - pub fn as_str(&self) -> &str { &self.0 } -} - -impl fmt::Display for ModulePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } -} - -/// Canonical exported/declared item name. -/// Invariants: -/// - must start with [A-Za-z] (types/services typically use UpperCamel; functions may be lowerCamel) -/// - remaining chars: [A-Za-z0-9_] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ItemName(String); - -impl ItemName { - pub fn new>(s: S) -> Result { - let s = s.as_ref().trim(); - if s.is_empty() { return Err(CanonError::Empty("ItemName")); } - let mut chars = s.chars(); - match chars.next() { - Some(c) if c.is_ascii_alphabetic() => {} - _ => return Err(CanonError::InvalidStart("ItemName")), - } - if !s[1..].chars().all(|c| c.is_ascii_alphanumeric() || c == '_') { - return Err(CanonError::InvalidChars("ItemName")); - } - Ok(Self(s.to_string())) - } - pub fn as_str(&self) -> &str { &self.0 } -} - -impl fmt::Display for ItemName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } -} - -/// A fully-qualified import reference: project + module + item. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ImportRef { - pub project: ProjectAlias, - pub module: ModulePath, - pub item: ItemName, -} - -impl ImportRef { - pub fn new(project: ProjectAlias, module: ModulePath, item: ItemName) -> Self { - Self { project, module, item } - } - - /// Parse a PBS-style import `from` string and an `item` into an `ImportRef`. - /// - /// Expected `from_str` format: "@:", e.g., "@sdk:input/testing". - /// `item_str` must be a single symbol name like "Test" or "ServiceName". - pub fn parse_pbs_import, S2: AsRef>(from_str: S1, item_str: S2) -> Result { - let (project, module) = parse_pbs_from_string(from_str)?; - let item = ItemName::new(item_str)?; - Ok(ImportRef { project, module, item }) - } -} - -/// Parse the PBS `from` string in the canonical format `@:`. -/// Returns `(ProjectAlias, ModulePath)` or a `CanonError` if invalid. -pub fn parse_pbs_from_string>(from_str: S) -> Result<(ProjectAlias, ModulePath), CanonError> { - let s = from_str.as_ref().trim(); - // Must start with '@' and contain a ':' separating alias and module path - let rest = s.strip_prefix('@').ok_or(CanonError::InvalidStart("ImportFrom"))?; - let mut parts = rest.splitn(2, ':'); - let alias = parts.next().unwrap_or(""); - let module = parts.next().unwrap_or(""); - if alias.is_empty() || module.is_empty() { - return Err(CanonError::Empty("ImportFrom")); - } - let project = ProjectAlias::new(alias)?; - let module = ModulePath::parse(module)?; - Ok((project, module)) -} - -/// Export kind — generic, FE-agnostic. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub enum ExportKind { - Function, - Service, - Type, - Const, -} - -/// Canonical export item (FE-owned, BE-agnostic, no string protocols). -/// -/// This surface is stable even if display formatting changes. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub enum ExportItem { - /// A declared canonical type (struct/enum/etc.) - Type { name: ItemName }, - /// A declared service owner (method container) - Service { name: ItemName }, - /// A function export (free fn or method) identified by canonical key - Function { fn_key: CanonicalFnKey }, -} - -/// A fully-qualified export reference: project + module + item + kind. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct ExportRef { - pub project: ProjectAlias, - pub module: ModulePath, - pub item: ItemName, - pub kind: ExportKind, -} - -impl ExportRef { - pub fn new(project: ProjectAlias, module: ModulePath, item: ItemName, kind: ExportKind) -> Self { - Self { project, module, item, kind } - } -} - -/// Canonical function identity. -/// -/// JVM-like canonical pieces to uniquely identify an overload across projects: -/// - `owner`: optional service/type name for methods (e.g., `Some("Log")`) or `None` for free fns -/// - `name`: unqualified function/method name (e.g., `"debug"`) -/// - `sig`: canonical signature id produced by the frontend -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct CanonicalFnKey { - pub owner: Option, - pub name: ItemName, - pub sig: SignatureRef, -} - -impl CanonicalFnKey { - pub fn new(owner: Option, name: ItemName, sig: SignatureRef) -> Self { - Self { owner, name, sig } - } - - /// Returns a human-friendly display name like "Log.debug" or just "debug". - pub fn debug_name(&self) -> String { - match &self.owner { - Some(o) => format!("{}.{}", o.as_str(), self.name.as_str()), - None => self.name.as_str().to_string(), - } - } -} - -/// Opaque canonical reference to a type known to the Frontend. -/// Backend must not rely on its internal representation. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct TypeRef(pub u32); - -/// Opaque canonical reference to a function signature (overload identity). -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct SignatureRef(pub u32); - -/// Diagnostic severity. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Severity { Error, Warning, Info } - -/// A simple diagnostic message produced by the frontend. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Diagnostic { - pub message: String, - pub severity: Severity, -} - -impl Diagnostic { - pub fn error>(m: M) -> Self { Self { message: m.into(), severity: Severity::Error } } - pub fn warning>(m: M) -> Self { Self { message: m.into(), severity: Severity::Warning } } - pub fn info>(m: M) -> Self { Self { message: m.into(), severity: Severity::Info } } -} - -/// Opaque lowered IR payload. -/// The backend owns the meaning of these bytes for a given `format` tag. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] -pub struct LoweredIr { - pub format: Cow<'static, str>, - pub bytes: Vec, -} - -impl LoweredIr { - pub fn new>>(format: S, bytes: Vec) -> Self { Self { format: format.into(), bytes } } -} - -/// Errors raised while constructing canonical identifiers. -#[derive(Debug, Error, PartialEq, Eq)] -pub enum CanonError { - #[error("{0} cannot be empty")] Empty(&'static str), - #[error("{0} has invalid starting character")] InvalidStart(&'static str), - #[error("{0} contains invalid characters")] InvalidChars(&'static str), - #[error("{0} contains empty segment")] EmptySegment(&'static str), - #[error("parent segments ('..') are forbidden in ModulePath")] ParentSegmentsForbidden, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn module_path_normalization_basic() { - assert_eq!(ModulePath::parse("input/testing").unwrap().as_str(), "input/testing"); - assert_eq!(ModulePath::parse("/input/testing/").unwrap().as_str(), "input/testing"); - assert_eq!(ModulePath::parse("INPUT/Testing").unwrap().as_str(), "input/testing"); - assert_eq!(ModulePath::parse("input/./testing").unwrap().as_str(), "input/testing"); - assert_eq!(ModulePath::parse("input\\testing").unwrap().as_str(), "input/testing"); - } - - #[test] - fn module_path_rejects_invalid() { - assert!(ModulePath::parse("").is_err()); - assert!(ModulePath::parse("/").is_err()); - assert!(ModulePath::parse("input//testing").is_err()); - assert!(ModulePath::parse("input/../testing").is_err()); - assert!(ModulePath::parse("in$put/testing").is_err()); - } - - #[test] - fn project_alias_rules() { - assert!(ProjectAlias::new("main").is_ok()); - assert!(ProjectAlias::new("_main").is_err()); - assert!(ProjectAlias::new("Main").is_err()); - assert!(ProjectAlias::new("main-1").is_ok()); - assert!(ProjectAlias::new("main/1").is_err()); - } - - #[test] - fn item_name_rules() { - assert!(ItemName::new("Test").is_ok()); - assert!(ItemName::new("Log2").is_ok()); - assert!(ItemName::new("test").is_ok()); - assert!(ItemName::new("_Test").is_err()); - assert!(ItemName::new("Te-st").is_err()); - } - - #[test] - fn parse_pbs_from_string_valid_and_normalization() { - let (proj, module) = parse_pbs_from_string("@sdk:input/testing").unwrap(); - assert_eq!(proj.as_str(), "sdk"); - assert_eq!(module.as_str(), "input/testing"); - - // Normalization of module path - let (_, module2) = parse_pbs_from_string("@sdk:/Input/./Testing/").unwrap(); - assert_eq!(module2.as_str(), "input/testing"); - - // Windows-style separators normalize - let (_, module3) = parse_pbs_from_string("@abc:foo\\bar").unwrap(); - assert_eq!(module3.as_str(), "foo/bar"); - } - - #[test] - fn parse_pbs_from_string_invalid_forms() { - assert!(parse_pbs_from_string("").is_err()); - assert!(parse_pbs_from_string("sdk:input/testing").is_err()); // missing '@' - assert!(parse_pbs_from_string("@Sdk:input/testing").is_err()); // invalid alias - assert!(parse_pbs_from_string("@sdk:").is_err()); // empty module - assert!(parse_pbs_from_string("@sdk:..").is_err()); // parent segments forbidden - } -} diff --git a/crates/tools/prometeu-cli/Cargo.toml b/crates/tools/prometeu-cli/Cargo.toml index 0a821982..cdb351d7 100644 --- a/crates/tools/prometeu-cli/Cargo.toml +++ b/crates/tools/prometeu-cli/Cargo.toml @@ -27,5 +27,5 @@ include = [ [dependencies] clap = { version = "4.5", features = ["derive"] } prometeu-host-desktop-winit = { path = "../../host/prometeu-host-desktop-winit" } -prometeu-compiler = { path = "../../compiler/prometeu-compiler" } +prometeu-build-pipeline = { path = "../../compiler/prometeu-build-pipeline" } anyhow = "1.0.100" diff --git a/crates/tools/prometeu-cli/src/bin/prometeuc.rs b/crates/tools/prometeu-cli/src/bin/prometeuc.rs index 818d5c72..8ee05386 100644 --- a/crates/tools/prometeu-cli/src/bin/prometeuc.rs +++ b/crates/tools/prometeu-cli/src/bin/prometeuc.rs @@ -1 +1 @@ -fn main() -> anyhow::Result<()> { prometeu_compiler::run() } +fn main() -> anyhow::Result<()> { prometeu_build_pipeline::run() } diff --git a/crates/tools/prometeu-cli/src/main.rs b/crates/tools/prometeu-cli/src/main.rs index 139b8412..7a9da20f 100644 --- a/crates/tools/prometeu-cli/src/main.rs +++ b/crates/tools/prometeu-cli/src/main.rs @@ -8,7 +8,7 @@ use std::process::Command; /// The main entry point for the user. This binary does not implement /// compilation or execution logic itself; instead, it acts as a smart /// front-end that locates and dispatches commands to specialized -/// components like `prometeu-host-desktop-winit` or `prometeu-compiler`. +/// components like `prometeu-host-desktop-winit` or `prometeu-build-pipeline`. #[derive(Parser)] #[command(name = "prometeu")] #[command(about = "Dispatcher for the Prometeu ecosystem", long_about = None)] diff --git a/crates/tools/prometeu-lsp/Cargo.toml b/crates/tools/prometeu-lsp/Cargo.toml index 9ba9de37..39165052 100644 --- a/crates/tools/prometeu-lsp/Cargo.toml +++ b/crates/tools/prometeu-lsp/Cargo.toml @@ -8,5 +8,5 @@ license = "MIT" tower-lsp = "0.20" tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7" } -prometeu-analysis = { path = "../../compiler/prometeu-analysis" } -prometeu-compiler = { path = "../../compiler/prometeu-compiler" } +prometeu-core = { path = "../../compiler/prometeu-core" } +prometeu-build-pipeline = { path = "../../compiler/prometeu-build-pipeline" } diff --git a/crates/tools/prometeu-lsp/src/analysis_db.rs b/crates/tools/prometeu-lsp/src/analysis_db.rs index dd2a1d66..283ac4ad 100644 --- a/crates/tools/prometeu-lsp/src/analysis_db.rs +++ b/crates/tools/prometeu-lsp/src/analysis_db.rs @@ -1,52 +1,51 @@ -use std::sync::Arc; -use tokio::sync::RwLock; -use tokio_util::sync::CancellationToken; - -use prometeu_analysis::FileDB; -use prometeu_analysis::ids::{FileId, ProjectId}; -use std::collections::HashMap; -use crate::rebuild::LspDiagnostic; -use crate::rebuild::FlatSymbol; - -#[derive(Default)] -pub struct AnalysisDb { - pub file_db: FileDB, - pub file_to_project: HashMap, - - // Os campos abaixo serão conectados conforme PR-03/04/05 (podem começar como None) - // pub ast: Option, - // pub symbols: Option, - // pub types: Option, - // pub diagnostics: Vec, - - /// Incrementa a cada rebuild concluído com sucesso - pub revision: u64, - - /// Cancel token do último rebuild em progresso (se houver) - pub active_rebuild: Option, - - /// Último snapshot bom (consultado pelos handlers LSP) - pub last_good: Option, -} - -pub type SharedDb = Arc>; - -impl AnalysisDb { - pub fn project_for_file(&self, file: FileId) -> Option { - self.file_to_project.get(&file).copied() - } - - /// Returns all known file ids in the FileDB. - pub fn file_ids(&self) -> Vec { - // delegate to FileDB helper (added in prometeu-analysis) - self.file_db.all_files() - } -} - -#[derive(Default, Clone)] -pub struct AnalysisSnapshot { - /// Diagnostics por arquivo (URI LSP → diagnostics já convertidos) - pub diagnostics_by_uri: HashMap>, - /// Lista “flatten” de símbolos para workspaceSymbol/documentSymbol - pub symbols_flat: Vec, -} +// use std::sync::Arc; +// use tokio::sync::RwLock; +// use tokio_util::sync::CancellationToken; +// +// use std::collections::HashMap; +// use prometeu_core::{FileDB, FileId, ProjectId}; +// use crate::rebuild::LspDiagnostic; +// use crate::rebuild::FlatSymbol; +// +// #[derive(Default)] +// pub struct AnalysisDb { +// pub file_db: FileDB, +// pub file_to_project: HashMap, +// +// // Os campos abaixo serão conectados conforme PR-03/04/05 (podem começar como None) +// // pub ast: Option, +// // pub symbols: Option, +// // pub types: Option, +// // pub diagnostics: Vec, +// +// /// Incrementa a cada rebuild concluído com sucesso +// pub revision: u64, +// +// /// Cancel token do último rebuild em progresso (se houver) +// pub active_rebuild: Option, +// +// /// Último snapshot bom (consultado pelos handlers LSP) +// pub last_good: Option, +// } +// +// pub type SharedDb = Arc>; +// +// impl AnalysisDb { +// pub fn project_for_file(&self, file: FileId) -> Option { +// self.file_to_project.get(&file).copied() +// } +// +// /// Returns all known file ids in the FileDB. +// pub fn file_ids(&self) -> Vec { +// // delegate to FileDB helper (added in prometeu-analysis) +// self.file_db.all_files() +// } +// } +// +// #[derive(Default, Clone)] +// pub struct AnalysisSnapshot { +// /// Diagnostics por arquivo (URI LSP → diagnostics já convertidos) +// pub diagnostics_by_uri: HashMap>, +// /// Lista “flatten” de símbolos para workspaceSymbol/documentSymbol +// pub symbols_flat: Vec, +// } diff --git a/crates/tools/prometeu-lsp/src/main.rs b/crates/tools/prometeu-lsp/src/main.rs index d547fefa..f3e34eae 100644 --- a/crates/tools/prometeu-lsp/src/main.rs +++ b/crates/tools/prometeu-lsp/src/main.rs @@ -1,192 +1,190 @@ -use std::sync::Arc; -use tokio::sync::RwLock; -use tower_lsp::{Client, LspService, Server}; -use tower_lsp::lsp_types as lsp; +// use std::sync::Arc; +// use tokio::sync::RwLock; +// use tower_lsp::{Client, LspService, Server}; +// use tower_lsp::lsp_types as lsp; +// +// mod analysis_db; +// mod rebuild; -mod analysis_db; -mod rebuild; +// struct Backend { +// db: SharedDb, +// client: Client, +// } -use analysis_db::SharedDb; - -struct Backend { - db: SharedDb, - client: Client, -} - -#[tower_lsp::async_trait] -impl tower_lsp::LanguageServer for Backend { - async fn initialize( - &self, - _: tower_lsp::lsp_types::InitializeParams, - ) -> tower_lsp::jsonrpc::Result { - 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, - ), - ), - // MVP capabilities only (PR-08): - definition_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)), - document_symbol_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)), - // workspace_symbol is not available in tower-lsp 0.20 trait - ..Default::default() - }, - ..Default::default() - }) - } - - async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {} - - async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> { - Ok(()) - } - - // didOpen: upsert texto, solicita rebuild - async fn did_open(&self, params: tower_lsp::lsp_types::DidOpenTextDocumentParams) { - let uri = params.text_document.uri.to_string(); - let text = params.text_document.text; - { - let mut guard = self.db.write().await; - guard.file_db.upsert(&uri, text); - } - rebuild::request_rebuild(self.db.clone(), self.client.clone()).await; - } - - // didChange (FULL): receber conteúdo completo e upsert - async fn did_change(&self, params: tower_lsp::lsp_types::DidChangeTextDocumentParams) { - let uri = params.text_document.uri.to_string(); - // Full-sync: esperamos 1 conteúdo completo - if let Some(change) = params.content_changes.into_iter().last() { - let text = change.text; - let mut guard = self.db.write().await; - guard.file_db.upsert(&uri, text); - } - rebuild::request_rebuild(self.db.clone(), self.client.clone()).await; - } - - // didClose: opcionalmente remover do db e limpar diagnostics - async fn did_close(&self, params: tower_lsp::lsp_types::DidCloseTextDocumentParams) { - let uri = params.text_document.uri; - // Estratégia simples: manter FileDB para estabilidade de IDs, mas limpar diagnostics - let _ = self - .client - .publish_diagnostics(uri.clone(), vec![], Some(0)) - .await; - } - - async fn goto_definition( - &self, - params: tower_lsp::lsp_types::GotoDefinitionParams, - ) -> tower_lsp::jsonrpc::Result> { - let tdp = params.text_document_position_params; - let uri = tdp.text_document.uri; - let pos = tdp.position; - - let guard = self.db.read().await; - // Map URI to current text and index - let Some(fid) = guard.file_db.file_id(uri.as_str()) else { return Ok(None) }; - let text = guard.file_db.text(fid).to_string(); - let idx = prometeu_analysis::TextIndex::new(&text); - let byte = idx.lsp_to_byte(pos.line, pos.character); - let ident = ident_at(&text, byte); - - if let Some(name) = ident { - if let Some(snap) = &guard.last_good { - let mut hits: Vec = Vec::new(); - for s in &snap.symbols_flat { - if s.name == name { - hits.push(s.location.clone()); - } - } - if !hits.is_empty() { - return Ok(Some(lsp::GotoDefinitionResponse::Array(hits))); - } - } - } - Ok(None) - } - - - // MVP stubs: documentSymbol/workspaceSymbol/definition retornam vazio até PRs seguintes - async fn document_symbol( - &self, - params: tower_lsp::lsp_types::DocumentSymbolParams, - ) -> tower_lsp::jsonrpc::Result> { - let uri = params.text_document.uri; - let guard = self.db.read().await; - if let Some(snap) = &guard.last_good { - let mut items: Vec = Vec::new(); - for s in &snap.symbols_flat { - if s.location.uri == uri { - items.push(lsp::SymbolInformation { - name: s.name.clone(), - kind: s.kind, - location: s.location.clone(), - tags: None, - deprecated: None, - container_name: None, - }); - } - } - return Ok(Some(lsp::DocumentSymbolResponse::Flat(items))); - } - Ok(Some(lsp::DocumentSymbolResponse::Flat(vec![]))) - } - - // async fn workspace_symbol( - // &self, - // params: lsp::WorkspaceSymbolParams, - // ) -> tower_lsp::jsonrpc::Result>> { - // let query = params.query.to_lowercase(); - // let guard = self.db.read().await; - // if let Some(snap) = &guard.last_good { - // let mut out: Vec = Vec::new(); - // for s in &snap.symbols_flat { - // if s.name.to_lowercase().contains(&query) { - // out.push(lsp::SymbolInformation { - // name: s.name.clone(), - // kind: s.kind, - // location: s.location.clone(), - // tags: None, - // deprecated: None, - // container_name: None, - // }); - // if out.len() >= 50 { break; } - // } - // } - // return Ok(Some(out)); - // } - // Ok(Some(vec![])) - // } -} +// #[tower_lsp::async_trait] +// impl tower_lsp::LanguageServer for Backend { +// async fn initialize( +// &self, +// _: tower_lsp::lsp_types::InitializeParams, +// ) -> tower_lsp::jsonrpc::Result { +// 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, +// ), +// ), +// // MVP capabilities only (PR-08): +// definition_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)), +// document_symbol_provider: Some(tower_lsp::lsp_types::OneOf::Left(true)), +// // workspace_symbol is not available in tower-lsp 0.20 trait +// ..Default::default() +// }, +// ..Default::default() +// }) +// } +// +// async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {} +// +// async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> { +// Ok(()) +// } +// +// // didOpen: upsert texto, solicita rebuild +// async fn did_open(&self, params: tower_lsp::lsp_types::DidOpenTextDocumentParams) { +// let uri = params.text_document.uri.to_string(); +// let text = params.text_document.text; +// { +// let mut guard = self.db.write().await; +// guard.file_db.upsert(&uri, text); +// } +// rebuild::request_rebuild(self.db.clone(), self.client.clone()).await; +// } +// +// // didChange (FULL): receber conteúdo completo e upsert +// async fn did_change(&self, params: tower_lsp::lsp_types::DidChangeTextDocumentParams) { +// let uri = params.text_document.uri.to_string(); +// // Full-sync: esperamos 1 conteúdo completo +// if let Some(change) = params.content_changes.into_iter().last() { +// let text = change.text; +// let mut guard = self.db.write().await; +// guard.file_db.upsert(&uri, text); +// } +// rebuild::request_rebuild(self.db.clone(), self.client.clone()).await; +// } +// +// // didClose: opcionalmente remover do db e limpar diagnostics +// async fn did_close(&self, params: tower_lsp::lsp_types::DidCloseTextDocumentParams) { +// let uri = params.text_document.uri; +// // Estratégia simples: manter FileDB para estabilidade de IDs, mas limpar diagnostics +// let _ = self +// .client +// .publish_diagnostics(uri.clone(), vec![], Some(0)) +// .await; +// } +// +// async fn goto_definition( +// &self, +// params: tower_lsp::lsp_types::GotoDefinitionParams, +// ) -> tower_lsp::jsonrpc::Result> { +// let tdp = params.text_document_position_params; +// let uri = tdp.text_document.uri; +// let pos = tdp.position; +// +// let guard = self.db.read().await; +// // Map URI to current text and index +// let Some(fid) = guard.file_db.file_id(uri.as_str()) else { return Ok(None) }; +// let text = guard.file_db.text(fid).to_string(); +// let idx = prometeu_analysis::TextIndex::new(&text); +// let byte = idx.lsp_to_byte(pos.line, pos.character); +// let ident = ident_at(&text, byte); +// +// if let Some(name) = ident { +// if let Some(snap) = &guard.last_good { +// let mut hits: Vec = Vec::new(); +// for s in &snap.symbols_flat { +// if s.name == name { +// hits.push(s.location.clone()); +// } +// } +// if !hits.is_empty() { +// return Ok(Some(lsp::GotoDefinitionResponse::Array(hits))); +// } +// } +// } +// Ok(None) +// } +// +// +// // MVP stubs: documentSymbol/workspaceSymbol/definition retornam vazio até PRs seguintes +// async fn document_symbol( +// &self, +// params: tower_lsp::lsp_types::DocumentSymbolParams, +// ) -> tower_lsp::jsonrpc::Result> { +// let uri = params.text_document.uri; +// let guard = self.db.read().await; +// if let Some(snap) = &guard.last_good { +// let mut items: Vec = Vec::new(); +// for s in &snap.symbols_flat { +// if s.location.uri == uri { +// items.push(lsp::SymbolInformation { +// name: s.name.clone(), +// kind: s.kind, +// location: s.location.clone(), +// tags: None, +// deprecated: None, +// container_name: None, +// }); +// } +// } +// return Ok(Some(lsp::DocumentSymbolResponse::Flat(items))); +// } +// Ok(Some(lsp::DocumentSymbolResponse::Flat(vec![]))) +// } +// +// // async fn workspace_symbol( +// // &self, +// // params: lsp::WorkspaceSymbolParams, +// // ) -> tower_lsp::jsonrpc::Result>> { +// // let query = params.query.to_lowercase(); +// // let guard = self.db.read().await; +// // if let Some(snap) = &guard.last_good { +// // let mut out: Vec = Vec::new(); +// // for s in &snap.symbols_flat { +// // if s.name.to_lowercase().contains(&query) { +// // out.push(lsp::SymbolInformation { +// // name: s.name.clone(), +// // kind: s.kind, +// // location: s.location.clone(), +// // tags: None, +// // deprecated: None, +// // container_name: None, +// // }); +// // if out.len() >= 50 { break; } +// // } +// // } +// // return Ok(Some(out)); +// // } +// // Ok(Some(vec![])) +// // } +// } #[tokio::main] async fn main() { - let stdin = tokio::io::stdin(); - let stdout = tokio::io::stdout(); - - let db: SharedDb = Arc::new(RwLock::new(analysis_db::AnalysisDb::default())); - - let (service, socket) = LspService::new(|client| Backend { db: db.clone(), client }); - Server::new(stdin, stdout, socket).serve(service).await; + // let stdin = tokio::io::stdin(); + // let stdout = tokio::io::stdout(); + // + // let db: SharedDb = Arc::new(RwLock::new(analysis_db::sourceDb::default())); + // + // let (service, socket) = LspService::new(|client| Backend { db: db.clone(), client }); + // Server::new(stdin, stdout, socket).serve(service).await; } -// Simple textual identifier extraction for MVP definition lookup. -fn ident_at(text: &str, byte: u32) -> Option { - let b = byte as usize; - if b > text.len() { return None; } - // Expand left and right over identifier characters (ASCII + underscore; acceptable MVP) - let bytes = text.as_bytes(); - let mut start = b; - while start > 0 { - let c = bytes[start - 1]; - if (c as char).is_ascii_alphanumeric() || c == b'_' { start -= 1; } else { break; } - } - let mut end = b; - while end < bytes.len() { - let c = bytes[end]; - if (c as char).is_ascii_alphanumeric() || c == b'_' { end += 1; } else { break; } - } - if start < end { Some(text[start..end].to_string()) } else { None } -} +// // Simple textual identifier extraction for MVP definition lookup. +// fn ident_at(text: &str, byte: u32) -> Option { +// let b = byte as usize; +// if b > text.len() { return None; } +// // Expand left and right over identifier characters (ASCII + underscore; acceptable MVP) +// let bytes = text.as_bytes(); +// let mut start = b; +// while start > 0 { +// let c = bytes[start - 1]; +// if (c as char).is_ascii_alphanumeric() || c == b'_' { start -= 1; } else { break; } +// } +// let mut end = b; +// while end < bytes.len() { +// let c = bytes[end]; +// if (c as char).is_ascii_alphanumeric() || c == b'_' { end += 1; } else { break; } +// } +// if start < end { Some(text[start..end].to_string()) } else { None } +// } diff --git a/crates/tools/prometeu-lsp/src/rebuild.rs b/crates/tools/prometeu-lsp/src/rebuild.rs index 665f3a2a..a24fd735 100644 --- a/crates/tools/prometeu-lsp/src/rebuild.rs +++ b/crates/tools/prometeu-lsp/src/rebuild.rs @@ -1,225 +1,147 @@ -use tokio_util::sync::CancellationToken; -use tower_lsp::Client; - -use crate::analysis_db::{AnalysisSnapshot, SharedDb}; -use prometeu_analysis::{TextIndex}; -use tower_lsp::lsp_types as lsp; -use prometeu_analysis::ids::FileId; -use crate::rebuild::compiler_bridge::{ParserFacade, Severity}; - -#[derive(Clone, Debug)] -pub struct LspDiagnostic { - pub range: lsp::Range, - pub severity: Option, - pub code: Option, - pub message: String, -} - -#[derive(Clone, Debug)] -pub struct FlatSymbol { - pub name: String, - pub kind: lsp::SymbolKind, - pub location: lsp::Location, -} - -/// Requests a project rebuild (coarse). Cancels the previous rebuild if in progress. -pub async fn request_rebuild(db: SharedDb, client: Client) { - // 1) short lock: cancel previous token and install a new one - let new_token = CancellationToken::new(); - { - let mut guard = db.write().await; - if let Some(prev) = guard.active_rebuild.take() { - prev.cancel(); - } - guard.active_rebuild = Some(new_token.clone()); - } - - // 2) spawn task: run analysis outside the lock - tokio::spawn(async move { - // Safe point: check before starting - if new_token.is_cancelled() { return; } - - // Clone snapshot of files (URIs and texts) under a short read lock - let (files, revision) = { - let guard = db.read().await; - let mut v = Vec::new(); - for fid in guard.file_ids() { - let uri = guard.file_db.uri(fid).to_string(); - let text = guard.file_db.text(fid).to_string(); - v.push((fid, uri, text)); - } - (v, guard.revision) - }; - - // Prepare accumulators - let mut diagnostics_by_uri: std::collections::HashMap> = std::collections::HashMap::new(); - let mut symbols_flat: Vec = Vec::new(); - - // For each file: run a minimal frontend to collect diagnostics and top-level symbols - for (fid, uri, text) in files.into_iter() { - if new_token.is_cancelled() { return; } - let text_index = TextIndex::new(&text); - - // Parser + basic pipeline - let mut interner = prometeu_analysis::NameInterner::new(); - let mut parser = ParserFacade::new(&text, fid, &mut interner); - match parser.parse_and_collect() { - Ok(parsed) => { - // Diagnostics (from parse/collect are already inside parsed.diags) - let mut file_diags = Vec::new(); - for d in parsed.diagnostics { - let range = span_to_range(fid, &text_index, d.span.start, d.span.end); - file_diags.push(LspDiagnostic { - range, - severity: Some(match d.severity { Severity::Error => lsp::DiagnosticSeverity::ERROR, Severity::Warning => lsp::DiagnosticSeverity::WARNING }), - code: Some(lsp::NumberOrString::String(d.code)), - message: d.message, - }); - } - diagnostics_by_uri.insert(uri.clone(), file_diags); - - // Symbols: flatten only top-level decls with their decl_span - for sym in parsed.symbols { - let lsp_loc = lsp::Location { - uri: uri.parse().unwrap_or_else(|_| lsp::Url::parse("untitled:").unwrap()), - range: span_to_range(fid, &text_index, sym.decl_span.start, sym.decl_span.end), - }; - let kind = match sym.kind { - prometeu_compiler::analysis::symbols::SymbolKind::Function => lsp::SymbolKind::FUNCTION, - prometeu_compiler::analysis::symbols::SymbolKind::Service => lsp::SymbolKind::INTERFACE, - prometeu_compiler::analysis::symbols::SymbolKind::Struct => lsp::SymbolKind::STRUCT, - prometeu_compiler::analysis::symbols::SymbolKind::Contract => lsp::SymbolKind::CLASS, - prometeu_compiler::analysis::symbols::SymbolKind::ErrorType => lsp::SymbolKind::ENUM, - _ => lsp::SymbolKind::VARIABLE, - }; - symbols_flat.push(FlatSymbol { name: sym.name, kind, location: lsp_loc }); - } - } - Err(diags) => { - // Parser returned errors only; publish them - let mut file_diags = Vec::new(); - for d in diags { - let range = span_to_range(fid, &text_index, d.span.start, d.span.end); - file_diags.push(LspDiagnostic { - range, - severity: Some(match d.severity { Severity::Error => lsp::DiagnosticSeverity::ERROR, Severity::Warning => lsp::DiagnosticSeverity::WARNING }), - code: Some(lsp::NumberOrString::String(d.code)), - message: d.message, - }); - } - diagnostics_by_uri.insert(uri.clone(), file_diags); - } - } - } - - if new_token.is_cancelled() { return; } - - // 3) short lock: swap state + revision++ if not cancelled; then publish diagnostics - let snapshot = AnalysisSnapshot { diagnostics_by_uri: diagnostics_by_uri.clone(), symbols_flat }; - { - let mut guard = db.write().await; - if new_token.is_cancelled() { return; } - // if no new changes since we started, accept this snapshot - guard.last_good = Some(snapshot); - guard.revision = revision.saturating_add(1); - } - - // Publish diagnostics per file - for (uri, diags) in diagnostics_by_uri.into_iter() { - let lsp_diags: Vec = diags.into_iter().map(|d| lsp::Diagnostic { - range: d.range, - severity: d.severity, - code: d.code, - message: d.message, - ..Default::default() - }).collect(); - let _ = client.publish_diagnostics(uri.parse().unwrap_or_else(|_| lsp::Url::parse("untitled:").unwrap()), lsp_diags, None).await; - } - }); -} - -fn span_to_range(file: FileId, idx: &TextIndex, start: u32, end: u32) -> lsp::Range { - // Ignore `file` here since idx is built from that file's text - let (s_line, s_col) = idx.byte_to_lsp(start); - let (e_line, e_col) = idx.byte_to_lsp(end); - lsp::Range { - start: lsp::Position { line: s_line, character: s_col }, - end: lsp::Position { line: e_line, character: e_col }, - } -} - -/// Minimal integration with the compiler frontend for the MVP rebuild loop. -mod compiler_bridge { - use super::*; - use prometeu_compiler::frontends::pbs as p; - use prometeu_compiler::common::spans as cspans; - use prometeu_compiler::common::diagnostics as cdiag; - - #[derive(Clone, Debug)] - pub enum Severity { Error, Warning } - - #[derive(Clone, Debug)] - pub struct Diag { pub severity: Severity, pub code: String, pub message: String, pub span: cspans::Span } - - #[derive(Clone, Debug)] - pub struct SymbolItem { pub name: String, pub kind: prometeu_compiler::analysis::symbols::SymbolKind, pub decl_span: cspans::Span } - - #[derive(Clone, Debug)] - pub struct ParsedResult { pub diagnostics: Vec, pub symbols: Vec } - - pub struct ParserFacade<'a> { - text: &'a str, - file_id: FileId, - interner: &'a mut prometeu_analysis::NameInterner, - } - - impl<'a> ParserFacade<'a> { - pub fn new(text: &'a str, file_id: FileId, interner: &'a mut prometeu_analysis::NameInterner) -> Self { - Self { text, file_id, interner } - } - - pub fn parse_and_collect(&mut self) -> Result> { - let mut parser = p::parser::Parser::new(self.text, cspans::FileId(self.file_id.0), self.interner); - let parsed = match parser.parse_file() { - Ok(p) => p, - Err(bundle) => { - let diags = bundle.diagnostics.into_iter().map(|d| Diag { severity: match d.severity { cdiag::Severity::Error => Severity::Error, cdiag::Severity::Warning => Severity::Warning }, code: d.code, message: d.message, span: d.span }).collect(); - return Err(diags); - } - }; - - let mut collector = p::collector::SymbolCollector::new(self.interner); - let (type_symbols, value_symbols) = match collector.collect(&parsed.arena, parsed.root) { - Ok(v) => v, - Err(bundle) => { - let diags = bundle.diagnostics.into_iter().map(|d| Diag { severity: match d.severity { cdiag::Severity::Error => Severity::Error, cdiag::Severity::Warning => Severity::Warning }, code: d.code, message: d.message, span: d.span }).collect(); - return Err(diags); - } - }; - let module_symbols = p::symbols::ModuleSymbols { type_symbols, value_symbols }; - - struct EmptyProvider; - impl p::resolver::ModuleProvider for EmptyProvider { fn get_module_symbols(&self, _from_path: &str) -> Option<&p::symbols::ModuleSymbols> { None } } - let mut resolver = p::resolver::Resolver::new(&module_symbols, &EmptyProvider, self.interner); - // bootstrap primitives using a throwaway interner behavior - resolver.bootstrap_types(self.interner); - if let Err(bundle) = resolver.resolve(&parsed.arena, parsed.root) { - let diags = bundle.diagnostics.into_iter().map(|d| Diag { severity: match d.severity { cdiag::Severity::Error => Severity::Error, cdiag::Severity::Warning => Severity::Warning }, code: d.code, message: d.message, span: d.span }).collect(); - return Err(diags); - } - - // Collect top-level symbols only for MVP - let mut symbols = Vec::new(); - for s in &resolver.symbol_arena.symbols { - // Keep only decls in this file - if s.decl_span.file.0 == self.file_id.0 { - let name = self.interner.resolve(s.name).to_string(); - let kind = s.kind; - symbols.push(SymbolItem { name, kind, decl_span: s.decl_span.clone() }); - } - } - - Ok(ParsedResult { diagnostics: vec![], symbols }) - } - } -} +// use tokio_util::sync::CancellationToken; +// use tower_lsp::Client; +// +// use crate::source_db::{AnalysisSnapshot, SharedDb}; +// use tower_lsp::lsp_types as lsp; +// use prometeu_core::{FileId, NameInterner, Severity, SymbolKind, TextIndex}; +// +// #[derive(Clone, Debug)] +// pub struct LspDiagnostic { +// pub range: lsp::Range, +// pub severity: Option, +// pub code: Option, +// pub message: String, +// } +// +// #[derive(Clone, Debug)] +// pub struct FlatSymbol { +// pub name: String, +// pub kind: lsp::SymbolKind, +// pub location: lsp::Location, +// } +// +// /// Requests a project rebuild (coarse). Cancels the previous rebuild if in progress. +// pub async fn request_rebuild(db: SharedDb, client: Client) { +// // 1) short lock: cancel previous token and install a new one +// let new_token = CancellationToken::new(); +// { +// let mut guard = db.write().await; +// if let Some(prev) = guard.active_rebuild.take() { +// prev.cancel(); +// } +// guard.active_rebuild = Some(new_token.clone()); +// } +// +// // 2) spawn task: run analysis outside the lock +// tokio::spawn(async move { +// // Safe point: check before starting +// if new_token.is_cancelled() { return; } +// +// // Clone snapshot of files (URIs and texts) under a short read lock +// let (files, revision) = { +// let guard = db.read().await; +// let mut v = Vec::new(); +// for fid in guard.file_ids() { +// let uri = guard.file_db.uri(fid).to_string(); +// let text = guard.file_db.text(fid).to_string(); +// v.push((fid, uri, text)); +// } +// (v, guard.revision) +// }; +// +// // Prepare accumulators +// let mut diagnostics_by_uri: std::collections::HashMap> = std::collections::HashMap::new(); +// let mut symbols_flat: Vec = Vec::new(); +// +// // For each file: run a minimal frontend to collect diagnostics and top-level symbols +// for (fid, uri, text) in files.into_iter() { +// if new_token.is_cancelled() { return; } +// let text_index = TextIndex::new(&text); +// +// // Parser + basic pipeline +// let mut interner = NameInterner::new(); +// let mut parser = ParserFacade::new(&text, fid, &mut interner); +// match parser.parse_and_collect() { +// Ok(parsed) => { +// // Diagnostics (from parse/collect are already inside parsed.diags) +// let mut file_diags = Vec::new(); +// for d in parsed.diagnostics { +// let range = span_to_range(fid, &text_index, d.span.start, d.span.end); +// file_diags.push(LspDiagnostic { +// range, +// severity: Some(match d.severity { Severity::Error => lsp::DiagnosticSeverity::ERROR, Severity::Warning => lsp::DiagnosticSeverity::WARNING }), +// code: Some(lsp::NumberOrString::String(d.code)), +// message: d.message, +// }); +// } +// diagnostics_by_uri.insert(uri.clone(), file_diags); +// +// // Symbols: flatten only top-level decls with their decl_span +// for sym in parsed.symbols { +// let lsp_loc = lsp::Location { +// uri: uri.parse().unwrap_or_else(|_| lsp::Url::parse("untitled:").unwrap()), +// range: span_to_range(fid, &text_index, sym.decl_span.start, sym.decl_span.end), +// }; +// let kind = match sym.kind { +// SymbolKind::Function => lsp::SymbolKind::FUNCTION, +// SymbolKind::Service => lsp::SymbolKind::INTERFACE, +// SymbolKind::Struct => lsp::SymbolKind::STRUCT, +// SymbolKind::Contract => lsp::SymbolKind::CLASS, +// SymbolKind::ErrorType => lsp::SymbolKind::ENUM, +// _ => lsp::SymbolKind::VARIABLE, +// }; +// symbols_flat.push(FlatSymbol { name: sym.name, kind, location: lsp_loc }); +// } +// } +// Err(diags) => { +// // Parser returned errors only; publish them +// let mut file_diags = Vec::new(); +// for d in diags { +// let range = span_to_range(fid, &text_index, d.span.start, d.span.end); +// file_diags.push(LspDiagnostic { +// range, +// severity: Some(match d.severity { Severity::Error => lsp::DiagnosticSeverity::ERROR, Severity::Warning => lsp::DiagnosticSeverity::WARNING }), +// code: Some(lsp::NumberOrString::String(d.code)), +// message: d.message, +// }); +// } +// diagnostics_by_uri.insert(uri.clone(), file_diags); +// } +// } +// } +// +// if new_token.is_cancelled() { return; } +// +// // 3) short lock: swap state + revision++ if not cancelled; then publish diagnostics +// let snapshot = AnalysisSnapshot { diagnostics_by_uri: diagnostics_by_uri.clone(), symbols_flat }; +// { +// let mut guard = db.write().await; +// if new_token.is_cancelled() { return; } +// // if no new changes since we started, accept this snapshot +// guard.last_good = Some(snapshot); +// guard.revision = revision.saturating_add(1); +// } +// +// // Publish diagnostics per file +// for (uri, diags) in diagnostics_by_uri.into_iter() { +// let lsp_diags: Vec = diags.into_iter().map(|d| lsp::Diagnostic { +// range: d.range, +// severity: d.severity, +// code: d.code, +// message: d.message, +// ..Default::default() +// }).collect(); +// let _ = client.publish_diagnostics(uri.parse().unwrap_or_else(|_| lsp::Url::parse("untitled:").unwrap()), lsp_diags, None).await; +// } +// }); +// } +// +// fn span_to_range(file: FileId, idx: &TextIndex, start: u32, end: u32) -> lsp::Range { +// // Ignore `file` here since idx is built from that file's text +// let (s_line, s_col) = idx.byte_to_lsp(start); +// let (e_line, e_col) = idx.byte_to_lsp(end); +// lsp::Range { +// start: lsp::Position { line: s_line, character: s_col }, +// end: lsp::Position { line: e_line, character: e_col }, +// } +// } diff --git a/crates/tools/prometeu-lsp/tests/adapters.rs b/crates/tools/prometeu-lsp/tests/adapters.rs deleted file mode 100644 index d1abdc5e..00000000 --- a/crates/tools/prometeu-lsp/tests/adapters.rs +++ /dev/null @@ -1,33 +0,0 @@ -use prometeu_analysis::TextIndex; - -#[test] -fn span_to_range_uses_utf16() { - // "ação" has: a (1), ç (1 utf16, BMP), ã (1 utf16, BMP), o (1) → total 4 utf16 units - let s = "ação\n"; // newline to ensure line indexing works - let idx = TextIndex::new(s); - - // Byte offsets for each char boundary - let bytes: Vec = s.char_indices().map(|(i, _)| i as u32).collect(); - // last boundary is newline; the previous is end of line content - let end_of_line = bytes[bytes.len() - 1]; - let (line, col) = idx.byte_to_lsp(end_of_line); - assert_eq!(line, 0); - assert_eq!(col, 4, "UTF-16 column should be 4 for 'ação'"); -} - -#[test] -fn position_to_byte_roundtrip() { - let s = "😀a\n"; // emoji is surrogate pair in UTF-16 - let idx = TextIndex::new(s); - // emoji (😀) occupies 2 utf16 code units; 'a' adds 1 - let emoji_end_byte = "😀".len() as u32; // byte length of emoji - - // From bytes→(line,col) - let (line, col) = idx.byte_to_lsp(emoji_end_byte); - assert_eq!(line, 0); - assert_eq!(col, 2, "emoji should count as 2 UTF-16 units"); - - // Back from (line,col)→bytes should land at the same boundary - let back = idx.lsp_to_byte(line, col); - assert_eq!(back, emoji_end_byte); -} diff --git a/crates/tools/prometeu-packer/.gitkeep b/crates/tools/prometeu-packer/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/phase-03-frontend-api.md b/docs/phase-03-frontend-api.md index 3491eb6b..7c3950fa 100644 --- a/docs/phase-03-frontend-api.md +++ b/docs/phase-03-frontend-api.md @@ -15,5 +15,5 @@ Implementation notes (PBS): Enforcement: -- A test in `prometeu-compiler` scans `src/backend/**` to ensure no references to `frontends::pbs` are introduced. +- A test in `prometeu-build-pipeline` scans `src/backend/**` to ensure no references to `frontends::pbs` are introduced. - Code review should reject any PRs that reintroduce prefix-based heuristics or FE-to-FE coupling.