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> { arena: &'a AstArena, 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( arena: &'a AstArena, 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 { arena, 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, root: NodeId, module_name: &str) -> Result { let file = match self.arena.kind(root) { NodeKind::File(file) => file, _ => { return Err(DiagnosticBundle::error( "Expected File node as root".to_string(), None, )) } }; // Pre-scan for function declarations to assign IDs for decl in &file.decls { match self.arena.kind(*decl) { NodeKind::FnDecl(n) => { let id = FunctionId(self.next_func_id); self.next_func_id += 1; self.function_ids .insert(self.interner.resolve(n.name).to_string(), id); } NodeKind::TypeDecl(n) => { let id = TypeId(self.next_type_id); self.next_type_id += 1; self.type_ids .insert(self.interner.resolve(n.name).to_string(), id); } _ => {} } } // Second pre-scan: calculate struct slots (recursive) let mut struct_nodes = HashMap::new(); for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { if n.type_kind == "struct" { struct_nodes.insert(self.interner.resolve(n.name).to_string(), *decl); } } } let mut changed = true; while changed { changed = false; for (name, node_id) in &struct_nodes { if !self.struct_slots.contains_key(name) { let mut slots = 0; let mut all_known = true; let node = match self.arena.kind(*node_id) { NodeKind::TypeDecl(node) => node, _ => continue, }; for param in &node.params { let member_ty = self.lower_type_node(param.ty); match &member_ty { Type::Struct(sname) => { if let Some(s_slots) = self.get_builtin_struct_slots(sname) { slots += s_slots; } else if let Some(s_slots) = self.struct_slots.get(sname) { slots += s_slots; } else { all_known = false; break; } } _ => slots += self.get_type_slots(&member_ty), } } if all_known { self.struct_slots.insert(name.clone(), slots); changed = true; } } } } for decl in &file.decls { if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) { let type_name = self.interner.resolve(n.name).to_string(); let mut constants = HashMap::new(); for c in &n.constants { if let NodeKind::ConstantDecl(constant) = self.arena.kind(*c) { constants.insert( self.interner.resolve(constant.name).to_string(), constant.value, ); } } self.type_constants.insert(type_name.clone(), constants); let mut ctors = HashMap::new(); // Default constructor: TypeName(...) if n.type_kind == "struct" { if let Some(default_ctor) = n.constructors.first() { if let NodeKind::ConstructorDecl(_) = self.arena.kind(*default_ctor) { ctors.insert(type_name.clone(), *default_ctor); } } } for ctor in &n.constructors { if let NodeKind::ConstructorDecl(ctor_node) = self.arena.kind(*ctor) { ctors.insert(self.interner.resolve(ctor_node.name).to_string(), *ctor); } } self.struct_constructors.insert(type_name, ctors); } } let mut module = Module { name: module_name.to_string(), functions: Vec::new(), }; for decl in &file.decls { if let NodeKind::FnDecl(_) = self.arena.kind(*decl) { let func = self.lower_function(*decl).map_err(|_| DiagnosticBundle { diagnostics: self.diagnostics.clone(), })?; module.functions.push(func); } } self.program.modules.push(module); Ok(self.program) } fn lower_function(&mut self, node: NodeId) -> Result { let n = match self.arena.kind(node) { NodeKind::FnDecl(n) => n, _ => return Err(()), }; let func_name = self.interner.resolve(n.name).to_string(); let func_id = *self.function_ids.get(&func_name).unwrap(); self.next_block_id = 0; self.local_vars = vec![HashMap::new()]; self.max_slots_used = 0; let mut params = Vec::new(); let mut local_types = HashMap::new(); let mut param_slots = 0u32; for param in &n.params { let ty = self.lower_type_node(param.ty); let slots = self.get_type_slots(&ty); params.push(Param { name: self.interner.resolve(param.name).to_string(), ty: ty.clone(), }); self.local_vars[0].insert( self.interner.resolve(param.name).to_string(), LocalInfo { slot: param_slots, ty: ty.clone(), }, ); for i in 0..slots { local_types.insert(param_slots + i, ty.clone()); } param_slots += slots; } self.max_slots_used = param_slots; let ret_ty = if let Some(ret) = n.ret { self.lower_type_node(ret) } else { Type::Void }; let return_slots = self.get_type_slots(&ret_ty); 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: NodeId) -> Result<(), ()> { let old_span = self.current_span; self.current_span = Some(self.arena.span(node)); let res = match self.arena.kind(node) { NodeKind::Block(n) => self.lower_block(node, n), NodeKind::LetStmt(n) => self.lower_let_stmt(node, n), NodeKind::ExprStmt(n) => self.lower_node(n.expr), NodeKind::ReturnStmt(n) => self.lower_return_stmt(node, n), NodeKind::IntLit(n) => { let id = self.program.const_pool.add_int(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::FloatLit(n) => { let id = self.program.const_pool.add_float(n.value); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::StringLit(n) => { let id = self.program.const_pool.add_string(n.value.clone()); self.emit(InstrKind::PushConst(id)); Ok(()) } NodeKind::BoundedLit(n) => { self.emit(InstrKind::PushBounded(n.value)); Ok(()) } NodeKind::Ident(n) => self.lower_ident(node, n), NodeKind::MemberAccess(n) => self.lower_member_access(node, n), NodeKind::Call(n) => self.lower_call(node, n), NodeKind::Binary(n) => self.lower_binary(node, n), NodeKind::Unary(n) => self.lower_unary(node, n), NodeKind::IfExpr(n) => self.lower_if_expr(node, n), NodeKind::WhenExpr(n) => self.lower_when_expr(node, n), NodeKind::Alloc(n) => self.lower_alloc(node, n), NodeKind::Mutate(n) => self.lower_mutate(node, n), NodeKind::Borrow(n) => self.lower_borrow(node, n), NodeKind::Peek(n) => self.lower_peek(node, n), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", self.arena.kind(node)), self.arena.span(node), ); Err(()) } }; self.current_span = old_span; res } fn lower_alloc(&mut self, _node: NodeId, n: &AllocNodeArena) -> Result<(), ()> { let (ty_id, slots) = self.get_type_id_and_slots(n.ty)?; self.emit(InstrKind::Alloc { ty: ty_id, slots }); Ok(()) } fn get_type_id_and_slots(&mut self, node: NodeId) -> Result<(TypeId, u32), ()> { match self.arena.kind(node) { NodeKind::TypeName(n) => { let name = self.interner.resolve(n.name); let slots = self.struct_slots.get(name).cloned().unwrap_or(1); let id = self.get_or_create_type_id(name); Ok((id, slots)) } NodeKind::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => { let size = if ta.args.len() > 1 { if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { il.value as u32 } else { 1 } } else { 1 }; let elem_ty = self.lower_type_node(ta.args[0]); let name = format!("array<{}>[{}]", elem_ty, size); let id = self.get_or_create_type_id(&name); Ok((id, size)) } _ => { self.error( "E_RESOLVE_UNDEFINED", format!("Unknown type in allocation: {:?}", self.arena.kind(node)), self.arena.span(node), ); Err(()) } } } fn get_or_create_type_id(&mut self, name: &str) -> TypeId { if let Some(id) = self.type_ids.get(name) { *id } else { let id = TypeId(self.next_type_id); self.next_type_id += 1; self.type_ids.insert(name.to_string(), id); id } } fn lower_peek(&mut self, _node: NodeId, n: &PeekNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndPeek); self.local_vars.pop(); Ok(()) } fn lower_borrow(&mut self, _node: NodeId, n: &BorrowNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndBorrow); self.local_vars.pop(); Ok(()) } fn lower_mutate(&mut self, _node: NodeId, n: &MutateNodeArena) -> Result<(), ()> { // 1. Evaluate target (gate) self.lower_node(n.target)?; // 2. Preserve gate identity let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); self.emit(InstrKind::SetLocal(gate_slot)); // 3. Begin Operation self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); // 4. Bind view to local self.local_vars.push(HashMap::new()); let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int); self.emit(InstrKind::SetLocal(view_slot)); // 5. Body self.lower_node(n.body)?; // 6. End Operation self.emit(InstrKind::EndMutate); self.local_vars.pop(); Ok(()) } fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> { self.local_vars.push(HashMap::new()); for stmt in &n.stmts { 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, _node: NodeId, n: &LetStmtNodeArena) -> Result<(), ()> { self.lower_node(n.init)?; let ty = if let Some(ty_node) = n.ty { self.lower_type_node(ty_node) } else { // Very basic inference for host calls if let NodeKind::Call(call) = self.arena.kind(n.init) { if let NodeKind::MemberAccess(ma) = self.arena.kind(call.callee) { if let NodeKind::Ident(obj) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj.name); let member_name = self.interner.resolve(ma.member); 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, _node: NodeId, n: &ReturnStmtNodeArena) -> Result<(), ()> { if let Some(expr) = n.expr { self.lower_node(expr)?; } self.terminate(Terminator::Return); Ok(()) } fn lower_ident(&mut self, node: NodeId, n: &IdentNodeArena) -> Result<(), ()> { let name_str = self.interner.resolve(n.name); if let Some(info) = self.find_local(name_str) { let slots = self.get_type_slots(&info.ty); for i in 0..slots { self.emit(InstrKind::GetLocal(info.slot + i)); } Ok(()) } else { // Check for special identifiers match name_str { "true" => { let id = self.program.const_pool.add_int(1); self.emit(InstrKind::PushConst(id)); return Ok(()); } "false" => { let id = self.program.const_pool.add_int(0); self.emit(InstrKind::PushConst(id)); return Ok(()); } "none" => { // For now, treat none as 0. This should be refined when optional is fully implemented. let id = self.program.const_pool.add_int(0); self.emit(InstrKind::PushConst(id)); return Ok(()); } _ => {} } // Check if it's a function (for first-class functions if supported) if let Some(_id) = self.function_ids.get(name_str) { // Push function reference? Not in v0. self.error( "E_LOWER_UNSUPPORTED", format!("First-class function reference '{}' not supported", name_str), self.arena.span(node), ); Err(()) } else { self.error( "E_RESOLVE_UNDEFINED", format!("Undefined identifier '{}'", name_str), self.arena.span(node), ); Err(()) } } } fn lower_member_access(&mut self, node: NodeId, n: &MemberAccessNodeArena) -> Result<(), ()> { if let NodeKind::Ident(id) = self.arena.kind(n.object) { let type_name = self.interner.resolve(id.name); let member_name = self.interner.resolve(n.member); if let Some(constants) = self.type_constants.get(type_name).cloned() { if let Some(const_val) = constants.get(member_name) { let old_ctx = self.current_type_context.replace(type_name.to_string()); let res = self.lower_node(*const_val); self.current_type_context = old_ctx; return res; } } if type_name == "Color" { let val = match member_name { "BLACK" => 0x0000, "WHITE" => 0xFFFF, "RED" => 0xF800, "GREEN" => 0x07E0, "BLUE" => 0x001F, "MAGENTA" => 0xF81F, "TRANSPARENT" => 0x0000, "COLOR_KEY" => 0x0000, _ => { // Check if it's a method call like Color.rgb, handled in lower_call return Ok(()); } }; self.emit(InstrKind::PushBounded(val)); return Ok(()); } } if let Some((slot, ty)) = self.resolve_member_access(node) { let slots = self.get_type_slots(&ty); for i in 0..slots { self.emit(InstrKind::GetLocal(slot + i)); } return Ok(()); } Ok(()) } fn resolve_member_access(&self, node: NodeId) -> Option<(u32, Type)> { match self.arena.kind(node) { NodeKind::MemberAccess(n) => match self.arena.kind(n.object) { NodeKind::Ident(id) => { let name_str = self.interner.resolve(id.name); let member_str = self.interner.resolve(n.member); let info = self.find_local(name_str)?; if let Type::Struct(sname) = &info.ty { let offset = self.get_field_offset(sname, member_str); let ty = self.get_field_type(sname, member_str); Some((info.slot + offset, ty)) } else { None } } NodeKind::MemberAccess(_) => { let member_str = self.interner.resolve(n.member); let (base_slot, ty) = self.resolve_member_access(n.object)?; if let Type::Struct(sname) = &ty { let offset = self.get_field_offset(sname, member_str); let final_ty = self.get_field_type(sname, member_str); Some((base_slot + offset, final_ty)) } else { None } } _ => None, }, _ => None, } } fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 { 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, node: NodeId, n: &CallNodeArena) -> Result<(), ()> { match self.arena.kind(n.callee) { NodeKind::Ident(id_node) => { let callee_name = self.interner.resolve(id_node.name).to_string(); // 1. Check for constructor call: TypeName(...) let ctor = self .struct_constructors .get(&callee_name) .and_then(|ctors| ctors.get(&callee_name)) .copied(); if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } if let Some(ctx) = &self.current_type_context { let ctor = self .struct_constructors .get(ctx) .and_then(|ctors| ctors.get(&callee_name)) .copied(); if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } } for arg in &n.args { self.lower_node(*arg)?; } if let Some(func_id) = self.function_ids.get(&callee_name) { self.emit(InstrKind::Call(*func_id, n.args.len() as u32)); Ok(()) } else if let Some(sym) = self.imported_symbols.value_symbols.get(id_node.name) { if let Some(origin) = &sym.origin { if origin.starts_with('@') { // Format: @dep_alias:module_path let parts: Vec<&str> = origin[1..].splitn(2, ':').collect(); if parts.len() == 2 { let dep_alias = parts[0].to_string(); let module_path = parts[1].to_string(); 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 ), self.arena.span(n.callee), ); Err(()) } else { // Try default constructor for struct calls like TypeName(...) if let Some(ctors) = self.struct_constructors.get(&callee_name) { if let Some(ctor) = ctors.get(&callee_name) { return self.lower_constructor_call(*ctor, &n.args); } } let type_sym = self .module_symbols .type_symbols .get(id_node.name) .or_else(|| self.imported_symbols.type_symbols.get(id_node.name)); if let Some(sym) = type_sym { if sym.kind == SymbolKind::Struct { // TODO: handle implicit struct constructor (lower args as field values) return Ok(()); } } // Check for special built-in functions match callee_name.as_str() { "some" | "ok" | "err" => { for arg in &n.args { self.lower_node(*arg)?; } return Ok(()); } _ => {} } self.error( "E_RESOLVE_UNDEFINED", format!("Undefined function '{}'", callee_name), self.arena.span(n.callee), ); Err(()) } } NodeKind::MemberAccess(ma) => { // Check if it's a constructor alias: TypeName.Alias(...) let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let member_name = self.interner.resolve(ma.member); self.struct_constructors .get(obj_name) .and_then(|ctors| ctors.get(member_name)) .copied() } else { None }; if let Some(ctor) = ctor { return self.lower_constructor_call(ctor, &n.args); } // Check for Pad.any() let member_name = self.interner.resolve(ma.member); if member_name == "any" { if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); if let Some(info) = self.find_local(obj_name) { if let Type::Struct(sname) = &info.ty { if sname == "Pad" { self.lower_pad_any(info.slot); return Ok(()); } } } } } // Host contract static calls: Contract.method(...) if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let is_local = self.find_local(obj_name).is_some(); if !is_local { // Check type symbol (current or imported) for a host contract let sym_opt = self .module_symbols .type_symbols .get(obj_id.name) .or_else(|| self.imported_symbols.type_symbols.get(obj_id.name)); if let Some(sym) = sym_opt { 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 NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { if self.interner.resolve(obj_id.name) == "Color" { if n.args.len() == 3 { // Try to get literal values for r, g, b let mut literals = Vec::new(); for arg in &n.args { if let NodeKind::IntLit(lit) = self.arena.kind(*arg) { literals.push(Some(lit.value)); } else if let NodeKind::BoundedLit(lit) = self.arena.kind(*arg) { literals.push(Some(lit.value as i64)); } else { literals.push(None); } } if let (Some(r), Some(g), Some(b)) = (literals[0], literals[1], literals[2]) { let r5 = (r & 0xFF) >> 3; let g6 = (g & 0xFF) >> 2; let b5 = (b & 0xFF) >> 3; let rgb565 = (r5 << 11) | (g6 << 5) | b5; self.emit(InstrKind::PushBounded(rgb565 as u32)); return Ok(()); } else { self.error( "E_LOWER_UNSUPPORTED", "Color.rgb only supports literal arguments in this version" .to_string(), self.arena.span(node), ); return Err(()); } } } } } for arg in &n.args { self.lower_node(*arg)?; } if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) { let obj_name = self.interner.resolve(obj_id.name); let is_host_contract = self .module_symbols .type_symbols .get(obj_id.name) .map(|sym| sym.kind == SymbolKind::Contract && sym.is_host) .unwrap_or(false); let is_shadowed = self.find_local(obj_name).is_some(); if is_host_contract && !is_shadowed { if let Some(method) = self.contract_registry.get_method(obj_name, member_name) { let ir_ty = self.convert_pbs_type(&method.return_type); let return_slots = self.get_type_slots(&ir_ty); self.emit(InstrKind::HostCall(method.id, return_slots)); return Ok(()); } else { self.error( "E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_name, member_name), self.arena.span(n.callee), ); return Err(()); } } } self.error( "E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), self.arena.span(n.callee), ); Err(()) } _ => { for arg in &n.args { self.lower_node(*arg)?; } self.error( "E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), self.arena.span(n.callee), ); Err(()) } } } fn lower_constructor_call(&mut self, ctor: NodeId, args: &[NodeId]) -> Result<(), ()> { let ctor_id = ctor; let ctor = match self.arena.kind(ctor) { NodeKind::ConstructorDecl(ctor) => ctor, _ => return Err(()), }; if args.len() != ctor.params.len() { self.error( "E_TYPE_MISMATCH", format!( "Expected {} arguments, found {}", ctor.params.len(), args.len() ), self.arena.span(ctor_id), ); return Err(()); } self.local_vars.push(HashMap::new()); let mut param_slots = Vec::new(); for param in &ctor.params { let ty = self.lower_type_node(param.ty); let slot = self.add_local_to_scope(self.interner.resolve(param.name).to_string(), ty.clone()); param_slots.push((slot, ty)); } for (index, arg) in args.iter().enumerate() { if let Some((slot, ty)) = param_slots.get(index) { self.lower_node(*arg)?; let slots = self.get_type_slots(ty); for i in (0..slots).rev() { self.emit(InstrKind::SetLocal(slot + i)); } } } for init in &ctor.initializers { self.lower_node(*init)?; } self.local_vars.pop(); Ok(()) } fn lower_pad_any(&mut self, base_slot: u32) { for i in 0..12 { let btn_base = base_slot + (i * 4); self.emit(InstrKind::GetLocal(btn_base)); // pressed self.emit(InstrKind::GetLocal(btn_base + 1)); // released self.emit(InstrKind::Or); self.emit(InstrKind::GetLocal(btn_base + 2)); // down self.emit(InstrKind::Or); if i > 0 { self.emit(InstrKind::Or); } } } fn lower_binary(&mut self, node: NodeId, n: &BinaryNodeArena) -> Result<(), ()> { self.lower_node(n.left)?; self.lower_node(n.right)?; match n.op.as_str() { "+" => self.emit(InstrKind::Add), "-" => self.emit(InstrKind::Sub), "*" => self.emit(InstrKind::Mul), "/" => self.emit(InstrKind::Div), "==" => self.emit(InstrKind::Eq), "!=" => self.emit(InstrKind::Neq), "<" => self.emit(InstrKind::Lt), "<=" => self.emit(InstrKind::Lte), ">" => self.emit(InstrKind::Gt), ">=" => self.emit(InstrKind::Gte), "&&" => self.emit(InstrKind::And), "||" => self.emit(InstrKind::Or), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), self.arena.span(node), ); return Err(()); } } Ok(()) } fn lower_unary(&mut self, node: NodeId, n: &UnaryNodeArena) -> Result<(), ()> { self.lower_node(n.expr)?; match n.op.as_str() { "-" => self.emit(InstrKind::Neg), "!" => self.emit(InstrKind::Not), _ => { self.error( "E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), self.arena.span(node), ); return Err(()); } } Ok(()) } fn lower_if_expr(&mut self, _node: NodeId, n: &IfExprNodeArena) -> Result<(), ()> { let then_id = self.reserve_block_id(); let else_id = self.reserve_block_id(); let merge_id = self.reserve_block_id(); self.lower_node(n.cond)?; self.terminate(Terminator::JumpIfFalse { target: else_id, else_target: then_id, }); // Then block self.start_block_with_id(then_id); self.lower_node(n.then_block)?; self.terminate(Terminator::Jump(merge_id)); // Else block self.start_block_with_id(else_id); if let Some(else_block) = n.else_block { self.lower_node(else_block)?; } self.terminate(Terminator::Jump(merge_id)); // Merge block self.start_block_with_id(merge_id); Ok(()) } fn lower_when_expr(&mut self, node: NodeId, n: &WhenExprNodeArena) -> Result<(), ()> { if n.arms.is_empty() { return Ok(()); } let merge_id = self.reserve_block_id(); for (idx, arm_id) in n.arms.iter().enumerate() { let arm = match self.arena.kind(*arm_id) { NodeKind::WhenArm(arm) => arm, _ => { self.error( "E_LOWER_UNSUPPORTED", "Expected when arm".to_string(), self.arena.span(node), ); return Err(()); } }; let body_id = self.reserve_block_id(); let next_cond_id = if idx + 1 < n.arms.len() { self.reserve_block_id() } else { merge_id }; self.lower_node(arm.cond)?; self.terminate(Terminator::JumpIfFalse { target: next_cond_id, else_target: body_id, }); self.start_block_with_id(body_id); self.lower_node(arm.body)?; self.terminate(Terminator::Jump(merge_id)); if idx + 1 < n.arms.len() { self.start_block_with_id(next_cond_id); } } self.start_block_with_id(merge_id); Ok(()) } fn lower_type_node(&mut self, node: NodeId) -> Type { match self.arena.kind(node) { NodeKind::TypeName(n) => match self.interner.resolve(n.name) { "int" => Type::Int, "bounded" => Type::Bounded, "float" => Type::Float, "bool" => Type::Bool, "string" => Type::String, "void" => Type::Void, _ => { if let Some(sym) = self .module_symbols .type_symbols .get(n.name) .or_else(|| self.imported_symbols.type_symbols.get(n.name)) { let name = self.interner.resolve(n.name).to_string(); match sym.kind { SymbolKind::Struct => Type::Struct(name), SymbolKind::Service => Type::Service(name), SymbolKind::Contract => Type::Contract(name), SymbolKind::ErrorType => Type::ErrorType(name), _ => Type::Struct(name), } } else { Type::Struct(self.interner.resolve(n.name).to_string()) } } }, NodeKind::TypeApp(ta) => { let base_name = self.interner.resolve(ta.base); if base_name == "array" { let elem_ty = self.lower_type_node(ta.args[0]); let size = if ta.args.len() > 1 { if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) { il.value as u32 } else { 0 } } else { 0 }; Type::Array(Box::new(elem_ty), size) } else if base_name == "optional" { Type::Optional(Box::new(self.lower_type_node(ta.args[0]))) } else if base_name == "result" { Type::Result( Box::new(self.lower_type_node(ta.args[0])), Box::new(self.lower_type_node(ta.args[1])) ) } else { Type::Struct(format!("{}<{}>", base_name, ta.args.len())) } } _ => Type::Void, } } fn start_block(&mut self) { let id = self.reserve_block_id(); self.start_block_with_id(id); } fn start_block_with_id(&mut self, id: u32) { if let Some(block) = self.current_block.take() { if let Some(func) = &mut self.current_function { func.blocks.push(block); } } self.current_block = Some(Block { id, instrs: Vec::new(), terminator: Terminator::Return, // Default, will be overwritten }); } fn reserve_block_id(&mut self) -> u32 { let id = self.next_block_id; self.next_block_id += 1; id } fn emit(&mut self, kind: InstrKind) { if let Some(block) = &mut self.current_block { block.instrs.push(Instr::new(kind, self.current_span)); } } 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; fn lower_program(code: &str) -> ir_core::Program { let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); lowerer.lower_file(parsed.root, "test").expect("Lowering failed") } #[test] fn test_basic_lowering() { let code = " fn add(a: int, b: int): int { return a + b; } fn main() { let x = add(10, 20); } "; let program = lower_program(code); // Verify program structure assert_eq!(program.modules.len(), 1); let module = &program.modules[0]; assert_eq!(module.functions.len(), 2); let add_func = module.functions.iter().find(|f| f.name == "add").unwrap(); assert_eq!(add_func.params.len(), 2); assert_eq!(add_func.return_type, 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_binary_ops_lowering() { let code = " fn main() { let a = 1 + 2; let b = 3 - 1; let c = 2 * 3; let d = 8 / 2; let e = 1 == 1; let f = 1 < 2; let g = 2 > 1; let h = true && false; let i = true || false; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Add))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Sub))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Mul))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Div))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Eq))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Lt))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Gt))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::And))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Or))); } #[test] fn test_unary_ops_lowering() { let code = " fn main() { let a = -1; let b = !true; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Neg))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Not))); } #[test] fn test_control_flow_lowering() { let code = " fn max(a: int, b: int): int { if (a > b) { return a; } else { return b; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let max_func = &program.modules[0].functions[0]; // Should have multiple blocks for if-else assert!(max_func.blocks.len() >= 3); } #[test] fn test_if_expr_lowering() { let code = " fn main(a: int, b: int) { if (a > b) { let x = a; } else { let y = b; } } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); assert!(terminators.iter().any(|t| matches!(t, ir_core::Terminator::JumpIfFalse { .. }))); assert!(terminators.iter().any(|t| matches!(t, ir_core::Terminator::Jump(_)))); assert!(main_func.blocks.len() >= 3); } #[test] fn test_when_expr_lowering() { let code = " fn main(x: int) { when { x == 0 -> { return; }, x == 1 -> { return; } }; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect(); assert!(terminators.iter().any(|t| matches!(t, ir_core::Terminator::JumpIfFalse { .. }))); assert!(main_func.blocks.len() >= 5); } #[test] fn test_lower_type_node() { let code = " service MyService { fn ping(): void; } declare contract MyContract host {} declare error MyError {} declare struct Point { x: int } fn main( s: MyService, c: MyContract, e: MyError, p: Point, o: optional, r: result, a: int[3] ) {} "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let params: Vec<_> = main_func.params.iter().map(|p| p.ty.clone()).collect(); assert_eq!(params[0], ir_core::Type::Service("MyService".to_string())); assert_eq!(params[1], ir_core::Type::Contract("MyContract".to_string())); assert_eq!(params[2], ir_core::Type::ErrorType("MyError".to_string())); assert_eq!(params[3], ir_core::Type::Struct("Point".to_string())); assert_eq!( params[4], ir_core::Type::Optional(Box::new(ir_core::Type::Int)) ); assert_eq!( params[5], ir_core::Type::Result( Box::new(ir_core::Type::Int), Box::new(ir_core::Type::String) ) ); assert_eq!( params[6], ir_core::Type::Array(Box::new(ir_core::Type::Int), 3) ); } #[test] fn test_call_lowering() { let code = " fn add(a: int, b: int): int { return a + b; } fn main() { let x = add(1, 2); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::Call(_, 2)))); } #[test] fn test_host_call_lowering() { let code = " declare contract Gfx host {} fn main() { Gfx.clear(Color.WHITE); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(_, _)))); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::PushBounded(_)))); } #[test] fn test_member_access_lowering() { let code = " declare contract Input host {} fn main() { let p: Pad = Input.pad(); let b: ButtonState = p.a; let d: bool = p.a.down; let c: Color = Color.WHITE; } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::GetLocal(16)))); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::GetLocal(18)))); assert!(instrs .iter() .any(|i| matches!(i.kind, ir_core::InstrKind::PushBounded(_)))); } #[test] fn test_constructor_call_lowering() { let code = " declare struct Vec2(x: int, y: int) [ (x: int, y: int): (x, y) as default { } (s: int): (s, s) as square { } ] fn main() { let a = Vec2(1, 2); let b = Vec2.square(3); } "; let program = lower_program(code); let module = &program.modules[0]; let main_func = module.functions.iter().find(|f| f.name == "main").unwrap(); let instrs: Vec<_> = main_func .blocks .iter() .flat_map(|b| b.instrs.iter()) .collect(); let push_consts = instrs .iter() .filter(|i| matches!(i.kind, ir_core::InstrKind::PushConst(_))) .count(); assert_eq!(push_consts, 3); assert!(!instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Call(_, _)))); assert!(!instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(_, _)))); } #[test] fn test_hip_lowering() { let code = " fn test_hip() { let g = alloc int; mutate g as x { let y = x + 1; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); assert!(instrs.iter().any(|i| matches!(i.kind, 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .unwrap(); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let json = serde_json::to_string_pretty(&program).unwrap(); // Assertions for PR-20 HIP Semantics: // 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0)) // 2. BeginMutate uses that local (BeginMutate { gate: 1 }) // 3. EndMutate exists // 4. No ReadGate/WriteGate (they were removed from Instr) assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local"); assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate"); assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local"); assert!(json.contains("\"EndMutate\""), "Should have EndMutate"); assert!(!json.contains("ReadGate"), "ReadGate should be gone"); assert!(!json.contains("WriteGate"), "WriteGate should be gone"); } #[test] fn test_hip_semantics_distinction() { let code = " fn test_hip(g: int) { peek g as p { let x = p; } borrow g as b { let y = b; } mutate g as m { let z = m; } } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); // Assert distinct Core IR instruction sequences assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndPeek))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginBorrow { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndBorrow))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate))); } #[test] fn test_host_contract_call_lowering() { let code = " declare contract Gfx host {} declare contract 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); // Gfx.clear -> 0x1010 assert!(instrs.iter().any(|i| matches!(i.kind, 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); assert_eq!(*alloc.1, 3, "Vec3 should have 3 slots"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } #[test] fn test_alloc_array_slots() { let code = " fn main() { let a = alloc array[10b]; } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); assert_eq!(*alloc.1, 10, "array[10b] should have 10 slots"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } #[test] fn test_alloc_primitive_slots() { let code = " fn main() { let x = alloc int; } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed"); let func = &program.modules[0].functions[0]; let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let alloc = instrs.iter().find_map(|i| { if let InstrKind::Alloc { ty, slots } = &i.kind { Some((ty, slots)) } else { None } }).expect("Should have Alloc instruction"); assert_eq!(*alloc.1, 1, "Primitive int should have 1 slot"); assert!(alloc.0.0 > 0, "Should have a valid TypeId"); } #[test] fn test_missing_function_error() { let code = " fn main() { missing_func(); } "; let mut interner = NameInterner::new(); let mut parser = Parser::new(code, 0, &mut interner); let parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == 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 parsed = parser.parse_file().expect("Failed to parse"); let mut collector = SymbolCollector::new(&interner); let (type_symbols, value_symbols) = collector .collect(&parsed.arena, parsed.root) .expect("Failed to collect symbols"); let module_symbols = ModuleSymbols { type_symbols, value_symbols }; let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &interner); let result = lowerer.lower_file(parsed.root, "test"); assert!(result.is_err()); let bundle = result.err().unwrap(); assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'"))); } }