use crate::frontends::ts::syscall_map; use crate::common::spans::Span as IRSpan; use crate::frontends::ts::ast_util; use crate::frontends::ts::input_map; use crate::ir; use crate::ir::instr::{InstrKind, Instruction as IRInstruction, Label as IRLabel}; use anyhow::{anyhow, Result}; use oxc_allocator::Vec as OXCVec; use oxc_ast::ast::*; use oxc_ast_visit::{walk, Visit}; use oxc_span::{GetSpan, Span}; use oxc_syntax::scope::ScopeFlags; use prometeu_core::model::Color; use std::collections::HashMap; /// Helper to count local variables and hoisted functions in a function body. struct LocalCounter { count: u32, } impl<'a> Visit<'a> for LocalCounter { fn visit_statement(&mut self, stmt: &Statement<'a>) { match stmt { Statement::FunctionDeclaration(f) => { self.visit_function(f, ScopeFlags::empty()); } _ => walk::walk_statement(self, stmt), } } fn visit_variable_declaration(&mut self, decl: &VariableDeclaration<'a>) { self.count += decl.declarations.len() as u32; walk::walk_variable_declaration(self, decl); } fn visit_function(&mut self, f: &Function<'a>, _flags: ScopeFlags) { if f.id.is_some() { self.count += 1; } // Stop recursion: nested functions have their own frames and locals. } } /// Metadata for a symbol (variable or function) in the symbol table. struct SymbolEntry { slot_index: u32, is_const: bool, is_initialized: bool, } /// The TS AST to Prometeu IR translator. pub struct ToIR { /// Name of the file being compiled (used for debug symbols). file_name: String, /// Full source code of the file (used for position lookup). source_text: String, /// ID of the file being compiled. current_file_id: usize, /// The stream of generated IR instructions. pub instructions: Vec, /// Scoped symbol table. Each element is a scope level containing a map of symbols. symbol_table: Vec>, /// Current depth of the scope (0 is global/function top-level). scope_depth: usize, /// Mapping of global variable names to their slots in the VM's global memory. globals: HashMap, /// Counter for the next available local variable ID. next_local: u32, /// Counter for the next available global variable ID. next_global: u32, /// Counter for generating unique labels (e.g., for 'if' or 'while' blocks). label_count: u32, } impl ToIR { /// Creates a new ToIR instance for a specific file. pub fn new(file_name: String, source_text: String) -> Self { Self { file_name, source_text, current_file_id: 0, instructions: Vec::new(), symbol_table: Vec::new(), scope_depth: 0, globals: HashMap::new(), next_local: 0, next_global: 0, label_count: 0, } } /// Enters a new scope level. fn enter_scope(&mut self) { self.symbol_table.push(HashMap::new()); self.scope_depth += 1; } /// Exits the current scope level. fn exit_scope(&mut self) { self.symbol_table.pop(); self.scope_depth -= 1; } /// Declares a new symbol in the current scope. fn declare_symbol(&mut self, name: String, is_const: bool, is_initialized: bool, span: Span) -> Result<&mut SymbolEntry> { let current_scope = self.symbol_table.last_mut().ok_or_else(|| anyhow!("No active scope"))?; if current_scope.contains_key(&name) { return Err(anyhow!("Variable '{}' already declared in this scope at {:?}", name, span)); } let slot_index = self.next_local; self.next_local += 1; let entry = SymbolEntry { slot_index, is_const, is_initialized, }; current_scope.insert(name.clone(), entry); Ok(self.symbol_table.last_mut().unwrap().get_mut(&name).unwrap()) } /// Marks a symbol as initialized. fn initialize_symbol(&mut self, name: &str) { for scope in self.symbol_table.iter_mut().rev() { if let Some(entry) = scope.get_mut(name) { entry.is_initialized = true; return; } } } /// Resolves a symbol name to its entry, searching from inner to outer scopes. fn resolve_symbol(&self, name: &str) -> Option<&SymbolEntry> { for scope in self.symbol_table.iter().rev() { if let Some(entry) = scope.get(name) { return Some(entry); } } None } /// Discovers all function declarations in a program, including nested ones. fn discover_functions<'a>( &self, file_id: usize, file: String, source: String, program: &'a Program<'a>, all_functions: &mut Vec<(usize, String, String, &'a Function<'a>)>, ) { struct Collector<'a, 'b> { file_id: usize, file: String, source: String, functions: &'b mut Vec<(usize, String, String, &'a Function<'a>)>, } impl<'a, 'b> Visit<'a> for Collector<'a, 'b> { fn visit_function(&mut self, f: &Function<'a>, flags: ScopeFlags) { // Safety: The program AST lives long enough as it's owned by the caller // of compile_programs and outlives the compilation process. let f_ref = unsafe { std::mem::transmute::<&Function<'a>, &'a Function<'a>>(f) }; self.functions.push((self.file_id, self.file.clone(), self.source.clone(), f_ref)); walk::walk_function(self, f, flags); } } let mut collector = Collector { file_id, file, source, functions: all_functions, }; collector.visit_program(program); } /// Compiles multiple programs (files) into a single Prometeu IR Module. pub fn compile_to_ir(&mut self, programs: Vec<(usize, String, String, &Program)>) -> Result { // --- FIRST PASS: Global Functions and Variables Collection --- let mut all_functions_ast = Vec::new(); for (file_id, file, source, program) in &programs { for item in &program.body { match item { Statement::ExportNamedDeclaration(decl) => { if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration { self.export_global_variable_declarations(&var); } } Statement::VariableDeclaration(var) => { self.export_global_variable_declarations(&var); } _ => {} } } self.discover_functions(*file_id, file.clone(), source.clone(), program, &mut all_functions_ast); } // --- ENTRY POINT VERIFICATION --- let mut frame_fn_name = None; if let Some((_, _, _, entry_program)) = programs.first() { for item in &entry_program.body { let f_opt = match item { Statement::FunctionDeclaration(f) => Some(f.as_ref()), Statement::ExportNamedDeclaration(decl) => { if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration { Some(f.as_ref()) } else { None } } _ => None, }; if let Some(f) = f_opt { if let Some(ident) = &f.id { if ident.name == "frame" { frame_fn_name = Some(ident.name.to_string()); break; } } } } } let frame_fn_name = frame_fn_name.ok_or_else(|| anyhow!("export function frame() not found in entry file"))?; let mut module = ir::Module::new("main".to_string()); // Populate globals in IR for (name, slot) in &self.globals { module.globals.push(ir::module::Global { name: name.clone(), r#type: ir::types::Type::Any, slot: *slot, }); } // --- GLOBAL INITIALIZATION AND MAIN LOOP (__init) --- self.instructions.clear(); for (file_id, file, source, program) in &programs { self.file_name = file.clone(); self.source_text = source.clone(); self.current_file_id = *file_id; for item in &program.body { let var_opt = match item { Statement::VariableDeclaration(var) => Some(var.as_ref()), Statement::ExportNamedDeclaration(decl) => { if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration { Some(var.as_ref()) } else { None } } _ => None, }; if let Some(var) = var_opt { for decl in &var.declarations { if let BindingPattern::BindingIdentifier(ident) = &decl.id { let name = ident.name.to_string(); let id = *self.globals.get(&name).unwrap(); if let Some(init) = &decl.init { self.compile_expr(init)?; } else { self.emit_instr(InstrKind::PushInt(0), decl.span); } self.emit_instr(InstrKind::SetGlobal(id), decl.span); } } } } } self.emit_label("entry".to_string()); self.emit_instr(InstrKind::Call { name: frame_fn_name, arg_count: 0 }, Span::default()); self.emit_instr(InstrKind::Pop, Span::default()); self.emit_instr(InstrKind::FrameSync, Span::default()); self.emit_instr(InstrKind::Jmp(IRLabel("entry".to_string())), Span::default()); module.functions.push(ir::module::Function { name: "__init".to_string(), params: Vec::new(), return_type: ir::types::Type::Void, body: self.instructions.clone(), }); // --- FUNCTION COMPILATION --- for (file_id, file, source, f) in all_functions_ast { self.instructions.clear(); self.file_name = file; self.source_text = source; self.current_file_id = file_id; if let Some(ident) = &f.id { let name = ident.name.to_string(); self.compile_function(f)?; self.emit_instr(InstrKind::Ret, Span::default()); module.functions.push(ir::module::Function { name, params: Vec::new(), // TODO: map parameters to IR return_type: ir::types::Type::Any, body: self.instructions.clone(), }); } } Ok(module) } /// Registers a global variable in the symbol table. /// Global variables are accessible from any function and persist between frames. fn export_global_variable_declarations(&mut self, var: &VariableDeclaration) { for decl in &var.declarations { if let BindingPattern::BindingIdentifier(ident) = &decl.id { let name = ident.name.to_string(); if !self.globals.contains_key(&name) { let id = self.next_global; self.globals.insert(name, id); self.next_global += 1; } } } } /// Compiles a function declaration. /// /// Functions in Prometeu follow the ABI: /// 1. Parameters are mapped to the first `n` local slots. /// 2. `PushScope` is called to protect the caller's environment. /// 3. The body is compiled sequentially. /// 4. `PopScope` and `Push Null` are executed before `Ret` to ensure the stack rule. fn compile_function(&mut self, f: &Function) -> Result<()> { self.symbol_table.clear(); self.scope_depth = 0; self.next_local = 0; // Start scope for parameters and local variables self.enter_scope(); self.emit_instr(InstrKind::PushScope, f.span); // Map parameters to locals (they are pushed by the caller before the Call instruction) for param in &f.params.items { if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern { let name = ident.name.to_string(); // Parameters are considered initialized self.declare_symbol(name, false, true, ident.span)?; } } if let Some(body) = &f.body { // Reserve slots for all local variables and hoisted functions let locals_to_reserve = self.count_locals(&body.statements); for _ in 0..locals_to_reserve { // Initializing with I32 0 as it's the safest default for Prometeu VM self.emit_instr(InstrKind::PushInt(0), f.span); } // Function and Variable hoisting within the function scope self.hoist_functions(&body.statements)?; self.hoist_variables(&body.statements)?; for stmt in &body.statements { self.compile_stmt(stmt)?; } } // ABI Rule: Every function MUST leave exactly one value on the stack before RET. // If the function doesn't have a return statement, we push Null. self.emit_instr(InstrKind::PopScope, Span::default()); self.emit_instr(InstrKind::PushNull, Span::default()); Ok(()) } /// Counts the total number of local variable and function declarations in a function body. fn count_locals(&self, statements: &OXCVec) -> u32 { let mut counter = LocalCounter { count: 0 }; for stmt in statements { counter.visit_statement(stmt); } counter.count } /// Hoists function declarations to the top of the current scope. fn hoist_functions(&mut self, statements: &OXCVec) -> Result<()> { for stmt in statements { if let Statement::FunctionDeclaration(f) = stmt { if let Some(ident) = &f.id { let name = ident.name.to_string(); // Functions are hoisted and already considered initialized self.declare_symbol(name, false, true, ident.span)?; } } } Ok(()) } /// Hoists variable declarations (let/const) to the top of the current scope. fn hoist_variables(&mut self, statements: &OXCVec) -> Result<()> { for stmt in statements { if let Statement::VariableDeclaration(var) = stmt { let is_const = var.kind == VariableDeclarationKind::Const; for decl in &var.declarations { if let BindingPattern::BindingIdentifier(ident) = &decl.id { let name = ident.name.to_string(); // Register as uninitialized for TDZ self.declare_symbol(name, is_const, false, ident.span)?; } } } } Ok(()) } /// Translates a Statement into bytecode. fn compile_stmt(&mut self, stmt: &Statement) -> Result<()> { match stmt { // var x = 10; Statement::VariableDeclaration(var) => { let is_const = var.kind == VariableDeclarationKind::Const; for decl in &var.declarations { if let BindingPattern::BindingIdentifier(ident) = &decl.id { let name = ident.name.to_string(); // Variable should already be in the symbol table due to hoisting let entry = self.resolve_symbol(&name) .ok_or_else(|| anyhow!("Internal compiler error: symbol '{}' not hoisted at {:?}", name, ident.span))?; let slot_index = entry.slot_index; if let Some(init) = &decl.init { self.compile_expr(init)?; self.emit_instr(InstrKind::SetLocal(slot_index), decl.span); self.initialize_symbol(&name); } else { if is_const { return Err(anyhow!("Missing initializer in const declaration at {:?}", decl.span)); } // Default initialization to 0 self.emit_instr(InstrKind::PushInt(0), decl.span); self.emit_instr(InstrKind::SetLocal(slot_index), decl.span); self.initialize_symbol(&name); } } } } // console.log("hello"); Statement::ExpressionStatement(expr_stmt) => { self.compile_expr(&expr_stmt.expression)?; // ABI requires us to Pop unused return values from the stack to prevent leaks self.emit_instr(InstrKind::Pop, expr_stmt.span); } // if (a == b) { ... } else { ... } Statement::IfStatement(if_stmt) => { let else_label = self.new_label("else"); let end_label = self.new_label("end_if"); self.compile_expr(&if_stmt.test)?; self.emit_instr(InstrKind::JmpIfFalse(IRLabel(else_label.clone())), if_stmt.span); self.compile_stmt(&if_stmt.consequent)?; self.emit_instr(InstrKind::Jmp(IRLabel(end_label.clone())), Span::default()); self.emit_label(else_label); if let Some(alt) = &if_stmt.alternate { self.compile_stmt(alt)?; } self.emit_label(end_label); } // { let x = 1; } Statement::BlockStatement(block) => { self.enter_scope(); self.emit_instr(InstrKind::PushScope, block.span); // Hoist functions and variables in the block self.hoist_functions(&block.body)?; self.hoist_variables(&block.body)?; for stmt in &block.body { self.compile_stmt(stmt)?; } self.emit_instr(InstrKind::PopScope, block.span); self.exit_scope(); } // Function declarations are handled by hoisting and compiled separately Statement::FunctionDeclaration(_) => {} _ => return Err(anyhow!("Unsupported statement type at {:?}", stmt.span())), } Ok(()) } /// Translates an Expression into bytecode. /// Expressions always leave exactly one value at the top of the stack. fn compile_expr(&mut self, expr: &Expression) -> Result<()> { match expr { // Literals: push the value directly onto the stack Expression::NumericLiteral(n) => { let val = n.value; if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 { self.emit_instr(InstrKind::PushInt(val as i64), n.span); } else { self.emit_instr(InstrKind::PushFloat(val), n.span); } } Expression::BooleanLiteral(b) => { self.emit_instr(InstrKind::PushBool(b.value), b.span); } Expression::StringLiteral(s) => { self.emit_instr(InstrKind::PushString(s.value.to_string()), s.span); } Expression::NullLiteral(n) => { self.emit_instr(InstrKind::PushNull, n.span); } // Variable access: resolve to GetLocal or GetGlobal Expression::Identifier(ident) => { let name = ident.name.to_string(); if let Some(entry) = self.resolve_symbol(&name) { if !entry.is_initialized { return Err(anyhow!("TDZ Violation: Variable '{}' accessed before initialization at {:?}", name, ident.span)); } self.emit_instr(InstrKind::GetLocal(entry.slot_index), ident.span); } else if let Some(&id) = self.globals.get(&name) { self.emit_instr(InstrKind::GetGlobal(id), ident.span); } else { return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span)); } } // Assignment: evaluate RHS and store result in LHS slot Expression::AssignmentExpression(assign) => { if let AssignmentTarget::AssignmentTargetIdentifier(ident) = &assign.left { let name = ident.name.to_string(); if let Some(entry) = self.resolve_symbol(&name) { if entry.is_const { return Err(anyhow!("Assignment to constant variable '{}' at {:?}", name, assign.span)); } if !entry.is_initialized { return Err(anyhow!("TDZ Violation: Variable '{}' accessed before initialization at {:?}", name, assign.span)); } let slot_index = entry.slot_index; self.compile_expr(&assign.right)?; self.emit_instr(InstrKind::SetLocal(slot_index), assign.span); self.emit_instr(InstrKind::GetLocal(slot_index), assign.span); // Assignment returns the value } else if let Some(&id) = self.globals.get(&name) { self.compile_expr(&assign.right)?; self.emit_instr(InstrKind::SetGlobal(id), assign.span); self.emit_instr(InstrKind::GetGlobal(id), assign.span); } else { return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span)); } } else { return Err(anyhow!("Unsupported assignment target at {:?}", assign.span)); } } // Binary operations: evaluate both sides and apply the opcode Expression::BinaryExpression(bin) => { self.compile_expr(&bin.left)?; self.compile_expr(&bin.right)?; let kind = match bin.operator { BinaryOperator::Addition => InstrKind::Add, BinaryOperator::Subtraction => InstrKind::Sub, BinaryOperator::Multiplication => InstrKind::Mul, BinaryOperator::Division => InstrKind::Div, BinaryOperator::Equality => InstrKind::Eq, BinaryOperator::Inequality => InstrKind::Neq, BinaryOperator::LessThan => InstrKind::Lt, BinaryOperator::GreaterThan => InstrKind::Gt, BinaryOperator::LessEqualThan => InstrKind::Lte, BinaryOperator::GreaterEqualThan => InstrKind::Gte, _ => return Err(anyhow!("Unsupported binary operator {:?} at {:?}", bin.operator, bin.span)), }; self.emit_instr(kind, bin.span); } // Logical operations: evaluate both sides and apply the opcode Expression::LogicalExpression(log) => { self.compile_expr(&log.left)?; self.compile_expr(&log.right)?; let kind = match log.operator { LogicalOperator::And => InstrKind::And, LogicalOperator::Or => InstrKind::Or, _ => return Err(anyhow!("Unsupported logical operator {:?} at {:?}", log.operator, log.span)), }; self.emit_instr(kind, log.span); } // Unary operations: evaluate argument and apply the opcode Expression::UnaryExpression(unary) => { self.compile_expr(&unary.argument)?; let kind = match unary.operator { UnaryOperator::UnaryNegation => InstrKind::Neg, UnaryOperator::UnaryPlus => return Ok(()), UnaryOperator::LogicalNot => InstrKind::Not, _ => return Err(anyhow!("Unsupported unary operator {:?} at {:?}", unary.operator, unary.span)), }; self.emit_instr(kind, unary.span); } // Function calls: resolve to Syscall or Call Expression::CallExpression(call) => { let name = ast_util::get_callee_name(&call.callee)?; let name_lower = name.to_lowercase(); if name_lower == "color.rgb" { // Special case for Color.rgb(r, g, b) // It's compiled to a sequence of bitwise operations for performance if call.arguments.len() != 3 { return Err(anyhow!("Color.rgb expects 3 arguments at {:?}", call.span)); } // Argument 0: r (shift right 3, shift left 11) if let Some(expr) = call.arguments[0].as_expression() { self.compile_expr(expr)?; self.emit_instr(InstrKind::PushInt(3), call.span); self.emit_instr(InstrKind::Shr, call.span); self.emit_instr(InstrKind::PushInt(11), call.span); self.emit_instr(InstrKind::Shl, call.span); } // Argument 1: g (shift right 2, shift left 5) if let Some(expr) = call.arguments[1].as_expression() { self.compile_expr(expr)?; self.emit_instr(InstrKind::PushInt(2), call.span); self.emit_instr(InstrKind::Shr, call.span); self.emit_instr(InstrKind::PushInt(5), call.span); self.emit_instr(InstrKind::Shl, call.span); } self.emit_instr(InstrKind::BitOr, call.span); // Argument 2: b (shift right 3) if let Some(expr) = call.arguments[2].as_expression() { self.compile_expr(expr)?; self.emit_instr(InstrKind::PushInt(3), call.span); self.emit_instr(InstrKind::Shr, call.span); } self.emit_instr(InstrKind::BitOr, call.span); } else if name_lower.starts_with("input.btn") || name_lower.starts_with("pinput.btn") { // Special case for legacy Input.btnX() calls // They map to input.getPad(BTN_X) let btn_name = if name_lower.starts_with("pinput.btn") { &name[10..] } else { &name[9..] }; // Strip parentheses if any (though get_callee_name should handle just the callee) let btn_name = btn_name.strip_suffix("()").unwrap_or(btn_name); let btn_id = input_map::map_btn_name(btn_name)?; self.emit_instr(InstrKind::PushInt(btn_id as i64), call.span); let pad_id = syscall_map::map_syscall("input.getPad").ok_or_else(|| anyhow!("input.getPad syscall not found"))?; self.emit_instr(InstrKind::Syscall(pad_id), call.span); } else if let Some(syscall_id) = syscall_map::map_syscall(&name) { // Standard System Call for arg in &call.arguments { if let Some(expr) = arg.as_expression() { self.compile_expr(expr)?; } } self.emit_instr(InstrKind::Syscall(syscall_id), call.span); } else { // Local function call (to a function defined in the project) for arg in &call.arguments { if let Some(expr) = arg.as_expression() { self.compile_expr(expr)?; } } self.emit_instr(InstrKind::Call { name, arg_count: call.arguments.len() as u32 }, call.span); } } // Member access (e.g., Color.RED, Pad.A.down) Expression::StaticMemberExpression(member) => { let full_name = ast_util::get_member_expr_name(expr)?; if full_name.to_lowercase().starts_with("color.") { // Resolved at compile-time to literal values match full_name.to_lowercase().as_str() { "color.black" => self.emit_instr(InstrKind::PushInt(Color::BLACK.raw() as i64), member.span), "color.white" => self.emit_instr(InstrKind::PushInt(Color::WHITE.raw() as i64), member.span), "color.red" => self.emit_instr(InstrKind::PushInt(Color::RED.raw() as i64), member.span), "color.green" => self.emit_instr(InstrKind::PushInt(Color::GREEN.raw() as i64), member.span), "color.blue" => self.emit_instr(InstrKind::PushInt(Color::BLUE.raw() as i64), member.span), "color.yellow" => self.emit_instr(InstrKind::PushInt(Color::YELLOW.raw() as i64), member.span), "color.cyan" => self.emit_instr(InstrKind::PushInt(Color::CYAN.raw() as i64), member.span), "color.gray" | "color.grey" => self.emit_instr(InstrKind::PushInt(Color::GRAY.raw() as i64), member.span), "color.orange" => self.emit_instr(InstrKind::PushInt(Color::ORANGE.raw() as i64), member.span), "color.indigo" => self.emit_instr(InstrKind::PushInt(Color::INDIGO.raw() as i64), member.span), "color.magenta" => self.emit_instr(InstrKind::PushInt(Color::MAGENTA.raw() as i64), member.span), "color.colorKey" | "color.color_key" => self.emit_instr(InstrKind::PushInt(Color::COLOR_KEY.raw() as i64), member.span), _ => return Err(anyhow!("Unsupported color constant: {} at {:?}", full_name, member.span)), } } else if full_name.to_lowercase().starts_with("pad.") { // Re-mapped to specific input syscalls let parts: Vec<&str> = full_name.split('.').collect(); if parts.len() == 3 { let btn_name = parts[1]; let state_name = parts[2]; let btn_id = input_map::map_btn_name(btn_name)?; let syscall_name = input_map::map_pad_state(state_name)?; let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); self.emit_instr(InstrKind::PushInt(btn_id as i64), member.span); self.emit_instr(InstrKind::Syscall(syscall_id), member.span); } else { return Err(anyhow!("Partial Pad access not supported: {} at {:?}", full_name, member.span)); } } else if full_name.to_lowercase().starts_with("touch.") { // Re-mapped to specific touch syscalls let parts: Vec<&str> = full_name.split('.').collect(); match parts.len() { 2 => { let prop = parts[1]; let syscall_name = input_map::map_touch_prop(prop)?; let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); self.emit_instr(InstrKind::Syscall(syscall_id), member.span); } 3 if parts[1] == "button" => { let state_name = parts[2]; let syscall_name = input_map::map_touch_button_state(state_name)?; let syscall_id = syscall_map::map_syscall(syscall_name).unwrap(); self.emit_instr(InstrKind::Syscall(syscall_id), member.span); } _ => return Err(anyhow!("Unsupported touch access: {} at {:?}", full_name, member.span)), } } else { return Err(anyhow!("Member expression outside call not supported: {} at {:?}", full_name, member.span)); } } _ => return Err(anyhow!("Unsupported expression type at {:?}", expr.span())), } Ok(()) } /// Generates a new unique label name for control flow. fn new_label(&mut self, prefix: &str) -> String { let label = format!("{}_{}", prefix, self.label_count); self.label_count += 1; label } /// Emits a label definition in the instruction stream. fn emit_label(&mut self, name: String) { self.instructions.push(IRInstruction::new(InstrKind::Label(IRLabel(name)), None)); } /// Emits an IR instruction. fn emit_instr(&mut self, kind: InstrKind, span: Span) { let ir_span = if !span.is_unspanned() { Some(IRSpan::new(0, span.start, span.end)) } else { None }; self.instructions.push(IRInstruction::new(kind, ir_span)); } }