764 lines
34 KiB
Rust

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<ir::Instruction>,
/// Scoped symbol table. Each element is a scope level containing a map of symbols.
symbol_table: Vec<HashMap<String, SymbolEntry>>,
/// 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<String, u32>,
/// 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<ir::Module> {
// --- 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) = &param.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<Statement>) -> 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<Statement>) -> 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<Statement>) -> 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));
}
}