From 6cf1e71b9998400411775cd28c2259b6bfcacdcf Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Mon, 2 Feb 2026 21:37:40 +0000 Subject: [PATCH] pr 64 --- .../prometeu-compiler/src/building/linker.rs | 13 ++-- .../prometeu-compiler/src/building/output.rs | 66 ++++++++++-------- .../src/frontends/pbs/ast.rs | 4 +- .../src/frontends/pbs/collector.rs | 34 +++++++-- .../src/frontends/pbs/parser.rs | 31 ++++---- .../src/frontends/pbs/symbols.rs | 4 +- crates/prometeu-compiler/src/lib.rs | 1 + .../src/semantics/export_surface.rs | 40 +++++++++++ crates/prometeu-compiler/src/semantics/mod.rs | 1 + .../tests/link_integration.rs | 2 +- crates/prometeu-core/tests/heartbeat.rs | 2 - test-cartridges/canonical/golden/ast.json | 4 +- test-cartridges/canonical/golden/program.pbc | Bin 1074 -> 1087 bytes test-cartridges/test01/cartridge/program.pbc | Bin 1074 -> 1087 bytes 14 files changed, 135 insertions(+), 67 deletions(-) create mode 100644 crates/prometeu-compiler/src/semantics/export_surface.rs create mode 100644 crates/prometeu-compiler/src/semantics/mod.rs diff --git a/crates/prometeu-compiler/src/building/linker.rs b/crates/prometeu-compiler/src/building/linker.rs index 3b801113..6248227d 100644 --- a/crates/prometeu-compiler/src/building/linker.rs +++ b/crates/prometeu-compiler/src/building/linker.rs @@ -237,14 +237,19 @@ impl Linker { 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.symbol_name), global_func_idx); + // Also provide short name for root module exports to facilitate entrypoint resolution + if !final_exports.contains_key(&key.symbol_name) { + final_exports.insert(key.symbol_name.clone(), global_func_idx); + } } } } - // v0: Fallback export for entrypoint `src/main/modules:frame` (root module) - if !final_exports.iter().any(|(name, _)| name.ends_with(":frame")) { + // 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); } } @@ -274,9 +279,9 @@ mod tests { use std::collections::BTreeMap; use super::*; use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata}; + use crate::semantics::export_surface::ExportSurfaceKind; use crate::building::plan::BuildTarget; use crate::deps::resolver::ProjectId; - use crate::frontends::pbs::symbols::SymbolKind; use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::FunctionMeta; @@ -294,7 +299,7 @@ mod tests { lib_exports.insert(ExportKey { module_path: "math".into(), symbol_name: "add".into(), - kind: SymbolKind::Function, + kind: ExportSurfaceKind::Service, }, ExportMetadata { func_idx: Some(0), is_host: false, ty: None }); let lib_module = CompiledModule { diff --git a/crates/prometeu-compiler/src/building/output.rs b/crates/prometeu-compiler/src/building/output.rs index 2273651a..d4febf7f 100644 --- a/crates/prometeu-compiler/src/building/output.rs +++ b/crates/prometeu-compiler/src/building/output.rs @@ -12,6 +12,7 @@ use crate::frontends::pbs::resolver::{ModuleProvider, Resolver}; use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility}; use crate::frontends::pbs::typecheck::TypeChecker; use crate::frontends::pbs::types::PbsType; +use crate::semantics::export_surface::ExportSurfaceKind; use crate::lowering::core_to_vm; use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use serde::{Deserialize, Serialize}; @@ -22,7 +23,7 @@ use std::path::Path; pub struct ExportKey { pub module_path: String, pub symbol_name: String, - pub kind: SymbolKind, + pub kind: ExportSurfaceKind, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -165,12 +166,17 @@ pub fn compile_project( let sym = Symbol { name: key.symbol_name.clone(), - kind: key.kind.clone(), - namespace: match key.kind { - SymbolKind::Function | SymbolKind::Service => Namespace::Value, - SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type, - _ => Namespace::Value, + kind: match key.kind { + ExportSurfaceKind::Service => SymbolKind::Service, + ExportSurfaceKind::DeclareType => { + match &meta.ty { + Some(PbsType::Contract(_)) => SymbolKind::Contract, + Some(PbsType::ErrorType(_)) => SymbolKind::ErrorType, + _ => SymbolKind::Struct, + } + } }, + namespace: key.kind.namespace(), visibility: Visibility::Pub, ty: meta.ty.clone(), is_host: meta.is_host, @@ -245,31 +251,35 @@ pub fn compile_project( for (module_path, ms) in &module_symbols_map { for sym in ms.type_symbols.symbols.values() { if sym.visibility == Visibility::Pub { - exports.insert(ExportKey { - module_path: module_path.clone(), - symbol_name: sym.name.clone(), - kind: sym.kind.clone(), - }, ExportMetadata { - func_idx: None, - is_host: sym.is_host, - ty: sym.ty.clone(), - }); + if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { + exports.insert(ExportKey { + module_path: module_path.clone(), + symbol_name: sym.name.clone(), + kind: surface_kind, + }, ExportMetadata { + func_idx: None, + is_host: sym.is_host, + ty: sym.ty.clone(), + }); + } } } for sym in ms.value_symbols.symbols.values() { if sym.visibility == Visibility::Pub { - // Find func_idx if it's a function or service - let func_idx = vm_module.functions.iter().position(|f| f.name == sym.name).map(|i| i as u32); - - exports.insert(ExportKey { - module_path: module_path.clone(), - symbol_name: sym.name.clone(), - kind: sym.kind.clone(), - }, ExportMetadata { - func_idx, - is_host: sym.is_host, - ty: sym.ty.clone(), - }); + if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) { + // Find func_idx if it's a function or service + let func_idx = vm_module.functions.iter().position(|f| f.name == sym.name).map(|i| i as u32); + + exports.insert(ExportKey { + module_path: module_path.clone(), + symbol_name: sym.name.clone(), + kind: surface_kind, + }, ExportMetadata { + func_idx, + is_host: sym.is_host, + ty: sym.ty.clone(), + }); + } } } } @@ -359,7 +369,7 @@ mod tests { let vec2_key = ExportKey { module_path: "src/main/modules".to_string(), symbol_name: "Vec2".to_string(), - kind: SymbolKind::Struct, + kind: ExportSurfaceKind::DeclareType, }; assert!(compiled.exports.contains_key(&vec2_key)); diff --git a/crates/prometeu-compiler/src/frontends/pbs/ast.rs b/crates/prometeu-compiler/src/frontends/pbs/ast.rs index ae0820e7..98c5a2dc 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/ast.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/ast.rs @@ -62,7 +62,7 @@ pub struct ImportSpecNode { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ServiceDeclNode { pub span: Span, - pub vis: String, // "pub" | "mod" + pub vis: Option, // "pub" | "mod" pub name: String, pub extends: Option, pub members: Vec, // ServiceFnSig @@ -86,7 +86,7 @@ pub struct ParamNode { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct FnDeclNode { pub span: Span, - pub vis: String, + pub vis: Option, pub name: String, pub params: Vec, pub ret: Option>, diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs index cabee7c1..f3ef16c8 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/collector.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -1,6 +1,7 @@ use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::frontends::pbs::ast::*; use crate::frontends::pbs::symbols::*; +use crate::semantics::export_surface::ExportSurfaceKind; pub struct SymbolCollector { type_symbols: SymbolTable, @@ -40,11 +41,14 @@ impl SymbolCollector { } fn collect_fn(&mut self, decl: &FnDeclNode) { - let vis = match decl.vis.as_str() { - "pub" => Visibility::Pub, - "mod" => Visibility::Mod, + let vis = match decl.vis.as_deref() { + Some("pub") => Visibility::Pub, + Some("mod") => Visibility::Mod, _ => Visibility::FilePrivate, }; + + self.check_export_eligibility(SymbolKind::Function, vis, decl.span); + let symbol = Symbol { name: decl.name.clone(), kind: SymbolKind::Function, @@ -59,11 +63,13 @@ impl SymbolCollector { } fn collect_service(&mut self, decl: &ServiceDeclNode) { - let vis = match decl.vis.as_str() { - "pub" => Visibility::Pub, - "mod" => Visibility::Mod, - _ => Visibility::FilePrivate, // Should not happen with valid parser + let vis = match decl.vis.as_deref() { + Some("pub") => Visibility::Pub, + _ => Visibility::Mod, // Defaults to Mod }; + + self.check_export_eligibility(SymbolKind::Service, vis, decl.span); + let symbol = Symbol { name: decl.name.clone(), kind: SymbolKind::Service, @@ -89,6 +95,9 @@ impl SymbolCollector { "error" => SymbolKind::ErrorType, _ => SymbolKind::Struct, // Default }; + + self.check_export_eligibility(kind.clone(), vis, decl.span); + let symbol = Symbol { name: decl.name.clone(), kind, @@ -148,4 +157,15 @@ impl SymbolCollector { span: Some(symbol.span), }); } + + 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 { + level: DiagnosticLevel::Error, + code: Some("E_SEMANTIC_EXPORT_RESTRICTION".to_string()), + message: msg, + span: Some(span), + }); + } + } } diff --git a/crates/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/prometeu-compiler/src/frontends/pbs/parser.rs index 76af9949..9fb354ec 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/parser.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/parser.rs @@ -154,7 +154,7 @@ impl Parser { fn parse_top_level_decl(&mut self) -> Result { match self.peek().kind { - TokenKind::Fn => self.parse_fn_decl("file".to_string()), + 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") { @@ -181,23 +181,14 @@ impl Parser { }; match self.peek().kind { - TokenKind::Service => self.parse_service_decl(vis.unwrap_or_else(|| "pub".to_string())), + TokenKind::Service => self.parse_service_decl(vis), TokenKind::Declare => self.parse_type_decl(vis), - TokenKind::Fn => { - let vis_str = vis.unwrap_or_else(|| "file".to_string()); - if vis_str == "pub" { - return Err(self.error_with_code( - "Functions cannot be public. They are always mod or file-private.", - Some("E_RESOLVE_VISIBILITY"), - )); - } - self.parse_fn_decl(vis_str) - } + TokenKind::Fn => self.parse_fn_decl(vis), _ => Err(self.error("Expected 'service', 'declare', or 'fn'")), } } - fn parse_service_decl(&mut self, vis: String) -> Result { + 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; @@ -362,7 +353,7 @@ impl Parser { })) } - fn parse_fn_decl(&mut self, vis: String) -> Result { + 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()?; @@ -1181,7 +1172,7 @@ fn good() {} let mut parser = Parser::new(source, 0); let result = parser.parse_file().expect("mod fn should be allowed"); if let Node::FnDecl(fn_decl) = &result.decls[0] { - assert_eq!(fn_decl.vis, "mod"); + assert_eq!(fn_decl.vis, Some("mod".to_string())); } else { panic!("Expected FnDecl"); } @@ -1191,10 +1182,12 @@ fn good() {} fn test_parse_pub_fn() { let source = "pub fn test() {}"; let mut parser = Parser::new(source, 0); - let result = parser.parse_file(); - assert!(result.is_err(), "pub fn should be disallowed"); - let err = result.unwrap_err(); - assert!(err.diagnostics[0].message.contains("Functions cannot be public")); + let result = parser.parse_file().expect("pub fn should be allowed in parser"); + if let Node::FnDecl(fn_decl) = &result.decls[0] { + assert_eq!(fn_decl.vis, Some("pub".to_string())); + } else { + panic!("Expected FnDecl"); + } } #[test] diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs index 511508a9..3799fe0f 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -3,14 +3,14 @@ use crate::frontends::pbs::types::PbsType; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Visibility { FilePrivate, Mod, Pub, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] pub enum SymbolKind { Function, Service, diff --git a/crates/prometeu-compiler/src/lib.rs b/crates/prometeu-compiler/src/lib.rs index ed59ec06..423d6a83 100644 --- a/crates/prometeu-compiler/src/lib.rs +++ b/crates/prometeu-compiler/src/lib.rs @@ -48,6 +48,7 @@ pub mod manifest; pub mod deps; pub mod sources; pub mod building; +pub mod semantics; use anyhow::Result; use clap::{Parser, Subcommand}; diff --git a/crates/prometeu-compiler/src/semantics/export_surface.rs b/crates/prometeu-compiler/src/semantics/export_surface.rs new file mode 100644 index 00000000..ede6eab5 --- /dev/null +++ b/crates/prometeu-compiler/src/semantics/export_surface.rs @@ -0,0 +1,40 @@ +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 +} + +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) + } + SymbolKind::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, + } + } +} diff --git a/crates/prometeu-compiler/src/semantics/mod.rs b/crates/prometeu-compiler/src/semantics/mod.rs new file mode 100644 index 00000000..51e988b2 --- /dev/null +++ b/crates/prometeu-compiler/src/semantics/mod.rs @@ -0,0 +1 @@ +pub mod export_surface; diff --git a/crates/prometeu-compiler/tests/link_integration.rs b/crates/prometeu-compiler/tests/link_integration.rs index d10bfc6c..dab6ca8d 100644 --- a/crates/prometeu-compiler/tests/link_integration.rs +++ b/crates/prometeu-compiler/tests/link_integration.rs @@ -67,7 +67,7 @@ fn test_integration_test01_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, "").expect("Failed to initialize VM"); + vm.initialize(unit.rom, "frame").expect("Failed to initialize VM"); let mut native = SimpleNative; let mut hw = SimpleHardware::new(); diff --git a/crates/prometeu-core/tests/heartbeat.rs b/crates/prometeu-core/tests/heartbeat.rs index 6b854714..dcccb73f 100644 --- a/crates/prometeu-core/tests/heartbeat.rs +++ b/crates/prometeu-core/tests/heartbeat.rs @@ -6,7 +6,6 @@ use prometeu_core::virtual_machine::{LogicalFrameEndingReason, VirtualMachine}; use prometeu_core::Hardware; use std::fs; use std::path::Path; -use prometeu_bytecode::BytecodeLoader; struct MockNative; impl NativeInterface for MockNative { @@ -40,7 +39,6 @@ fn test_canonical_cartridge_heartbeat() { let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?"); // Determine entrypoint from the compiled module exports - let module = BytecodeLoader::load(&pbc_bytes).expect("Failed to parse PBC"); let entry_symbol = "src/main/modules:frame"; let mut vm = VirtualMachine::new(vec![], vec![]); diff --git a/test-cartridges/canonical/golden/ast.json b/test-cartridges/canonical/golden/ast.json index 64bf21af..36c3b763 100644 --- a/test-cartridges/canonical/golden/ast.json +++ b/test-cartridges/canonical/golden/ast.json @@ -641,7 +641,7 @@ "start": 739, "end": 788 }, - "vis": "file", + "vis": null, "name": "add", "params": [ { @@ -743,7 +743,7 @@ "start": 790, "end": 1180 }, - "vis": "file", + "vis": null, "name": "frame", "params": [], "ret": { diff --git a/test-cartridges/canonical/golden/program.pbc b/test-cartridges/canonical/golden/program.pbc index 807e2d8bce710f6d3efbbf102e9abbe1fa933a1a..3a84bc0cfffebf8d23cbab9645ffb94ecc299c98 100644 GIT binary patch delta 32 mcmdnQv7ci?1f%}O$nDIGOp^s!6nPmL7#LWAIISo#Hx&Sh;|E9p delta 18 Zcmdnbv58|s1f$Z%$nDIGjFSaf6ahK~1snhX diff --git a/test-cartridges/test01/cartridge/program.pbc b/test-cartridges/test01/cartridge/program.pbc index 807e2d8bce710f6d3efbbf102e9abbe1fa933a1a..3a84bc0cfffebf8d23cbab9645ffb94ecc299c98 100644 GIT binary patch delta 32 mcmdnQv7ci?1f%}O$nDIGOp^s!6nPmL7#LWAIISo#Hx&Sh;|E9p delta 18 Zcmdnbv58|s1f$Z%$nDIGjFSaf6ahK~1snhX