use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; 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::types::PbsType; use crate::ir_core; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId}; use crate::ir_core::{Block, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type}; use prometeu_analysis::NameInterner; use std::collections::HashMap; #[derive(Clone)] struct LocalInfo { slot: u32, ty: Type, } pub struct Lowerer<'a> { module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, 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, contract_registry: ContractRegistry, diagnostics: Vec, max_slots_used: u32, current_span: Option, } impl<'a> Lowerer<'a> { pub fn new( module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols, 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); struct_slots.insert("Pad".to_string(), 48); struct_slots.insert("Touch".to_string(), 6); Self { module_symbols, imported_symbols, interner, program: Program { const_pool: ir_core::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, contract_registry: ContractRegistry::new(), diagnostics: Vec::new(), max_slots_used: 0, current_span: None, } } fn error(&mut self, code: &str, message: String, span: crate::common::spans::Span) { self.diagnostics.push(Diagnostic { level: DiagnosticLevel::Error, code: Some(code.to_string()), message, span: Some(span), }); } pub fn lower_file(mut self, file: &FileNode, module_name: &str) -> Result { // Pre-scan for function declarations to assign IDs for decl in &file.decls { if let Node::FnDecl(n) = decl { let id = FunctionId(self.next_func_id); self.next_func_id += 1; self.function_ids .insert(self.interner.resolve(n.name).to_string(), id); } if let Node::TypeDecl(n) = decl { let id = TypeId(self.next_type_id); self.next_type_id += 1; self.type_ids .insert(self.interner.resolve(n.name).to_string(), id); } } // Second pre-scan: calculate struct slots (recursive) let mut struct_nodes = HashMap::new(); for decl in &file.decls { if let Node::TypeDecl(n) = decl { if n.type_kind == "struct" { struct_nodes.insert(self.interner.resolve(n.name).to_string(), n); } } } let mut changed = true; while changed { changed = false; for (name, node) in &struct_nodes { if !self.struct_slots.contains_key(name) { let mut slots = 0; let mut all_known = true; for param in &node.params { let member_ty = self.lower_type_node(¶m.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 Node::TypeDecl(n) = decl { let type_name = self.interner.resolve(n.name).to_string(); let mut constants = HashMap::new(); for c in &n.constants { constants.insert(self.interner.resolve(c.name).to_string(), *c.value.clone()); } self.type_constants.insert(type_name.clone(), constants); let mut ctors = HashMap::new(); // Default constructor: TypeName(...) if n.type_kind == "struct" { let mut params = Vec::new(); let mut initializers = Vec::new(); for p in &n.params { params.push(p.clone()); initializers.push(Node::Ident(IdentNode { span: p.span, name: p.name.clone(), })); } let default_ctor = ConstructorDeclNode { span: n.span, params, initializers, name: n.name, body: Box::new(Node::Block(BlockNode { span: n.span, stmts: Vec::new(), tail: None, })), }; ctors.insert(type_name.clone(), default_ctor); } for ctor in &n.constructors { ctors.insert(self.interner.resolve(ctor.name).to_string(), ctor.clone()); } self.struct_constructors.insert(type_name, ctors); } } let mut module = Module { name: module_name.to_string(), functions: Vec::new(), }; for decl in &file.decls { match decl { Node::FnDecl(fn_decl) => { let func = self.lower_function(fn_decl).map_err(|_| DiagnosticBundle { diagnostics: self.diagnostics.clone(), })?; module.functions.push(func); } _ => {} // Other declarations not handled for now } } self.program.modules.push(module); Ok(self.program) } fn lower_function(&mut self, n: &FnDeclNode) -> Result { 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(¶m.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); let func = Function { id: func_id, name: func_name, 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_node(&mut self, node: &Node) -> Result<(), ()> { let old_span = self.current_span; self.current_span = Some(node.span()); let res = match node { Node::Block(n) => self.lower_block(n), Node::LetStmt(n) => self.lower_let_stmt(n), Node::ExprStmt(n) => self.lower_node(&n.expr), Node::ReturnStmt(n) => self.lower_return_stmt(n), Node::IntLit(n) => { let id = self.program.const_pool.add_int(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } Node::FloatLit(n) => { let id = self.program.const_pool.add_float(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } Node::StringLit(n) => { let id = self.program.const_pool.add_string(n.value.clone()); self.emit(InstrKind::PushConst(id)); Ok(()) } Node::BoundedLit(n) => { self.emit(InstrKind::PushBounded(n.value)); Ok(()) } Node::Ident(n) => self.lower_ident(n), Node::MemberAccess(n) => self.lower_member_access(n), Node::Call(n) => self.lower_call(n), Node::Binary(n) => self.lower_binary(n), Node::Unary(n) => self.lower_unary(n), Node::IfExpr(n) => self.lower_if_expr(n), Node::Alloc(n) => self.lower_alloc(n), Node::Mutate(n) => self.lower_mutate(n), Node::Borrow(n) => self.lower_borrow(n), Node::Peek(n) => self.lower_peek(n), _ => { // For unhandled nodes, we can either ignore or error. // Given the PR, maybe we should error on things we don't support yet in lowering. self.error("E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", node), node.span()); Err(()) } }; self.current_span = old_span; res } fn lower_alloc(&mut self, n: &AllocNode) -> 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: &Node) -> Result<(TypeId, u32), ()> { match node { Node::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)) } Node::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => { let size = if ta.args.len() > 1 { if let Node::IntLit(il) = &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: {:?}", node), node.span()); 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, n: &PeekNode) -> 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, n: &BorrowNode) -> 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, n: &MutateNode) -> 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, n: &BlockNode) -> Result<(), ()> { self.local_vars.push(HashMap::new()); for stmt in &n.stmts { self.lower_node(stmt)?; } if let Some(tail) = &n.tail { self.lower_node(tail)?; } self.local_vars.pop(); Ok(()) } fn lower_let_stmt(&mut self, n: &LetStmtNode) -> 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 Node::Call(call) = &*n.init { if let Node::MemberAccess(ma) = &*call.callee { if let Node::Ident(obj) = &*ma.object { let obj_name = self.interner.resolve(obj.name); let member_name = self.interner.resolve(ma.member); 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 { 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, n: &ReturnStmtNode) -> Result<(), ()> { if let Some(expr) = &n.expr { self.lower_node(expr)?; } self.terminate(Terminator::Return); Ok(()) } fn lower_ident(&mut self, n: &IdentNode) -> 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 { // 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), n.span, ); Err(()) } else { self.error( "E_RESOLVE_UNDEFINED", format!("Undefined identifier '{}'", name_str), n.span, ); Err(()) } } } fn lower_member_access(&mut self, n: &MemberAccessNode) -> Result<(), ()> { if let Node::Ident(id) = &*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(n) { let slots = self.get_type_slots(&ty); for i in 0..slots { self.emit(InstrKind::GetLocal(slot + i)); } return Ok(()); } Ok(()) } fn resolve_member_access(&self, n: &MemberAccessNode) -> Option<(u32, Type)> { match &*n.object { Node::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 } } Node::MemberAccess(inner) => { let member_str = self.interner.resolve(n.member); let (base_slot, ty) = self.resolve_member_access(inner)?; 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 } } fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 { match struct_name { "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 { match struct_name { "Pad" => Type::Struct("ButtonState".to_string()), "ButtonState" => match field_name { "hold_frames" => Type::Bounded, _ => Type::Bool, }, "Touch" => match field_name { "f" => Type::Struct("ButtonState".to_string()), _ => Type::Int, }, _ => Type::Int, } } fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> { match &*n.callee { Node::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)) .cloned(); 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)) .cloned(); 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(); self.emit(InstrKind::ImportCall( dep_alias, module_path, self.interner.resolve(sym.name).to_string(), n.args.len() as u32, )); return Ok(()); } } } self.error( "E_LOWER_UNSUPPORTED", format!( "Calling symbol '{}' with origin {:?} is not supported yet in v0", callee_name, sym.origin ), id_node.span, ); Err(()) } else { // Check for special built-in functions match callee_name.as_str() { "some" | "ok" | "err" => { return Ok(()); } _ => {} } self.error( "E_RESOLVE_UNDEFINED", format!("Undefined function '{}'", callee_name), id_node.span, ); Err(()) } } Node::MemberAccess(ma) => { // Check if it's a constructor alias: TypeName.Alias(...) let ctor = if let Node::Ident(obj_id) = &*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)) .cloned() } 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 Node::Ident(obj_id) = &*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 Node::Ident(obj_id) = &*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 { if sym.kind == SymbolKind::Contract && sym.is_host { // Lower arguments first to avoid borrowing conflicts for arg in &n.args { self.lower_node(arg)?; } if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { let id = method.id; let return_slots = if matches!(method.return_type, PbsType::Void) { 0 } else { 1 }; self.emit(InstrKind::HostCall(id, return_slots)); return Ok(()); } } } } } // Check for .raw() if member_name == "raw" { self.lower_node(&ma.object)?; return Ok(()); } // Check for Color.rgb if member_name == "rgb" { if let Node::Ident(obj_id) = &*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 Node::IntLit(lit) = arg { literals.push(Some(lit.value)); } 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(), n.span); return Err(()); } } } } } for arg in &n.args { self.lower_node(arg)?; } if let Node::Ident(obj_id) = &*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), ma.span, ); return Err(()); } } } self.error("E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), ma.span); Err(()) } _ => { for arg in &n.args { self.lower_node(arg)?; } self.error("E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), n.callee.span()); Err(()) } } } fn lower_constructor_call(&mut self, ctor: &ConstructorDeclNode, args: &[Node]) -> Result<(), ()> { let mut param_map = HashMap::new(); for (i, param) in ctor.params.iter().enumerate() { if i < args.len() { param_map.insert(self.interner.resolve(param.name).to_string(), args[i].clone()); } } for init in &ctor.initializers { let substituted = self.substitute_node(init, ¶m_map); self.lower_node(&substituted)?; } Ok(()) } fn substitute_node(&self, node: &Node, param_map: &HashMap) -> Node { match node { Node::Ident(id) => { if let Some(arg) = param_map.get(self.interner.resolve(id.name)) { arg.clone() } else { node.clone() } } Node::Binary(bin) => { Node::Binary(BinaryNode { span: bin.span, left: Box::new(self.substitute_node(&bin.left, param_map)), right: Box::new(self.substitute_node(&bin.right, param_map)), op: bin.op.clone(), }) } Node::Unary(un) => { Node::Unary(UnaryNode { span: un.span, op: un.op.clone(), expr: Box::new(self.substitute_node(&un.expr, param_map)), }) } Node::Call(call) => { Node::Call(CallNode { span: call.span, callee: Box::new(self.substitute_node(&call.callee, param_map)), args: call.args.iter().map(|a| self.substitute_node(a, param_map)).collect(), }) } _ => node.clone() } } 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, n: &BinaryNode) -> 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), n.span); return Err(()); } } Ok(()) } fn lower_unary(&mut self, n: &UnaryNode) -> 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), n.span); return Err(()); } } Ok(()) } fn lower_if_expr(&mut self, n: &IfExprNode) -> 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_type_node(&mut self, node: &Node) -> Type { match node { Node::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, _ => Type::Struct(self.interner.resolve(n.name).to_string()), }, Node::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 Node::IntLit(il) = &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)); } } 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::frontends::pbs::collector::SymbolCollector; use crate::frontends::pbs::parser::Parser; use crate::frontends::pbs::symbols::ModuleSymbols; use crate::ir_core; use prometeu_analysis::NameInterner; #[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 mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "test").expect("Lowering failed"); // 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, ir_core::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, ir_core::InstrKind::Add))); } #[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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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_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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, ir_core::InstrKind::Alloc { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::BeginMutate { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).unwrap(); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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 Log host {} fn main() { Gfx.clear(0); Log.write(2, \"Hello\"); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, ir_core::InstrKind::HostCall(0x1010, 0)))); // Log.write -> 0x5001 assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(0x5001, 0)))); } #[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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string()))); } #[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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_LOWER_UNSUPPORTED".to_string()))); } #[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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); } #[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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let program = lowerer.lower_file(&ast, "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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); 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, 0, &mut interner); let ast = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported, &interner); let result = lowerer.lower_file(&ast, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'"))); } }