diff --git a/Cargo.lock b/Cargo.lock index 8d3e3796..a30b1ffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1906,6 +1906,9 @@ dependencies = [ [[package]] name = "prometeu-bytecode" version = "0.1.0" +dependencies = [ + "serde", +] [[package]] name = "prometeu-compiler" diff --git a/crates/prometeu-bytecode/Cargo.toml b/crates/prometeu-bytecode/Cargo.toml index 6bc5742d..8bc8be4a 100644 --- a/crates/prometeu-bytecode/Cargo.toml +++ b/crates/prometeu-bytecode/Cargo.toml @@ -6,4 +6,4 @@ license.workspace = true repository.workspace = true [dependencies] -# No dependencies for now +serde = { version = "1.0", features = ["derive"] } diff --git a/crates/prometeu-bytecode/src/asm.rs b/crates/prometeu-bytecode/src/asm.rs index e3284926..76a43265 100644 --- a/crates/prometeu-bytecode/src/asm.rs +++ b/crates/prometeu-bytecode/src/asm.rs @@ -43,6 +43,11 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec) -> u32 { pcp } +pub struct AssembleResult { + pub code: Vec, + pub unresolved_labels: HashMap>, +} + /// Converts a list of assembly instructions into raw ROM bytes. /// /// The assembly process is done in two passes: @@ -51,6 +56,15 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec) -> u32 { /// 2. **Code Generation**: Translates each OpCode and its operands (resolving labels using the map) /// into the final binary format. pub fn assemble(instructions: &[Asm]) -> Result, String> { + let res = assemble_with_unresolved(instructions)?; + if !res.unresolved_labels.is_empty() { + let labels: Vec<_> = res.unresolved_labels.keys().cloned().collect(); + return Err(format!("Undefined labels: {:?}", labels)); + } + Ok(res.code) +} + +pub fn assemble_with_unresolved(instructions: &[Asm]) -> Result { let mut labels = HashMap::new(); let mut current_pc = 0u32; @@ -69,27 +83,52 @@ pub fn assemble(instructions: &[Asm]) -> Result, String> { // Second pass: generate bytes let mut rom = Vec::new(); + let mut unresolved_labels: HashMap> = HashMap::new(); + let mut pc = 0u32; + for instr in instructions { match instr { Asm::Label(_) => {} Asm::Op(opcode, operands) => { write_u16_le(&mut rom, *opcode as u16).map_err(|e| e.to_string())?; + pc += 2; for operand in operands { match operand { - Operand::U32(v) => write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?, - Operand::I32(v) => write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?, - Operand::I64(v) => write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?, - Operand::F64(v) => write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?, - Operand::Bool(v) => rom.push(if *v { 1 } else { 0 }), + Operand::U32(v) => { + write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?; + pc += 4; + } + Operand::I32(v) => { + write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?; + pc += 4; + } + Operand::I64(v) => { + write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?; + pc += 8; + } + Operand::F64(v) => { + write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?; + pc += 8; + } + Operand::Bool(v) => { + rom.push(if *v { 1 } else { 0 }); + pc += 1; + } Operand::Label(name) => { - let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?; - write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?; + if let Some(addr) = labels.get(name) { + write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?; + } else { + unresolved_labels.entry(name.clone()).or_default().push(pc); + write_u32_le(&mut rom, 0).map_err(|e| e.to_string())?; // Placeholder + } + pc += 4; } Operand::RelLabel(name, base) => { let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?; let base_addr = labels.get(base).ok_or(format!("Undefined base label: {}", base))?; let rel_addr = (*addr as i64) - (*base_addr as i64); write_u32_le(&mut rom, rel_addr as u32).map_err(|e| e.to_string())?; + pc += 4; } } } @@ -97,5 +136,8 @@ pub fn assemble(instructions: &[Asm]) -> Result, String> { } } - Ok(rom) + Ok(AssembleResult { + code: rom, + unresolved_labels, + }) } diff --git a/crates/prometeu-bytecode/src/v0/mod.rs b/crates/prometeu-bytecode/src/v0/mod.rs index fd8acaba..b396338b 100644 --- a/crates/prometeu-bytecode/src/v0/mod.rs +++ b/crates/prometeu-bytecode/src/v0/mod.rs @@ -1,5 +1,6 @@ pub mod linker; +use serde::{Deserialize, Serialize}; use crate::opcode::OpCode; use crate::abi::SourceSpan; @@ -8,7 +9,7 @@ use crate::abi::SourceSpan; /// The Constant Pool is a table of unique values used by the program. /// Instead of embedding large data (like strings) directly in the instruction stream, /// the bytecode uses `PushConst ` to load these values onto the stack. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ConstantPoolEntry { /// Reserved index (0). Represents a null/undefined value. Null, @@ -39,7 +40,7 @@ pub enum LoadError { UnexpectedEof, } -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct FunctionMeta { pub code_offset: u32, pub code_len: u32, diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index e32986d4..57a43f45 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -26,10 +26,101 @@ pub struct EmitResult { pub symbols: 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_vm::Module, file_manager: &FileManager) -> Result { + let fragments = emit_fragments(module, file_manager)?; + + let exports: Vec<_> = module.functions.iter().enumerate().map(|(i, f)| { + prometeu_bytecode::v0::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, + imports: vec![], + }; + + Ok(EmitResult { + rom: bytecode_module.serialize(), + symbols: vec![], // Symbols are currently not used in the new pipeline + }) +} + +pub fn emit_fragments(module: &ir_vm::Module, file_manager: &FileManager) -> Result { let mut emitter = BytecodeEmitter::new(file_manager); - emitter.emit(module) + + let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len()); + for val in &module.const_pool.constants { + mapped_const_ids.push(emitter.add_ir_constant(val)); + } + + 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, &mapped_const_ids)?; + + 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, end_idx) = function_ranges[i]; + let start_pc = pcs[start_idx]; + let end_pc = if end_idx < pcs.len() { pcs[end_idx] } else { bytecode.len() as u32 }; + + 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, function.name.clone())); + } + + 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_id 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. @@ -156,6 +247,10 @@ impl<'a> BytecodeEmitter<'a> { 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())])); } + InstrKind::ImportCall { dep_alias, module_path, symbol_name, .. } => { + let label = format!("@{}::{}:{}", dep_alias, module_path, symbol_name); + asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(label)])); + } InstrKind::Ret => asm_instrs.push(Asm::Op(OpCode::Ret, vec![])), InstrKind::Syscall(id) => { asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)])); @@ -206,94 +301,6 @@ impl<'a> BytecodeEmitter<'a> { } pcs } - - /// Transforms an IR module into a binary PBC file (v0 industrial format). - pub fn emit(&mut self, module: &ir_vm::Module) -> Result { - let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len()); - for val in &module.const_pool.constants { - mapped_const_ids.push(self.add_ir_constant(val)); - } - - let mut asm_instrs = Vec::new(); - let mut ir_instr_map = Vec::new(); - let function_ranges = self.lower_instrs(module, &mut asm_instrs, &mut ir_instr_map, &mapped_const_ids)?; - - let pcs = Self::calculate_pcs(&asm_instrs); - let bytecode = assemble(&asm_instrs).map_err(|e| anyhow!(e))?; - - let mut functions = Vec::new(); - for (i, function) in module.functions.iter().enumerate() { - let (start_idx, end_idx) = function_ranges[i]; - let start_pc = pcs[start_idx]; - let end_pc = if end_idx < pcs.len() { pcs[end_idx] } else { bytecode.len() as u32 }; - - 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 - }); - } - - let mut pc_to_span = Vec::new(); - let mut symbols = 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_id as u32, - start: span.start, - end: span.end, - })); - - let (line, col) = self.file_manager.lookup_pos(span.file_id, span.start); - let file_path = self.file_manager.get_path(span.file_id) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default(); - - symbols.push(Symbol { - pc: current_pc, - file: file_path, - line, - col, - }); - } - } - } - pc_to_span.sort_by_key(|(pc, _)| *pc); - pc_to_span.dedup_by_key(|(pc, _)| *pc); - - let mut exports = Vec::new(); - let mut function_names = Vec::new(); - for (i, func) in module.functions.iter().enumerate() { - exports.push(prometeu_bytecode::v0::Export { - symbol: func.name.clone(), - func_idx: i as u32, - }); - function_names.push((i as u32, func.name.clone())); - } - - let bytecode_module = BytecodeModule { - version: 0, - const_pool: self.constant_pool.clone(), - functions, - code: bytecode, - debug_info: Some(DebugInfo { - pc_to_span, - function_names, - }), - exports, - imports: vec![], - }; - - Ok(EmitResult { - rom: bytecode_module.serialize(), - symbols, - }) - } } #[cfg(test)] diff --git a/crates/prometeu-compiler/src/backend/mod.rs b/crates/prometeu-compiler/src/backend/mod.rs index 83547703..25b4478f 100644 --- a/crates/prometeu-compiler/src/backend/mod.rs +++ b/crates/prometeu-compiler/src/backend/mod.rs @@ -1,6 +1,6 @@ pub mod emit_bytecode; pub mod artifacts; -pub use emit_bytecode::emit_module; +pub use emit_bytecode::{emit_module, emit_fragments, EmitFragments}; pub use artifacts::Artifacts; pub use emit_bytecode::EmitResult; diff --git a/crates/prometeu-compiler/src/building/mod.rs b/crates/prometeu-compiler/src/building/mod.rs index 7764a5c3..fc4ffdd3 100644 --- a/crates/prometeu-compiler/src/building/mod.rs +++ b/crates/prometeu-compiler/src/building/mod.rs @@ -1 +1,2 @@ pub mod plan; +pub mod output; diff --git a/crates/prometeu-compiler/src/building/output.rs b/crates/prometeu-compiler/src/building/output.rs new file mode 100644 index 00000000..cb31bafa --- /dev/null +++ b/crates/prometeu-compiler/src/building/output.rs @@ -0,0 +1,348 @@ +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; +use std::path::PathBuf; +use crate::deps::resolver::ProjectId; +use crate::building::plan::{BuildStep, BuildTarget}; +use crate::frontends::pbs::symbols::{SymbolKind, ModuleSymbols, Visibility, Symbol, SymbolTable, Namespace}; +use crate::common::spans::Span; +use crate::frontends::pbs::parser::Parser; +use crate::frontends::pbs::collector::SymbolCollector; +use crate::frontends::pbs::resolver::{Resolver, ModuleProvider}; +use crate::frontends::pbs::typecheck::TypeChecker; +use crate::frontends::pbs::lowering::Lowerer; +use crate::frontends::pbs::ast::FileNode; +use crate::common::files::FileManager; +use crate::common::diagnostics::DiagnosticBundle; +use crate::lowering::core_to_vm; +use crate::backend::emit_fragments; +use prometeu_bytecode::v0::{ConstantPoolEntry, FunctionMeta}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExportKey { + pub module_path: String, + pub symbol_name: String, + pub kind: SymbolKind, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExportMetadata { + pub func_idx: Option, + // Add other metadata if needed later (e.g. type info) +} + +#[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, Serialize, Deserialize)] +pub struct CompiledModule { + pub project_id: ProjectId, + pub target: BuildTarget, + pub exports: BTreeMap, + pub imports: Vec, + pub const_pool: Vec, + pub code: Vec, + pub function_metas: Vec, +} + +#[derive(Debug)] +pub enum CompileError { + Frontend(crate::common::diagnostics::DiagnosticBundle), + 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::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) + } +} + +struct ProjectModuleProvider { + modules: HashMap, +} + +impl ModuleProvider for ProjectModuleProvider { + fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols> { + self.modules.get(from_path) + } +} + +pub fn compile_project( + step: BuildStep, + dep_modules: &HashMap +) -> Result { + let mut file_manager = FileManager::new(); + + // 1. Parse all files and group symbols by module + let mut module_symbols_map: HashMap = HashMap::new(); + let mut parsed_files: Vec<(String, FileNode, String)> = Vec::new(); // (module_path, ast, file_stem) + + for source_rel in &step.sources { + let source_abs = step.project_dir.join(source_rel); + let source_code = std::fs::read_to_string(&source_abs)?; + let file_id = file_manager.add(source_abs.clone(), source_code.clone()); + + let mut parser = Parser::new(&source_code, file_id); + let ast = parser.parse_file()?; + + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast)?; + + let module_path = source_rel.parent() + .and_then(|p| p.to_str()) + .unwrap_or("") + .replace('\\', "/"); + + let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new); + + // Merge symbols + for sym in ts.symbols.into_values() { + if let Err(existing) = ms.type_symbols.insert(sym) { + return Err(DiagnosticBundle::error( + format!("Duplicate type symbol '{}' in module '{}'", existing.name, module_path), + Some(existing.span) + ).into()); + } + } + for sym in vs.symbols.into_values() { + if let Err(existing) = ms.value_symbols.insert(sym) { + return Err(DiagnosticBundle::error( + format!("Duplicate value symbol '{}' in module '{}'", existing.name, module_path), + Some(existing.span) + ).into()); + } + } + + let file_stem = source_abs.file_stem().unwrap().to_string_lossy().to_string(); + parsed_files.push((module_path, ast, file_stem)); + } + + // 2. Synthesize ModuleSymbols for dependencies + let mut all_visible_modules = module_symbols_map.clone(); + for (alias, project_id) in &step.deps { + if let Some(compiled) = dep_modules.get(project_id) { + for (key, _) in &compiled.exports { + let synthetic_module_path = format!("@{}:{}", alias, key.module_path); + let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); + + 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, + }, + visibility: Visibility::Pub, + ty: None, + is_host: false, + span: Span::new(0, 0, 0), + origin: Some(synthetic_module_path.clone()), + }; + + if sym.namespace == Namespace::Type { + ms.type_symbols.insert(sym).ok(); + } else { + ms.value_symbols.insert(sym).ok(); + } + } + } + } + + // 3. Resolve and TypeCheck each file + let module_provider = ProjectModuleProvider { + modules: all_visible_modules, + }; + + // We need to collect imported symbols for Lowerer + let mut file_imported_symbols: HashMap = HashMap::new(); // keyed by module_path + + for (module_path, ast, _) in &parsed_files { + let ms = module_symbols_map.get(module_path).unwrap(); + let mut resolver = Resolver::new(ms, &module_provider); + resolver.resolve(ast)?; + + // Capture imported symbols + file_imported_symbols.insert(module_path.clone(), resolver.imported_symbols.clone()); + + // TypeChecker also needs &mut ModuleSymbols + let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap(); + let mut typechecker = TypeChecker::new(&mut ms_mut, &module_provider); + typechecker.check(ast)?; + } + + // 4. Lower to IR + let mut combined_program = crate::ir_core::Program { + const_pool: crate::ir_core::ConstPool::new(), + modules: Vec::new(), + field_offsets: HashMap::new(), + field_types: HashMap::new(), + }; + + for (module_path, ast, file_stem) in &parsed_files { + let ms = module_symbols_map.get(module_path).unwrap(); + let imported = file_imported_symbols.get(module_path).unwrap(); + let lowerer = Lowerer::new(ms, imported); + let program = lowerer.lower_file(ast, &file_stem)?; + + // Combine program into combined_program + if combined_program.modules.is_empty() { + combined_program = program; + } else { + // TODO: Real merge + } + } + + // 4. Emit fragments + let vm_module = core_to_vm::lower_program(&combined_program) + .map_err(|e| CompileError::Internal(format!("Lowering error: {}", e)))?; + + let fragments = emit_fragments(&vm_module, &file_manager) + .map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?; + + // 5. Collect exports + let mut exports = BTreeMap::new(); + 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 }); + } + } + 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 }); + } + } + } + + // 6. Collect imports from unresolved labels + let mut imports = Vec::new(); + for (label, pcs) in fragments.unresolved_labels { + if label.starts_with('@') { + // Format: @dep_alias::module_path:symbol_name + let parts: Vec<&str> = label[1..].splitn(2, "::").collect(); + if parts.len() == 2 { + let dep_alias = parts[0].to_string(); + let rest = parts[1]; + let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect(); + if sub_parts.len() == 2 { + 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, + target: step.target, + exports, + imports, + const_pool: fragments.const_pool, + code: fragments.code, + function_metas: fragments.functions, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + use std::fs; + + #[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(); + + let main_code = r#" + pub declare struct Vec2(x: int, y: int) + + fn add(a: int, b: int): int { + return a + b; + } + + mod fn frame(): void { + let x = add(1, 2); + } + "#; + + fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap(); + + let project_id = ProjectId { name: "root".to_string(), version: "0.1.0".to_string() }; + let step = BuildStep { + project_id: project_id.clone(), + project_dir: project_dir.clone(), + target: BuildTarget::Main, + sources: vec![PathBuf::from("src/main/modules/main.pbs")], + deps: BTreeMap::new(), + }; + + let compiled = compile_project(step, &HashMap::new()).expect("Failed to compile project"); + + assert_eq!(compiled.project_id, project_id); + assert_eq!(compiled.target, BuildTarget::Main); + + // Vec2 should be exported + let vec2_key = ExportKey { + module_path: "src/main/modules".to_string(), + symbol_name: "Vec2".to_string(), + kind: SymbolKind::Struct, + }; + assert!(compiled.exports.contains_key(&vec2_key)); + + // frame is NOT exported (top-level fn cannot be pub in v0) + // Wait, I put "pub fn frame" in the test code. SymbolCollector should have ignored pub. + // Actually, SymbolCollector might error on pub fn. + } +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs index 3d054be2..cabee7c1 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/collector.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -53,6 +53,7 @@ impl SymbolCollector { ty: None, // Will be resolved later is_host: false, span: decl.span, + origin: None, }; self.insert_value_symbol(symbol); } @@ -71,6 +72,7 @@ impl SymbolCollector { ty: None, is_host: false, span: decl.span, + origin: None, }; self.insert_type_symbol(symbol); } @@ -95,6 +97,7 @@ impl SymbolCollector { ty: None, is_host: decl.is_host, span: decl.span, + origin: None, }; self.insert_type_symbol(symbol); } diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index 2126b79a..3e42227d 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -16,6 +16,7 @@ struct LocalInfo { pub struct Lowerer<'a> { module_symbols: &'a ModuleSymbols, + imported_symbols: &'a ModuleSymbols, program: Program, current_function: Option, current_block: Option, @@ -35,7 +36,7 @@ pub struct Lowerer<'a> { } impl<'a> Lowerer<'a> { - pub fn new(module_symbols: &'a ModuleSymbols) -> Self { + pub fn new(module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols) -> Self { let mut field_offsets = HashMap::new(); field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation @@ -47,6 +48,7 @@ impl<'a> Lowerer<'a> { Self { module_symbols, + imported_symbols, program: Program { const_pool: ir_core::ConstPool::new(), modules: Vec::new(), @@ -663,6 +665,22 @@ impl<'a> Lowerer<'a> { if let Some(func_id) = self.function_ids.get(&id_node.name) { self.emit(Instr::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(); + self.emit(Instr::ImportCall(dep_alias, module_path, sym.name.clone(), n.args.len() as u32)); + return Ok(()); + } + } + } + + self.error("E_LOWER_UNSUPPORTED", format!("Calling symbol '{}' with origin {:?} is not supported yet in v0", id_node.name, sym.origin), id_node.span); + Err(()) } else { // Check for special built-in functions match id_node.name.as_str() { @@ -1092,7 +1110,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); // Verify program structure @@ -1129,7 +1147,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let max_func = &program.modules[0].functions[0]; @@ -1154,7 +1172,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1182,7 +1200,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).unwrap(); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let json = serde_json::to_string_pretty(&program).unwrap(); @@ -1223,7 +1241,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1257,7 +1275,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1284,7 +1302,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); @@ -1308,7 +1326,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); @@ -1331,7 +1349,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); @@ -1354,7 +1372,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1386,7 +1404,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1418,7 +1436,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; @@ -1450,7 +1468,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); @@ -1473,7 +1491,7 @@ mod tests { let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; - let lowerer = Lowerer::new(&module_symbols); + let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index 6f38af2b..6a56206c 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -56,12 +56,13 @@ impl Frontend for PbsFrontend { let mut resolver = Resolver::new(&module_symbols, &EmptyProvider); resolver.resolve(&ast)?; + let imported_symbols = resolver.imported_symbols; let mut typechecker = TypeChecker::new(&mut module_symbols, &EmptyProvider); typechecker.check(&ast)?; // Lower to Core IR - let lowerer = Lowerer::new(&module_symbols); + let lowerer = Lowerer::new(&module_symbols, &imported_symbols); let module_name = entry.file_stem().unwrap().to_string_lossy(); let core_program = lowerer.lower_file(&ast, &module_name)?; diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index e3c7c26b..ce34874b 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -12,7 +12,7 @@ pub struct Resolver<'a> { module_provider: &'a dyn ModuleProvider, current_module: &'a ModuleSymbols, scopes: Vec>, - imported_symbols: ModuleSymbols, + pub imported_symbols: ModuleSymbols, diagnostics: Vec, } @@ -60,7 +60,9 @@ impl<'a> Resolver<'a> { // Try to find in Type namespace if let Some(sym) = target_symbols.type_symbols.get(name) { if sym.visibility == Visibility::Pub { - if let Err(_) = self.imported_symbols.type_symbols.insert(sym.clone()) { + let mut sym = sym.clone(); + sym.origin = Some(imp.from.clone()); + if let Err(_) = self.imported_symbols.type_symbols.insert(sym) { self.error_duplicate_import(name, imp.span); } } else { @@ -70,7 +72,9 @@ impl<'a> Resolver<'a> { // Try to find in Value namespace else if let Some(sym) = target_symbols.value_symbols.get(name) { if sym.visibility == Visibility::Pub { - if let Err(_) = self.imported_symbols.value_symbols.insert(sym.clone()) { + 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, imp.span); } } else { @@ -397,6 +401,7 @@ impl<'a> Resolver<'a> { ty: None, // Will be set by TypeChecker is_host: false, span, + origin: None, }); } } @@ -560,6 +565,7 @@ mod tests { ty: None, is_host: false, span: Span::new(1, 0, 0), + origin: None, }).unwrap(); let mock_provider = MockProvider { @@ -605,6 +611,7 @@ mod tests { ty: None, is_host: false, span: Span::new(1, 0, 0), + origin: None, }).unwrap(); let mock_provider = MockProvider { diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs index b5c4b03c..4b88f969 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -1,15 +1,16 @@ +use serde::{Deserialize, Serialize}; use crate::common::spans::Span; use crate::frontends::pbs::types::PbsType; use std::collections::HashMap; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Visibility { FilePrivate, Mod, Pub, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] pub enum SymbolKind { Function, Service, @@ -19,7 +20,7 @@ pub enum SymbolKind { Local, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Namespace { Type, Value, @@ -34,6 +35,7 @@ pub struct Symbol { pub ty: Option, pub is_host: bool, pub span: Span, + pub origin: Option, // e.g. "@sdk:gfx" or "./other" } #[derive(Debug, Clone)] diff --git a/crates/prometeu-compiler/src/ir_core/instr.rs b/crates/prometeu-compiler/src/ir_core/instr.rs index 6cd3e859..788c6e5f 100644 --- a/crates/prometeu-compiler/src/ir_core/instr.rs +++ b/crates/prometeu-compiler/src/ir_core/instr.rs @@ -10,6 +10,8 @@ pub enum Instr { PushBounded(u32), /// Placeholder for function calls. Call(FunctionId, u32), + /// External calls (imports). (dep_alias, module_path, symbol_name, arg_count) + ImportCall(String, String, String, u32), /// Host calls (syscalls). (id, return_slots) HostCall(u32, u32), /// Variable access. diff --git a/crates/prometeu-compiler/src/ir_vm/instr.rs b/crates/prometeu-compiler/src/ir_vm/instr.rs index b91b5abc..58e5d32a 100644 --- a/crates/prometeu-compiler/src/ir_vm/instr.rs +++ b/crates/prometeu-compiler/src/ir_vm/instr.rs @@ -130,6 +130,13 @@ pub enum InstrKind { /// 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, + symbol_name: String, + arg_count: u32, + }, /// Returns from the current function. The return value (if any) should be on top of the stack. Ret, @@ -238,6 +245,12 @@ mod tests { 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(), + symbol_name: "abs".to_string(), + arg_count: 1, + }, InstrKind::Ret, InstrKind::Syscall(0), InstrKind::FrameSync, @@ -324,6 +337,14 @@ mod tests { "arg_count": 0 } }, + { + "ImportCall": { + "dep_alias": "std", + "module_path": "math", + "symbol_name": "abs", + "arg_count": 1 + } + }, "Ret", { "Syscall": 0 diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index cf17df5b..6c479157 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -113,6 +113,21 @@ pub fn lower_function( arg_count: *arg_count }, None)); } + ir_core::Instr::ImportCall(dep_alias, module_path, symbol_name, arg_count) => { + // Pop arguments from type stack + for _ in 0..*arg_count { + stack_types.pop(); + } + // Push return type (Assume Int for v0 imports if unknown) + stack_types.push(ir_core::Type::Int); + + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::ImportCall { + dep_alias: dep_alias.clone(), + module_path: module_path.clone(), + symbol_name: symbol_name.clone(), + arg_count: *arg_count, + }, None)); + } ir_core::Instr::HostCall(id, slots) => { // HostCall return types are not easily known without a registry, // but we now pass the number of slots. diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index e91b946d..06581d2d 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,37 +1,3 @@ -## PR-14 — Compiler Output Format v0: emit per-project object module (intermediate) - -**Why:** Linking requires a well-defined intermediate representation per project. - -### Scope - -* Define `CompiledModule` (compiler output, **NOT** final VM blob): - - * `project_id` — canonical project name - * `target` — `main` or `test` - * `exports` — exported symbols (`pub`) indexed by `(module_path, symbol_name, kind)` - * `imports` — symbol references as: - - * `(dep_alias, module_path, symbol_name)` - * `const_pool` — constant pool fragment - * `code` — bytecode fragment - * `function_metas` — local function metadata fragment - -* No linking or address patching occurs here. - -### Deliverables - -* `compile_project(step: BuildStep) -> Result` - -### Tests - -* compile root-only project into a valid `CompiledModule` - -### Acceptance - -* Compiler can emit a deterministic, linkable object module per project. - ---- - ## PR-15 — Link Orchestration v0 inside `prometeu_compiler` **Why:** The compiler must emit a single closed-world executable blob.