#![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use helpers::{Span, prelude::*}; use il::{Instruction, InstructionNode, Instructions, Operand}; use parser::{ Parser as ASTParser, sys_call::{Math, SysCall, System}, tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, IndexAccessExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, LoopExpression, MemberAccessExpression, Spanned, TernaryExpression, TupleAssignmentExpression, TupleDeclarationExpression, WhileExpression, }, }; use rust_decimal::Decimal; use std::{borrow::Cow, collections::HashMap}; use thiserror::Error; use tokenizer::token::{Number, Unit}; fn extract_literal<'a>( literal: Literal<'a>, allow_strings: bool, ) -> Result, Error<'a>> { if !allow_strings && matches!(literal, Literal::String(_)) { return Err(Error::Unknown( "Literal strings are not allowed in this context".to_string(), None, )); } Ok(match literal { Literal::String(s) => Operand::LogicType(s), Literal::Number(n) => Operand::Number(n.into()), Literal::Boolean(b) => Operand::Number(Number::from(b).into()), }) } #[derive(Error, Debug)] pub enum Error<'a> { #[error("{0}")] Parse(parser::Error<'a>), #[error("{0}")] Scope(variable_manager::Error<'a>), #[error("IO Error: {0}")] IO(String), #[error("`{0}` has already been defined.")] DuplicateIdentifier(Cow<'a, str>, Span), #[error("`{0}` is not found in the current scope.")] UnknownIdentifier(Cow<'a, str>, Span), #[error("`{0}` is not valid.")] InvalidDevice(Cow<'a, str>, Span), #[error("Incorrent number of arguments passed into `{0}`")] AgrumentMismatch(Cow<'a, str>, Span), #[error("Attempted to re-assign a value to const variable `{0}`")] ConstAssignment(Cow<'a, str>, Span), #[error("Attempted to re-assign a value to a device const `{0}`")] DeviceAssignment(Cow<'a, str>, Span), #[error("Expected a {0}-tuple, but you're trying to destructure into {1} variables")] TupleSizeMismatch(usize, usize, Span), #[error("{0}")] OperationNotSupported(String, Span), #[error("{0}")] Unknown(String, Option), } impl<'a> From> for lsp_types::Diagnostic { fn from(value: Error) -> Self { use Error::*; use lsp_types::*; match value { Parse(e) => e.into(), IO(e) => Diagnostic { message: e.to_string(), severity: Some(DiagnosticSeverity::ERROR), ..Default::default() }, Scope(e) => e.into(), DuplicateIdentifier(_, span) | UnknownIdentifier(_, span) | InvalidDevice(_, span) | ConstAssignment(_, span) | DeviceAssignment(_, span) | AgrumentMismatch(_, span) | TupleSizeMismatch(_, _, span) | OperationNotSupported(_, span) => Diagnostic { range: span.into(), message: value.to_string(), severity: Some(DiagnosticSeverity::ERROR), ..Default::default() }, Unknown(msg, span) => Diagnostic { message: msg.to_string(), severity: Some(DiagnosticSeverity::ERROR), range: span.map(lsp_types::Range::from).unwrap_or_default(), ..Default::default() }, } } } impl<'a> From> for Error<'a> { fn from(value: parser::Error<'a>) -> Self { Self::Parse(value) } } impl<'a> From> for Error<'a> { fn from(value: variable_manager::Error<'a>) -> Self { Self::Scope(value) } } // Map io::Error to Error manually since we can't clone io::Error impl<'a> From for Error<'a> { fn from(err: std::io::Error) -> Self { Error::IO(err.to_string()) } } #[derive(Default)] #[repr(C)] pub struct CompilerConfig { pub debug: bool, } #[derive(Debug)] struct CompileLocation<'a> { location: VariableLocation<'a>, /// If Some, this is the name of the temporary variable that holds the result. /// It must be freed by the caller when done. temp_name: Option>, } pub struct CompilationResult<'a> { pub errors: Vec>, pub instructions: Instructions<'a>, } /// Metadata for the currently compiling function #[derive(Debug)] struct FunctionMetadata<'a> { /// Maps function name to its instruction location locations: HashMap, usize>, /// Maps function name to list of parameter names params: HashMap, Vec>>, /// Maps function name to tuple return size (if it returns a tuple) tuple_return_sizes: HashMap, usize>, /// Name of the function currently being compiled current_name: Option>, /// Return label for the current function return_label: Option>, /// Size of tuple return for the current function (0 if not returning tuple) tuple_return_size: u16, /// Whether the SP (stack pointer) has been saved for the current function sp_saved: bool, /// Variable name for the saved SP at function entry (for stack unwinding) sp_backup_var: Option>, } impl<'a> Default for FunctionMetadata<'a> { fn default() -> Self { Self { locations: HashMap::new(), params: HashMap::new(), tuple_return_sizes: HashMap::new(), current_name: None, return_label: None, tuple_return_size: 0, sp_saved: false, sp_backup_var: None, } } } pub struct Compiler<'a> { pub parser: ASTParser<'a>, function_meta: FunctionMetadata<'a>, devices: HashMap, Cow<'a, str>>, // This holds the IL code which will be used in the // optimizer pub instructions: Instructions<'a>, current_line: usize, declared_main: bool, _config: CompilerConfig, temp_counter: usize, label_counter: usize, loop_stack: Vec<(Cow<'a, str>, Cow<'a, str>)>, // Stores (start_label, end_label) /// stores (IC10 `line_num`, `Vec`) pub source_map: HashMap>, /// Accumulative errors from the compilation process pub errors: Vec>, } impl<'a> Compiler<'a> { pub fn new(parser: ASTParser<'a>, config: Option) -> Self { Self { parser, function_meta: FunctionMetadata::default(), devices: HashMap::new(), instructions: Instructions::default(), current_line: 1, declared_main: false, _config: config.unwrap_or_default(), temp_counter: 0, label_counter: 0, loop_stack: Vec::new(), source_map: HashMap::new(), errors: Vec::new(), } } pub fn compile(mut self) -> CompilationResult<'a> { let expr = self.parser.parse_all(); // Copy errors from parser for e in std::mem::take(&mut self.parser.errors) { self.errors.push(Error::Parse(e)); } // We treat parse_all result as potentially partial let expr = match expr { Ok(Some(expr)) => expr, Ok(None) => { return CompilationResult { errors: self.errors, instructions: self.instructions, }; } Err(e) => { // Should be covered by parser.errors, but just in case self.errors.push(Error::Parse(e)); return CompilationResult { errors: self.errors, instructions: self.instructions, }; } }; // Wrap the root expression in a dummy span for consistency let span = if let Expression::Block(ref block) = expr { block.span } else { Span { start_line: 0, end_line: 0, start_col: 0, end_col: 0, } }; let spanned_root = Spanned { node: expr, span }; if let Err(e) = self.write_instruction( Instruction::Jump(Operand::Label(Cow::from("main"))), Some(span), ) { self.errors.push(e); return CompilationResult { errors: self.errors, instructions: self.instructions, }; } let mut scope = VariableScope::default(); // We ignore the result of the root expression (usually a block) if let Err(e) = self.expression(spanned_root, &mut scope) { self.errors.push(e); } CompilationResult { errors: self.errors, instructions: self.instructions, } } /// Performs a write to the output buffer as well as a push to the IL instructions vec fn write_instruction( &mut self, instr: Instruction<'a>, span: Option, ) -> Result<(), Error<'a>> { self.current_line += 1; self.instructions.push(InstructionNode::new(instr, span)); Ok(()) } fn next_temp_name(&mut self) -> Cow<'a, str> { self.temp_counter += 1; Cow::from(format!("__binary_temp_{}", self.temp_counter)) } fn next_label_name(&mut self) -> Cow<'a, str> { self.label_counter += 1; Cow::from(format!("__internal_L{}", self.label_counter)) } /// Merges two spans into a single span covering both fn merge_spans(start: Span, end: Span) -> Span { Span { start_line: start.start_line, start_col: start.start_col, end_line: end.end_line, end_col: end.end_col, } } /// Cleans up temporary variables, ignoring errors fn cleanup_temps( scope: &mut VariableScope<'a, '_>, temps: &[Option>], ) -> Result<(), Error<'a>> { for temp in temps { if let Some(name) = temp { scope.free_temp(name.clone(), None)?; } } Ok(()) } fn expression( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result>, Error<'a>> { match expr.node { Expression::Function(expr_func) => { self.expression_function(expr_func, scope)?; Ok(None) } Expression::Block(expr_block) => { self.expression_block(expr_block.node, scope)?; Ok(None) } Expression::If(expr_if) => { self.expression_if(expr_if.node, scope)?; Ok(None) } Expression::Loop(expr_loop) => { self.expression_loop(expr_loop.node, scope)?; Ok(None) } Expression::Syscall(Spanned { node: SysCall::System(system), span, }) => self.expression_syscall_system(system, span, scope), Expression::Syscall(Spanned { node: SysCall::Math(math), span, }) => self.expression_syscall_math(math, span, scope), Expression::While(expr_while) => { self.expression_while(expr_while.node, scope)?; Ok(None) } Expression::Break(span) => { self.expression_break(span)?; Ok(None) } Expression::Continue(span) => { self.expression_continue(span)?; Ok(None) } Expression::DeviceDeclaration(expr_dev) => { self.expression_device(expr_dev.node)?; Ok(None) } Expression::Declaration(var_name, decl_expr) => { // decl_expr is Box> self.expression_declaration(var_name, *decl_expr, scope) } Expression::ConstDeclaration(const_decl_expr) => { self.expression_const_declaration(const_decl_expr.node, scope)?; Ok(None) } Expression::Assignment(assign_expr) => { self.expression_assignment(assign_expr.node, scope)?; Ok(None) } Expression::Ternary(tern) => Ok(Some(self.expression_ternary(tern.node, scope)?)), Expression::Invocation(expr_invoke) => { // Special case: hash() with string literal can be evaluated at compile time if expr_invoke.node.name.node == "hash" && expr_invoke.node.arguments.len() == 1 { if let Expression::Literal(Spanned { node: Literal::String(str_to_hash), .. }) = &expr_invoke.node.arguments[0].node { // Evaluate hash at compile time let hash_value = crc_hash_signed(str_to_hash); return Ok(Some(CompileLocation { location: VariableLocation::Constant(Literal::Number(Number::Integer( hash_value, Unit::None, ))), temp_name: None, })); } } // Non-constant hash calls or other function calls self.expression_function_invocation(expr_invoke, scope)?; // Invocation returns result in r15 (RETURN_REGISTER). // If used as an expression, we must move it to a temp to avoid overwrite. let temp_name = self.next_temp_name(); let temp_loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment( &temp_loc, Operand::Register(VariableScope::RETURN_REGISTER), )?; Ok(Some(CompileLocation { location: temp_loc, temp_name: Some(temp_name), })) } Expression::Binary(bin_expr) => { let result = self.expression_binary(bin_expr, scope)?; Ok(Some(result)) } Expression::Logical(log_expr) => { let result = self.expression_logical(log_expr, scope)?; Ok(Some(result)) } Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment(&loc, Operand::Number(num.into()))?; Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), })) } Literal::Boolean(b) => { let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment(&loc, Operand::Number(Number::from(b).into()))?; Ok(Some(CompileLocation { location: loc, temp_name: Some(temp_name), })) } _ => Ok(None), // String literals don't return values in this context typically }, Expression::Variable(name) => { match scope.get_location_of(&name.node, Some(name.span)) { Ok(loc) => Ok(Some(CompileLocation { location: loc, temp_name: None, // User variable, do not free })), Err(_) => { // fallback, check devices if let Some(device) = self.devices.get(&name.node) { Ok(Some(CompileLocation { location: VariableLocation::Device(device.clone()), temp_name: None, })) } else { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); Ok(Some(CompileLocation { location: VariableLocation::Temporary(0), temp_name: None, })) } } } } Expression::MemberAccess(access) => { // "load" behavior (e.g. `let x = d0.On`) let MemberAccessExpression { object, member } = access.node; // 1. Resolve the object to a device string (e.g., "d0" or "rX") let (device, cleanup) = self.resolve_device(*object, scope)?; // 2. Allocate a temp register for the result let result_name = self.next_temp_name(); let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let reg = self.resolve_register(&loc)?; // 3. Emit load instruction: l rX device member self.write_instruction( Instruction::Load( Operand::Register(reg), device, Operand::LogicType(member.node), ), Some(expr.span), )?; // 4. Cleanup if let Some(c) = cleanup { scope.free_temp(c, None)?; } Ok(Some(CompileLocation { location: loc, temp_name: Some(result_name), })) } Expression::IndexAccess(access) => { // "get" behavior (e.g. `let x = d0[255]`) let IndexAccessExpression { object, index } = access.node; // 1. Resolve the object to a device string let (device, dev_cleanup) = self.resolve_device(*object, scope)?; // Check if device is "db" (not allowed) if let Operand::Device(ref dev_str) = device { if dev_str.as_ref() == "db" { return Err(Error::OperationNotSupported( "Direct stack access on 'db' is not yet supported".to_string(), expr.span, )); } } // 2. Compile the index expression to get the address let (addr, addr_cleanup) = self.compile_operand(*index, scope)?; // 3. Allocate a temp register for the result let result_name = self.next_temp_name(); let loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let reg = self.resolve_register(&loc)?; // 4. Emit get instruction: get rX device address self.write_instruction( Instruction::Get(Operand::Register(reg), device, addr), Some(expr.span), )?; // 5. Cleanup if let Some(c) = dev_cleanup { scope.free_temp(c, None)?; } if let Some(c) = addr_cleanup { scope.free_temp(c, None)?; } Ok(Some(CompileLocation { location: loc, temp_name: Some(result_name), })) } Expression::MethodCall(call) => { // Methods are not yet fully supported (e.g. `d0.SomeFunc()`). // This would likely map to specialized syscalls or batch instructions. Err(Error::Unknown( format!( "Method calls are not yet supported: {}", call.node.method.node ), Some(call.span), )) } Expression::Priority(inner_expr) => self.expression(*inner_expr, scope), Expression::Negation(inner_expr) => { // Compile negation as 0 - inner let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?; let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; self.write_instruction( Instruction::Sub( Operand::Register(result_reg), Operand::Number(0.into()), inner_str, ), Some(expr.span), )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } Ok(Some(CompileLocation { location: result_loc, temp_name: Some(result_name), })) } Expression::BitwiseNot(inner_expr) => { // Compile bitwise NOT using the NOT instruction let (inner_str, cleanup) = self.compile_operand(*inner_expr, scope)?; let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; self.write_instruction( Instruction::Not(Operand::Register(result_reg), inner_str), Some(expr.span), )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } Ok(Some(CompileLocation { location: result_loc, temp_name: Some(result_name), })) } Expression::TupleDeclaration(tuple_decl) => { self.expression_tuple_declaration(tuple_decl.node, scope)?; Ok(None) } Expression::TupleAssignment(tuple_assign) => { self.expression_tuple_assignment(tuple_assign.node, scope)?; Ok(None) } _ => Err(Error::Unknown( format!( "Expression type not yet supported in general expression context: {:?}", expr.node ), Some(expr.span), )), } } /// Resolves an expression to a device identifier string for use in instructions like `s` or `l`. /// Returns (device_string, optional_cleanup_temp_name). fn resolve_device( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result<(Operand<'a>, Option>), Error<'a>> { // If it's a direct variable reference, check if it's a known device alias first if let Expression::Variable(ref name) = expr.node && let Some(device_id) = self.devices.get(&name.node) { return Ok((Operand::Device(device_id.clone()), None)); } // Otherwise, compile it as an operand (e.g. it might be a register holding a device hash/id) self.compile_operand(expr, scope) } fn emit_variable_assignment( &mut self, location: &VariableLocation<'a>, source_value: Operand<'a>, ) -> Result<(), Error<'a>> { match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_instruction( Instruction::Move(Operand::Register(*reg), source_value), None, )?; } VariableLocation::Stack(_) => { self.write_instruction(Instruction::Push(source_value), None)?; } VariableLocation::Constant(_) => { return Err(Error::Unknown( r#"Attempted to emit a variable assignent for a constant value. This is a Compiler bug and should be reported to the developer."# .into(), None, )); } VariableLocation::Device(_) => { return Err(Error::Unknown( r#"Attempted to emit a variable assignent for device. This is a Compiler bug and should be reported to the developer."# .into(), None, )); } } Ok(()) } fn expression_declaration( &mut self, var_name: Spanned>, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result>, Error<'a>> { let name_str = var_name.node; let name_span = var_name.span; // optimization. Check for a negated numeric literal (including nested negations) // e.g., -5, -(-5), -(-(5)), etc. if let Some(num) = self.try_fold_negation(&expr.node) { let loc = scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?; self.emit_variable_assignment(&loc, Operand::Number(num.into()))?; return Ok(Some(CompileLocation { location: loc, temp_name: None, })); } let (loc, temp_name) = match expr.node { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { let var_location = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment(&var_location, Operand::Number(num.into()))?; (var_location, None) } Literal::Boolean(b) => { let var_location = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment( &var_location, Operand::Number(Number::from(b).into()), )?; (var_location, None) } _ => return Ok(None), }, Expression::Invocation(invoke_expr) => { self.expression_function_invocation(invoke_expr, scope)?; let loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment( &loc, Operand::Register(VariableScope::RETURN_REGISTER), )?; (loc, None) } Expression::Syscall(spanned_call) => { let sys_call = spanned_call.node; let res = match sys_call { SysCall::System(s) => { self.expression_syscall_system(s, spanned_call.span, scope)? } SysCall::Math(m) => { self.expression_syscall_math(m, spanned_call.span, scope)? } }; if res.is_none() { return Err(Error::Unknown( "SysCall did not return a value".into(), Some(spanned_call.span), )); }; let loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment( &loc, Operand::Register(VariableScope::RETURN_REGISTER), )?; (loc, None) } // Support assigning binary expressions to variables directly Expression::Binary(bin_expr) => { let result = self.expression_binary(bin_expr, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; if let CompileLocation { location: VariableLocation::Constant(Literal::Number(num)), .. } = result { self.emit_variable_assignment(&var_loc, Operand::Number(num.into()))?; (var_loc, None) } else { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&result.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } (var_loc, None) } } Expression::Logical(log_expr) => { let result = self.expression_logical(log_expr, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; // Move result from temp to new persistent variable let result_reg = self.resolve_register(&result.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } (var_loc, None) } Expression::Variable(name) => { let src_loc_res = scope.get_location_of(&name.node, Some(name.span)); let src_loc = match src_loc_res { Ok(l) => l, Err(_) => { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); VariableLocation::Temporary(0) } }; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; // Handle loading from stack if necessary let src = match src_loc { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { Operand::Register(r) } VariableLocation::Stack(offset) => { self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(offset.into()), ), Some(expr.span), )?; self.write_instruction( Instruction::Get( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(expr.span), )?; Operand::Register(VariableScope::TEMP_STACK_REGISTER) } VariableLocation::Constant(Literal::Number(num)) => Operand::Number(num.into()), VariableLocation::Constant(Literal::Boolean(b)) => { Operand::Number(Number::from(b).into()) } VariableLocation::Device(_) | VariableLocation::Constant(Literal::String(_)) => unreachable!(), }; self.emit_variable_assignment(&var_loc, src)?; (var_loc, None) } Expression::Priority(inner) => { return self.expression_declaration( Spanned { node: name_str, span: name_span, }, *inner, scope, ); } Expression::MemberAccess(access) => { // Compile the member access (load instruction) let result = self.expression( Spanned { node: Expression::MemberAccess(access), span: name_span, // Use declaration span roughly }, scope, )?; // Result is in a temp register let Some(comp_res) = result else { return Err(Error::Unknown( "Member access did not return a value".into(), Some(name_span), )); }; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; let result_reg = self.resolve_register(&comp_res.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; if let Some(temp) = comp_res.temp_name { scope.free_temp(temp, None)?; } (var_loc, None) } Expression::Ternary(ternary) => { let res = self.expression_ternary(ternary.node, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; let res_register = self.resolve_register(&res.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(res_register))?; if let Some(name) = res.temp_name { scope.free_temp(name, None)?; } (var_loc, None) } Expression::Negation(_) => { // Use try_fold_negation to see if this is a constant folded negation if let Some(num) = self.try_fold_negation(&expr.node) { let loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment(&loc, Operand::Number(num.into()))?; return Ok(Some(CompileLocation { location: loc, temp_name: None, })); } // Otherwise, compile the negation expression let result = self.expression(expr, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; if let Some(res) = result { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&res.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = res.temp_name { scope.free_temp(name, None)?; } } else { return Err(Error::Unknown( format!("`{name_str}` negation expression did not produce a value"), Some(name_span), )); } (var_loc, None) } Expression::BitwiseNot(_) => { // Compile the bitwise NOT expression let result = self.expression(expr, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; if let Some(res) = result { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&res.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = res.temp_name { scope.free_temp(name, None)?; } } else { return Err(Error::Unknown( format!("`{name_str}` bitwise NOT expression did not produce a value"), Some(name_span), )); } (var_loc, None) } Expression::IndexAccess(_) => { // Compile the index access expression let result = self.expression(expr, scope)?; let var_loc = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; if let Some(res) = result { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&res.location)?; self.emit_variable_assignment(&var_loc, Operand::Register(result_reg))?; // Free the temp result if let Some(name) = res.temp_name { scope.free_temp(name, None)?; } } else { return Err(Error::Unknown( format!("`{name_str}` index access expression did not produce a value"), Some(name_span), )); } (var_loc, None) } _ => { return Err(Error::Unknown( format!("`{name_str}` declaration of this type is not supported/implemented."), Some(name_span), )); } }; Ok(Some(CompileLocation { location: loc, temp_name, })) } fn expression_const_declaration( &mut self, expr: ConstDeclarationExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { let ConstDeclarationExpression { name: const_name, value: const_value, } = expr; // check for a hash expression or a literal let value = match const_value { LiteralOr::Or(Spanned { node: SysCall::System(System::Hash(Spanned { node: Literal::String(str_to_hash), .. })), .. }) => Literal::Number(Number::Integer(crc_hash_signed(&str_to_hash), Unit::None)), LiteralOr::Or(Spanned { span, .. }) => { return Err(Error::Unknown( "hash only supports string literals in this context.".into(), Some(span), )); } LiteralOr::Literal(Spanned { node, .. }) => node, }; Ok(CompileLocation { location: scope.define_const(const_name.node, value, Some(const_name.span))?, temp_name: None, }) } fn expression_assignment( &mut self, expr: AssignmentExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let AssignmentExpression { assignee, expression, } = expr; let expr_span = expression.span; match assignee.node { Expression::Variable(identifier) => { let location = match scope.get_location_of(&identifier.node, Some(identifier.span)) { Ok(l) => l, Err(_) => { self.errors.push(Error::UnknownIdentifier( identifier.node.clone(), identifier.span, )); VariableLocation::Temporary(0) } }; let (val, cleanup) = self.compile_operand(*expression, scope)?; match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_instruction( Instruction::Move(Operand::Register(reg), val), Some(expr_span), )?; } VariableLocation::Stack(offset) => { // Calculate address: sp - offset self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(offset.into()), ), Some(expr_span), )?; // Store value to stack/db at address self.write_instruction( Instruction::Put( Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), val, ), Some(expr_span), )?; } VariableLocation::Constant(_) => { return Err(Error::ConstAssignment(identifier.node, identifier.span)); } VariableLocation::Device(_) => { return Err(Error::DeviceAssignment(identifier.node, identifier.span)); } } if let Some(name) = cleanup { scope.free_temp(name, None)?; } } Expression::MemberAccess(access) => { // Set instruction: s device member value let MemberAccessExpression { object, member } = access.node; let (device, dev_cleanup) = self.resolve_device(*object, scope)?; let (val, val_cleanup) = self.compile_operand(*expression, scope)?; self.write_instruction( Instruction::Store(device, Operand::LogicType(member.node), val), Some(member.span), )?; if let Some(c) = dev_cleanup { scope.free_temp(c, None)?; } if let Some(c) = val_cleanup { scope.free_temp(c, None)?; } } Expression::IndexAccess(access) => { // Put instruction: put device address value let IndexAccessExpression { object, index } = access.node; let (device, dev_cleanup) = self.resolve_device(*object, scope)?; // Check if device is "db" (not allowed) if let Operand::Device(ref dev_str) = device { if dev_str.as_ref() == "db" { return Err(Error::OperationNotSupported( "Direct stack access on 'db' is not yet supported".to_string(), assignee.span, )); } } let (addr, addr_cleanup) = self.compile_operand(*index, scope)?; let (val, val_cleanup) = self.compile_operand(*expression, scope)?; self.write_instruction(Instruction::Put(device, addr, val), Some(assignee.span))?; if let Some(c) = dev_cleanup { scope.free_temp(c, None)?; } if let Some(c) = addr_cleanup { scope.free_temp(c, None)?; } if let Some(c) = val_cleanup { scope.free_temp(c, None)?; } } _ => { return Err(Error::Unknown( "Invalid assignment target. Only variables and member access are supported." .into(), Some(assignee.span), )); } } Ok(()) } fn expression_function_invocation_with_invocation( &mut self, invoke_expr: &InvocationExpression<'a>, parent_scope: &mut VariableScope<'a, '_>, backup_registers: bool, ) -> Result<(), Error<'a>> { let InvocationExpression { name, arguments } = invoke_expr; if !self .function_meta .locations .contains_key(name.node.as_ref()) { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); return Ok(()); } let Some(args) = self.function_meta.params.get(name.node.as_ref()) else { return Err(Error::UnknownIdentifier(name.node.clone(), name.span)); }; if args.len() != arguments.len() { self.errors .push(Error::AgrumentMismatch(name.node.clone(), name.span)); return Ok(()); } let mut stack = VariableScope::scoped(parent_scope); // Get the list of active registers (may or may not backup) let active_registers = stack.registers(); // backup all used registers to the stack (unless this is for tuple return handling) if backup_registers { for register in &active_registers { stack.add_variable( Cow::from(format!("temp_{register}")), LocationRequest::Stack, None, )?; self.write_instruction( Instruction::Push(Operand::Register(*register)), Some(name.span), )?; } } for arg in arguments { match &arg.node { Expression::Literal(spanned_lit) => match &spanned_lit.node { Literal::Number(num) => { self.write_instruction( Instruction::Push(Operand::Number((*num).into())), Some(spanned_lit.span), )?; } Literal::Boolean(b) => { self.write_instruction( Instruction::Push(Operand::Number(Number::from(*b).into())), Some(spanned_lit.span), )?; } _ => {} }, Expression::Variable(var_name) => { let loc = match stack.get_location_of(&var_name.node, Some(var_name.span)) { Ok(l) => l, Err(_) => { self.errors.push(Error::UnknownIdentifier( var_name.node.clone(), var_name.span, )); VariableLocation::Temporary(0) } }; match loc { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { self.write_instruction( Instruction::Push(Operand::Register(reg)), Some(var_name.span), )?; } VariableLocation::Constant(lit) => { self.write_instruction( Instruction::Push(extract_literal(lit, false)?), Some(var_name.span), )?; } VariableLocation::Stack(stack_offset) => { self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(stack_offset.into()), ), Some(var_name.span), )?; self.write_instruction( Instruction::Get( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(var_name.span), )?; self.write_instruction( Instruction::Push(Operand::Register( VariableScope::TEMP_STACK_REGISTER, )), Some(var_name.span), )?; } VariableLocation::Device(_) => { self.errors.push(Error::Unknown( "Device references not supported in function arguments".into(), Some(var_name.span), )); } } } _ => { self.errors.push(Error::Unknown( "Only literals and variables supported in function arguments".into(), Some(arg.span), )); } } } let Some(_location) = self.function_meta.locations.get(&name.node) else { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); return Ok(()); }; self.write_instruction( Instruction::JumpAndLink(Operand::Label(name.node.clone())), Some(name.span), )?; // Pop the arguments off the stack (caller cleanup convention) // BUT: If the function returns a tuple, it saves SP in r15 and the caller // will restore SP with "move sp r15", which automatically cleans up everything. // So we only pop arguments for non-tuple-returning functions. let returns_tuple = self .function_meta .tuple_return_sizes .get(&name.node) .copied() .unwrap_or(0) > 0; if !returns_tuple { for _ in 0..arguments.len() { self.write_instruction( Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)), Some(name.span), )?; } } // pop all registers back (if they were backed up) if backup_registers { for register in active_registers.iter().rev() { self.write_instruction( Instruction::Pop(Operand::Register(*register)), Some(name.span), )?; } } Ok(()) } /// Helper: Validate tuple size from function return fn validate_tuple_function_size( &mut self, func_name: &Cow<'a, str>, expected_count: usize, span: Span, ) { if let Some(&actual_size) = self.function_meta.tuple_return_sizes.get(func_name) { if actual_size != expected_count { self.errors .push(Error::TupleSizeMismatch(actual_size, expected_count, span)); } } } /// Helper: Pop tuple values from stack into variables (for function returns) /// Variables are popped in reverse order (LIFO) fn pop_tuple_values( &mut self, var_locations: Vec<(Option, Span)>, ) -> Result<(), Error<'a>> { for (var_loc_opt, span) in var_locations.into_iter().rev() { if let Some(var_location) = var_loc_opt { match var_location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_instruction( Instruction::Pop(Operand::Register(reg)), Some(span), )?; } VariableLocation::Stack(offset) => { // Pop into temp register, then write to stack self.write_instruction( Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)), Some(span), )?; self.write_instruction( Instruction::Sub( Operand::Register(0), Operand::StackPointer, Operand::Number(offset.into()), ), Some(span), )?; self.write_instruction( Instruction::Put( Operand::Device(Cow::from("db")), Operand::Register(0), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; } VariableLocation::Constant(_) => { return Err(Error::ConstAssignment(Cow::from("tuple element"), span)); } VariableLocation::Device(_) => { return Err(Error::DeviceAssignment(Cow::from("tuple element"), span)); } } } else { // Underscore: pop into temp register to discard self.write_instruction( Instruction::Pop(Operand::Register(VariableScope::TEMP_STACK_REGISTER)), Some(span), )?; } } // Restore stack pointer from r15 to clean up remaining tuple values // (r15 contains the caller's SP from before the function was called) self.write_instruction( Instruction::Move( Operand::StackPointer, Operand::Register(VariableScope::RETURN_REGISTER), ), None, )?; Ok(()) } fn expression_tuple_declaration( &mut self, tuple_decl: TupleDeclarationExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let TupleDeclarationExpression { names, value } = tuple_decl; match value.node { Expression::Invocation(invoke_expr) => { // Execute the function call - tuple values will be on the stack self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?; // Validate tuple return size matches the declaration self.validate_tuple_function_size( &invoke_expr.node.name.node, names.len(), value.span, ); // Allocate variables and collect their locations let var_locations: Vec<_> = names .iter() .map(|name_spanned| { if name_spanned.node.as_ref() == "_" { Ok((None, name_spanned.span)) } else { let var_location = scope.add_variable( name_spanned.node.clone(), LocationRequest::Persist, Some(name_spanned.span), )?; Ok((Some(var_location), name_spanned.span)) } }) .collect::>>()?; // Pop tuple values from stack into variables self.pop_tuple_values(var_locations)?; } Expression::Tuple(tuple_expr) => { // Direct tuple literal: (value1, value2, ...) let tuple_elements = tuple_expr.node; // Validate tuple size matches names if tuple_elements.len() != names.len() { return Err(Error::TupleSizeMismatch( names.len(), tuple_elements.len(), value.span, )); } // Compile each element and assign to corresponding variable for (name_spanned, element) in names.into_iter().zip(tuple_elements.into_iter()) { // Skip underscores if name_spanned.node.as_ref() == "_" { continue; } // Add variable to scope let var_location = scope.add_variable( name_spanned.node.clone(), LocationRequest::Persist, Some(name_spanned.span), )?; // Compile the element expression - use compile_operand to handle all expression types let (value_operand, cleanup) = self.compile_operand(element, scope)?; self.emit_variable_assignment(&var_location, value_operand)?; // Clean up any temporary registers used for complex expressions if let Some(temp_name) = cleanup { scope.free_temp(temp_name, None)?; } } } _ => { return Err(Error::Unknown( "Tuple declaration only supports function invocations or tuple literals as RHS" .into(), Some(value.span), )); } } Ok(()) } fn expression_tuple_assignment( &mut self, tuple_assign: TupleAssignmentExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let TupleAssignmentExpression { names, value } = tuple_assign; match value.node { Expression::Invocation(invoke_expr) => { // Execute the function call - tuple values will be on the stack self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?; // Validate tuple return size matches the assignment self.validate_tuple_function_size( &invoke_expr.node.name.node, names.len(), value.span, ); // Look up existing variable locations let var_locations: Vec<_> = names .iter() .map(|name_spanned| { if name_spanned.node.as_ref() == "_" { Ok((None, name_spanned.span)) } else { let var_location = scope .get_location_of(&name_spanned.node, Some(name_spanned.span)) .unwrap_or_else(|_| { self.errors.push(Error::UnknownIdentifier( name_spanned.node.clone(), name_spanned.span, )); VariableLocation::Temporary(0) }); Ok((Some(var_location), name_spanned.span)) } }) .collect::>>()?; // Pop tuple values from stack into variables self.pop_tuple_values(var_locations)?; } Expression::Tuple(tuple_expr) => { // Direct tuple literal: (value1, value2, ...) let tuple_elements = tuple_expr.node; // Validate tuple size matches names if tuple_elements.len() != names.len() { return Err(Error::TupleSizeMismatch( tuple_elements.len(), names.len(), value.span, )); } // Compile each element and assign to corresponding variable for (name_spanned, element) in names.into_iter().zip(tuple_elements.into_iter()) { // Skip underscores if name_spanned.node.as_ref() == "_" { continue; } // Get the existing variable location let var_location = match scope.get_location_of(&name_spanned.node, Some(name_spanned.span)) { Ok(l) => l, Err(_) => { self.errors.push(Error::UnknownIdentifier( name_spanned.node.clone(), name_spanned.span, )); VariableLocation::Temporary(0) } }; // Compile the element expression - use compile_operand to handle all expression types let (value_operand, cleanup) = self.compile_operand(element, scope)?; // Assign the compiled value to the target variable location match &var_location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_instruction( Instruction::Move(Operand::Register(*reg), value_operand), Some(name_spanned.span), )?; } VariableLocation::Stack(offset) => { self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number((*offset).into()), ), Some(name_spanned.span), )?; self.write_instruction( Instruction::Put( Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), value_operand, ), Some(name_spanned.span), )?; } VariableLocation::Constant(_) => { return Err(Error::ConstAssignment( name_spanned.node.clone(), name_spanned.span, )); } VariableLocation::Device(_) => { return Err(Error::DeviceAssignment( name_spanned.node.clone(), name_spanned.span, )); } } // Clean up any temporary registers used for complex expressions if let Some(temp_name) = cleanup { scope.free_temp(temp_name, None)?; } } } _ => { return Err(Error::Unknown( "Tuple assignment only supports function invocations or tuple literals as RHS" .into(), Some(value.span), )); } } Ok(()) } fn expression_function_invocation( &mut self, invoke_expr: Spanned>, parent_scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let InvocationExpression { name, arguments } = invoke_expr.node; if !self.function_meta.locations.contains_key(&name.node) { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); // Don't emit call, just pretend we did? // Actually, we should probably emit a dummy call or just skip to avoid logic errors // But if we skip, registers might be unbalanced if something expected a return. // For now, let's just return early. return Ok(()); } let Some(args) = self.function_meta.params.get(&name.node) else { // Should be covered by check above return Err(Error::UnknownIdentifier(name.node, name.span)); }; if args.len() != arguments.len() { self.errors .push(Error::AgrumentMismatch(name.node, name.span)); // Proceed anyway? The assembly will likely crash or act weird. // Best to skip generation of this call to prevent bad IC10 return Ok(()); } let mut stack = VariableScope::scoped(parent_scope); // backup all used registers to the stack let active_registers = stack.registers(); for register in &active_registers { stack.add_variable( Cow::from(format!("temp_{register}")), LocationRequest::Stack, None, )?; self.write_instruction( Instruction::Push(Operand::Register(*register)), Some(name.span), )?; } for arg in arguments { match arg.node { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { self.write_instruction( Instruction::Push(Operand::Number(num.into())), Some(spanned_lit.span), )?; } Literal::Boolean(b) => { self.write_instruction( Instruction::Push(Operand::Number(Number::from(b).into())), Some(spanned_lit.span), )?; } _ => {} }, Expression::Variable(var_name) => { let loc = match stack.get_location_of(&var_name.node, Some(var_name.span)) { Ok(l) => l, Err(_) => { self.errors .push(Error::UnknownIdentifier(var_name.node, var_name.span)); VariableLocation::Temporary(0) } }; match loc { VariableLocation::Persistant(reg) | VariableLocation::Temporary(reg) => { self.write_instruction( Instruction::Push(Operand::Register(reg)), Some(var_name.span), )?; } VariableLocation::Constant(lit) => { self.write_instruction( Instruction::Push(extract_literal(lit, false)?), Some(var_name.span), )?; } VariableLocation::Stack(stack_offset) => { self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(stack_offset.into()), ), Some(var_name.span), )?; self.write_instruction( Instruction::Get( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(var_name.span), )?; self.write_instruction( Instruction::Push(Operand::Register( VariableScope::TEMP_STACK_REGISTER, )), Some(var_name.span), )?; } VariableLocation::Device(_) => { return Err(Error::Unknown( r#"Attempted to pass a device contant into a function argument. These values can be used without scope."#.into(), Some(arg.span), )); } } } Expression::Binary(bin_expr) => { let span = bin_expr.span; // Compile the binary expression to a temp register let result = self.expression_binary(bin_expr, &mut stack)?; let reg = self.resolve_register(&result.location)?; self.write_instruction(Instruction::Push(Operand::Register(reg)), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::Logical(log_expr) => { let span = log_expr.span; // Compile the logical expression to a temp register let result = self.expression_logical(log_expr, &mut stack)?; let reg = self.resolve_register(&result.location)?; self.write_instruction(Instruction::Push(Operand::Register(reg)), Some(span))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::MemberAccess(access) => { let span = access.span; // Compile member access to temp and push let result_opt = self.expression( Spanned { node: Expression::MemberAccess(access), span: Span { start_col: 0, end_col: 0, start_line: 0, end_line: 0, }, // Dummy span }, &mut stack, )?; if let Some(result) = result_opt { let reg_str = self.resolve_register(&result.location)?; self.write_instruction( Instruction::Push(Operand::Register(reg_str)), Some(span), )?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } else { self.write_instruction( Instruction::Push(Operand::Number(Decimal::from(0))), Some(span), )?; } } _ => { return Err(Error::Unknown( format!( "Attempted to call `{}` with an unsupported argument type", name.node ), Some(name.span), )); } } } // jump to the function and store current line in ra self.write_instruction( Instruction::JumpAndLink(Operand::Label(name.node)), Some(name.span), )?; // cleanup spilled temporary variables let total_stack_usage = stack.stack_offset(); let saved_regs_count = active_registers.len() as u16; if total_stack_usage > saved_regs_count { let spill_amount = total_stack_usage - saved_regs_count; self.write_instruction( Instruction::Sub( Operand::StackPointer, Operand::StackPointer, Operand::Number(spill_amount.into()), ), Some(name.span), )?; } // restore the registers in reverse order from the stack, now using `pop` for register in active_registers.iter().rev() { self.write_instruction( Instruction::Pop(Operand::Register(*register)), Some(name.span), )?; } Ok(()) } fn expression_device( &mut self, expr: DeviceDeclarationExpression<'a>, ) -> Result<(), Error<'a>> { if self.devices.contains_key(&expr.name.node) { self.errors.push(Error::DuplicateIdentifier( expr.name.node.clone(), expr.name.span, )); // We can overwrite or ignore. Let's ignore new declaration to avoid cascading errors? // Actually, for recovery, maybe we want to allow it so subsequent uses work? // But we already have it. return Ok(()); } self.devices.insert(expr.name.node, expr.device); Ok(()) } fn expression_if( &mut self, expr: IfExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let end_label = self.next_label_name(); let else_label = if expr.else_branch.is_some() { self.next_label_name() } else { end_label.clone() }; let cond_span = expr.condition.span; // Compile Condition let (cond, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE (0), jump to else_label self.write_instruction( Instruction::BranchEqZero(cond, Operand::Label(else_label.clone())), Some(cond_span), )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } // Compile Body // Scope variables in body are ephemeral to the block, handled by expression_block self.expression_block(expr.body.node, scope)?; // If we have an else branch, we need to jump over it after the 'if' body if let Some(else_branch) = expr.else_branch { self.write_instruction( Instruction::Jump(Operand::Label(end_label.clone())), Some(else_branch.span), )?; self.write_instruction(Instruction::LabelDef(else_label), Some(else_branch.span))?; match else_branch.node { Expression::Block(block) => self.expression_block(block.node, scope)?, Expression::If(if_expr) => self.expression_if(if_expr.node, scope)?, _ => unreachable!("Parser ensures else branch is Block or If"), } } self.write_instruction(Instruction::LabelDef(end_label), Some(expr.body.span))?; Ok(()) } fn expression_loop( &mut self, expr: LoopExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let start_label = self.next_label_name(); let end_label = self.next_label_name(); // Push labels to stack for 'break' and 'continue' self.loop_stack .push((start_label.clone(), end_label.clone())); self.write_instruction( Instruction::LabelDef(start_label.clone()), Some(expr.body.span), )?; // Compile Body self.expression_block(expr.body.node, scope)?; // Jump back to start self.write_instruction( Instruction::Jump(Operand::Label(start_label)), Some(expr.body.span), )?; self.write_instruction(Instruction::LabelDef(end_label), Some(expr.body.span))?; self.loop_stack.pop(); Ok(()) } fn expression_while( &mut self, expr: WhileExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let start_label = self.next_label_name(); let end_label = self.next_label_name(); // Push labels to stack for 'break' and 'continue' self.loop_stack .push((start_label.clone(), end_label.clone())); let span = expr.condition.span; self.write_instruction(Instruction::LabelDef(start_label.clone()), Some(span))?; // Compile Condition let (cond, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE, jump to end self.write_instruction( Instruction::BranchEqZero(cond, Operand::Label(end_label.clone())), Some(span), )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } // Compile Body self.expression_block(expr.body, scope)?; // Jump back to start self.write_instruction(Instruction::Jump(Operand::Label(start_label)), Some(span))?; self.write_instruction(Instruction::LabelDef(end_label), Some(span))?; self.loop_stack.pop(); Ok(()) } fn expression_break(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((_, end_label)) = self.loop_stack.last() { self.write_instruction( Instruction::Jump(Operand::Label(end_label.clone())), Some(span), )?; Ok(()) } else { Err(Error::Unknown( "Break statement outside of loop".into(), None, )) } } fn expression_continue(&mut self, span: Span) -> Result<(), Error<'a>> { if let Some((start_label, _)) = self.loop_stack.last() { self.write_instruction( Instruction::Jump(Operand::Label(start_label.clone())), Some(span), )?; Ok(()) } else { Err(Error::Unknown( "Continue statement outside of loop".into(), None, )) } } fn expression_ternary( &mut self, expr: TernaryExpression<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { let TernaryExpression { condition, true_value, false_value, } = expr; let span = Span { start_line: condition.span.start_line, start_col: condition.span.start_col, end_line: false_value.span.end_line, end_col: false_value.span.end_col, }; let (cond, cond_clean) = self.compile_operand(*condition, scope)?; let (true_val, true_clean) = self.compile_operand(*true_value, scope)?; let (false_val, false_clean) = self.compile_operand(*false_value, scope)?; let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; self.write_instruction( Instruction::Select(Operand::Register(result_reg), cond, true_val, false_val), Some(span), )?; if let Some(clean) = cond_clean { scope.free_temp(clean, None)?; } if let Some(clean) = true_clean { scope.free_temp(clean, None)?; } if let Some(clean) = false_clean { scope.free_temp(clean, None)?; } Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) } /// Helper to resolve a location to a register string (e.g., "r0"). /// Note: This does not handle Stack locations automatically, as they require /// instruction emission to load. Use `compile_operand` for general handling. fn resolve_register(&self, loc: &VariableLocation) -> Result> { match loc { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => Ok(*r), VariableLocation::Constant(_) => Err(Error::Unknown( "Cannot resolve a constant value to register".into(), None, )), VariableLocation::Device(_) => Err(Error::Unknown( "Cannot resolve a device to a register".into(), None, )), VariableLocation::Stack(_) => Err(Error::Unknown( "Cannot resolve Stack location directly to register string without context".into(), None, )), } } /// Compiles an expression and ensures the result is available as a string valid for an /// IC10 operand (either a register "rX" or a literal value "123"). /// If the result was stored in a new temporary register, returns the name of that temp /// so the caller can free it. fn compile_operand( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result<(Operand<'a>, Option>), Error<'a>> { // Optimization for literals if let Expression::Literal(spanned_lit) = &expr.node { if let Literal::Number(n) = spanned_lit.node { return Ok((Operand::Number(n.into()), None)); } if let Literal::Boolean(b) = spanned_lit.node { return Ok((Operand::Number(Decimal::from(if b { 1 } else { 0 })), None)); } if let Literal::String(ref s) = spanned_lit.node { return Ok((Operand::LogicType(s.clone()), None)); } } // Optimization for negated literals used as operands. // E.g., `1 + -2` -> return "-2" string, no register used. if let Expression::Negation(inner) = &expr.node && let Expression::Literal(spanned_lit) = &inner.node && let Literal::Number(n) = spanned_lit.node { return Ok((Operand::Number((-n).into()), None)); } let result_opt = self.expression(expr, scope)?; let result = match result_opt { Some(r) => r, None => { // Expression failed or returned void. Recover with dummy. return Ok((Operand::Register(0), None)); } }; match result.location { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { Ok((Operand::Register(r), result.temp_name)) } VariableLocation::Constant(lit) => match lit { Literal::Number(n) => Ok((Operand::Number(n.into()), None)), Literal::Boolean(b) => Ok((Operand::Number(Number::from(b).into()), None)), Literal::String(s) => Ok((Operand::LogicType(s), None)), }, VariableLocation::Stack(offset) => { // If it's on the stack, we must load it into a temp to use it as an operand let temp_name = self.next_temp_name(); let temp_loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; let temp_reg = self.resolve_register(&temp_loc)?; self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(Decimal::from(offset)), ), None, )?; self.write_instruction( Instruction::Get( Operand::Register(temp_reg), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), None, )?; // If the original result had a temp name (unlikely for Stack, but possible logic), // we technically should free it if it's not needed, but Stack usually implies it's safe there. // We return the NEW temp name to be freed. Ok((Operand::Register(temp_reg), Some(temp_name))) } VariableLocation::Device(d) => Ok((Operand::Device(d), None)), } } fn compile_literal_or_variable( &mut self, val: LiteralOrVariable<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(Operand<'a>, Option>), Error<'a>> { let dummy_span = Span { start_line: 0, start_col: 0, end_line: 0, end_col: 0, }; let expr = match val { LiteralOrVariable::Literal(l) => Expression::Literal(Spanned { node: l, span: dummy_span, }), LiteralOrVariable::Variable(v) => Expression::Variable(v), }; self.compile_operand( Spanned { node: expr, span: dummy_span, }, scope, ) } /// Recursively fold negations of numeric literals, e.g., -5 => 5, -(-5) => 5 fn try_fold_negation(&self, expr: &Expression) -> Option { match expr { // Base case: plain number literal Expression::Literal(lit) => { if let Literal::Number(n) = lit.node { Some(n) } else { None } } // Recursive case: negation of something foldable Expression::Negation(inner) => self.try_fold_negation(&inner.node).map(|n| -n), // Parentheses just pass through Expression::Priority(inner) => self.try_fold_negation(&inner.node), _ => None, } } fn expression_binary( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { fn fold_binary_expression<'a>( expr: &BinaryExpression<'a>, scope: &VariableScope<'a, '_>, ) -> Option { fn number_to_i64(n: Number) -> Option { match n { Number::Integer(i, _) => i64::try_from(i).ok(), Number::Decimal(d, _) => { // Convert decimal to i64 by truncating let int_part = d.trunc(); i64::try_from(int_part.mantissa() / 10_i128.pow(int_part.scale())).ok() } } } fn i64_to_number(i: i64) -> Number { Number::Integer(i as i128, Unit::None) } let (lhs, rhs) = match &expr { BinaryExpression::Add(l, r) | BinaryExpression::Subtract(l, r) | BinaryExpression::Multiply(l, r) | BinaryExpression::Divide(l, r) | BinaryExpression::Exponent(l, r) | BinaryExpression::Modulo(l, r) | BinaryExpression::BitwiseAnd(l, r) | BinaryExpression::BitwiseOr(l, r) | BinaryExpression::BitwiseXor(l, r) | BinaryExpression::LeftShift(l, r) | BinaryExpression::RightShiftArithmetic(l, r) | BinaryExpression::RightShiftLogical(l, r) => { (fold_expression(l, scope)?, fold_expression(r, scope)?) } }; match expr { BinaryExpression::Add(..) => Some(lhs + rhs), BinaryExpression::Subtract(..) => Some(lhs - rhs), BinaryExpression::Multiply(..) => Some(lhs * rhs), BinaryExpression::Divide(..) => Some(lhs / rhs), // Watch out for div by zero panics! BinaryExpression::Modulo(..) => Some(lhs % rhs), BinaryExpression::BitwiseAnd(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int & rhs_int)) } BinaryExpression::BitwiseOr(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int | rhs_int)) } BinaryExpression::BitwiseXor(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int ^ rhs_int)) } BinaryExpression::LeftShift(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int << rhs_int)) } BinaryExpression::RightShiftArithmetic(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int >> rhs_int)) } BinaryExpression::RightShiftLogical(..) => { let lhs_int = number_to_i64(lhs)?; let rhs_int = number_to_i64(rhs)?; Some(i64_to_number(lhs_int >> rhs_int)) } _ => None, // Exponent not handled in compile-time folding } } fn fold_expression<'a>( expr: &Expression<'a>, scope: &VariableScope<'a, '_>, ) -> Option { match expr { // 1. Base Case: It's already a number Expression::Literal(lit) => match lit.node { Literal::Number(n) => Some(n), _ => None, }, // 2. Handle Parentheses: Just recurse deeper Expression::Priority(inner) => fold_expression(&inner.node, scope), // 3. Handle Negation: Recurse, then negate Expression::Negation(inner) => { let val = fold_expression(&inner.node, scope)?; Some(-val) // Requires impl Neg for Number } // 4. Handle Binary Ops: Recurse BOTH sides, then combine Expression::Binary(bin) => fold_binary_expression(&bin.node, scope), // 5. Handle Variable Reference: Check if it's a const Expression::Variable(var_id) => { if let Ok(var_loc) = scope.get_location_of(var_id, None) { if let VariableLocation::Constant(Literal::Number(num)) = var_loc { return Some(num); } } None } // 6. Handle hash() syscall - evaluates to a constant at compile time Expression::Syscall(Spanned { node: SysCall::System(System::Hash(Spanned { node: Literal::String(str_to_hash), .. })), .. }) => { return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None)); } // 7. Handle hash() macro as invocation - evaluates to a constant at compile time Expression::Invocation(inv) => { if inv.node.name.node == "hash" && inv.node.arguments.len() == 1 { if let Expression::Literal(Spanned { node: Literal::String(str_to_hash), .. }) = &inv.node.arguments[0].node { // hash() takes a string literal and returns a signed integer return Some(Number::Integer(crc_hash_signed(str_to_hash), Unit::None)); } } None } // 8. Anything else cannot be compile-time folded _ => None, } } if let Some(const_lit) = fold_binary_expression(&expr.node, scope) { return Ok(CompileLocation { location: VariableLocation::Constant(Literal::Number(const_lit)), temp_name: None, }); }; #[allow(clippy::type_complexity)] let (op_instr, left_expr, right_expr): ( fn(Operand<'a>, Operand<'a>, Operand<'a>) -> Instruction<'a>, Box>>, Box>>, ) = match expr.node { BinaryExpression::Add(l, r) => { (|into, lhs, rhs| Instruction::Add(into, lhs, rhs), l, r) } BinaryExpression::Multiply(l, r) => { (|into, lhs, rhs| Instruction::Mul(into, lhs, rhs), l, r) } BinaryExpression::Divide(l, r) => { (|into, lhs, rhs| Instruction::Div(into, lhs, rhs), l, r) } BinaryExpression::Subtract(l, r) => { (|into, lhs, rhs| Instruction::Sub(into, lhs, rhs), l, r) } BinaryExpression::Exponent(l, r) => { (|into, lhs, rhs| Instruction::Pow(into, lhs, rhs), l, r) } BinaryExpression::Modulo(l, r) => { (|into, lhs, rhs| Instruction::Mod(into, lhs, rhs), l, r) } BinaryExpression::BitwiseAnd(l, r) => { (|into, lhs, rhs| Instruction::And(into, lhs, rhs), l, r) } BinaryExpression::BitwiseOr(l, r) => { (|into, lhs, rhs| Instruction::Or(into, lhs, rhs), l, r) } BinaryExpression::BitwiseXor(l, r) => { (|into, lhs, rhs| Instruction::Xor(into, lhs, rhs), l, r) } BinaryExpression::LeftShift(l, r) => { (|into, lhs, rhs| Instruction::Sll(into, lhs, rhs), l, r) } BinaryExpression::RightShiftArithmetic(l, r) => { (|into, lhs, rhs| Instruction::Sra(into, lhs, rhs), l, r) } BinaryExpression::RightShiftLogical(l, r) => { (|into, lhs, rhs| Instruction::Srl(into, lhs, rhs), l, r) } }; let span = Self::merge_spans(left_expr.span, right_expr.span); // Compile LHS let (lhs, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS let (rhs, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; // Allocate result register let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs self.write_instruction( op_instr(Operand::Register(result_reg), lhs, rhs), Some(span), )?; // Clean up operand temps Self::cleanup_temps(scope, &[lhs_cleanup, rhs_cleanup])?; Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) } fn expression_logical( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { match expr.node { LogicalExpression::Not(inner) => { let span = inner.span; let (inner_str, cleanup) = self.compile_operand(*inner, scope)?; let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; // seq rX rY 0 => if rY == 0 set rX = 1 else rX = 0 self.write_instruction( Instruction::SetEq( Operand::Register(result_reg), inner_str, Operand::Number(0.into()), ), Some(span), )?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) } _ => { #[allow(clippy::type_complexity)] let (op_instr, left_expr, right_expr): ( fn(Operand<'a>, Operand<'a>, Operand<'a>) -> Instruction<'a>, Box>>, Box>>, ) = match expr.node { LogicalExpression::And(l, r) => { (|into, lhs, rhs| Instruction::And(into, lhs, rhs), l, r) } LogicalExpression::Or(l, r) => { (|into, lhs, rhs| Instruction::Or(into, lhs, rhs), l, r) } LogicalExpression::Equal(l, r) => { (|into, lhs, rhs| Instruction::SetEq(into, lhs, rhs), l, r) } LogicalExpression::NotEqual(l, r) => { (|into, lhs, rhs| Instruction::SetNe(into, lhs, rhs), l, r) } LogicalExpression::GreaterThan(l, r) => { (|into, lhs, rhs| Instruction::SetGt(into, lhs, rhs), l, r) } LogicalExpression::GreaterThanOrEqual(l, r) => { (|into, lhs, rhs| Instruction::SetGe(into, lhs, rhs), l, r) } LogicalExpression::LessThan(l, r) => { (|into, lhs, rhs| Instruction::SetLt(into, lhs, rhs), l, r) } LogicalExpression::LessThanOrEqual(l, r) => { (|into, lhs, rhs| Instruction::SetLe(into, lhs, rhs), l, r) } LogicalExpression::Not(_) => unreachable!(), }; let span = Self::merge_spans(left_expr.span, right_expr.span); // Compile LHS let (lhs, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS let (rhs, rhs_cleanup) = self.compile_operand(*right_expr, scope)?; // Allocate result register let result_name = self.next_temp_name(); let result_loc = scope.add_variable(result_name.clone(), LocationRequest::Temp, None)?; let result_reg = self.resolve_register(&result_loc)?; // Emit instruction: op result lhs rhs self.write_instruction( op_instr(Operand::Register(result_reg), lhs, rhs), Some(span), )?; // Clean up operand temps Self::cleanup_temps(scope, &[lhs_cleanup, rhs_cleanup])?; Ok(CompileLocation { location: result_loc, temp_name: Some(result_name), }) } } } fn expression_block<'v>( &mut self, mut expr: BlockExpression<'a>, parent_scope: &'v mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { fn get_expression_priority<'a>(expr: &Spanned>) -> u32 { match expr.node { Expression::ConstDeclaration(_) => 0, Expression::DeviceDeclaration(_) => 1, Expression::Function(_) => 2, _ => 3, } } // First, sort the expressions to ensure functions are hoisted expr.0.sort_by(|a, b| { let a_cost = get_expression_priority(a); let b_cost = get_expression_priority(b); a_cost.cmp(&b_cost) }); let mut scope = VariableScope::scoped(parent_scope); for expr in expr.0 { if !self.declared_main && !matches!( expr.node, Expression::Function(_) | Expression::ConstDeclaration(_) | Expression::DeviceDeclaration(_) ) && !parent_scope.has_parent() { self.write_instruction(Instruction::LabelDef(Cow::from("main")), Some(expr.span))?; self.declared_main = true; } match expr.node { Expression::Return(ret_expr) => { self.expression_return(ret_expr, &mut scope)?; } _ => { // Swallow errors within expressions so block can continue if let Err(e) = self.expression(expr, &mut scope).and_then(|result| { // If the expression was a statement that returned a temp result (e.g. `1 + 2;` line), // we must free it to avoid leaking registers. if let Some(comp_res) = result && let Some(name) = comp_res.temp_name { scope.free_temp(name, None)?; } Ok(()) }) { self.errors.push(e); } } } } if scope.stack_offset() > 0 { self.write_instruction( Instruction::Sub( Operand::StackPointer, Operand::StackPointer, Operand::Number(scope.stack_offset().into()), ), None, )?; } Ok(()) } /// Takes the result of the expression and stores it in VariableScope::RETURN_REGISTER fn expression_return( &mut self, expr: Option>>>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { if let Some(expr) = expr { let span = expr.span; if let Expression::Negation(neg_expr) = &expr.node && let Expression::Literal(spanned_lit) = &neg_expr.node && let Literal::Number(neg_num) = &spanned_lit.node { let loc = VariableLocation::Persistant(VariableScope::RETURN_REGISTER); self.emit_variable_assignment(&loc, Operand::Number((-*neg_num).into()))?; return Ok(loc); }; match expr.node { Expression::Variable(var_name) => { match scope.get_location_of(&var_name.node, Some(var_name.span)) { Ok(loc) => match loc { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(reg), ), Some(span), )?; } VariableLocation::Constant(lit) => { let op = extract_literal(lit, false)?; self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), op, ), Some(span), )?; } VariableLocation::Stack(offset) => { self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(offset.into()), ), Some(span), )?; self.write_instruction( Instruction::Get( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; } VariableLocation::Device(_) => { return Err(Error::Unknown( "You can not return a device from a function.".into(), Some(var_name.span), )); } }, Err(_) => { self.errors.push(Error::UnknownIdentifier( var_name.node.clone(), var_name.span, )); // Proceed with dummy } } } Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { self.emit_variable_assignment( &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), Operand::Number(num.into()), )?; } Literal::Boolean(b) => { self.emit_variable_assignment( &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), Operand::Number(Number::from(b).into()), )?; } _ => {} }, Expression::Binary(bin_expr) => { let span = bin_expr.span; let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(result_reg), ), Some(span), )?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::Logical(log_expr) => { let span = log_expr.span; let result = self.expression_logical(log_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(result_reg), ), Some(span), )?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::MemberAccess(access) => { let span = access.span; // Return result of member access let res_opt = self.expression( Spanned { node: Expression::MemberAccess(access), span: expr.span, }, scope, )?; if let Some(res) = res_opt { let reg = self.resolve_register(&res.location)?; self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(reg), ), Some(span), )?; if let Some(temp) = res.temp_name { scope.free_temp(temp, Some(span))?; } } } Expression::Tuple(tuple_expr) => { let span = expr.span; let tuple_elements = tuple_expr.node; let tuple_size = tuple_elements.len(); // Push each tuple element onto the stack using compile_operand for element in tuple_elements.into_iter() { let (push_operand, cleanup) = self.compile_operand(element, scope)?; self.write_instruction(Instruction::Push(push_operand), Some(span))?; // Don't track the push in the scope's stack offset because these values // are being returned to the caller, not allocated in this block's scope. // They will be left on the stack when we return. if let Some(temp_name) = cleanup { scope.free_temp(temp_name, Some(span))?; } } // Load the saved SP from stack and move to r15 for caller's stack unwinding if let Some(sp_var_name) = &self.function_meta.sp_backup_var { let sp_var_loc = scope.get_location_of(sp_var_name, Some(span))?; if let VariableLocation::Stack(offset) = sp_var_loc { // Calculate address of saved SP, accounting for tuple values just pushed let adjusted_offset = offset + tuple_size as u16; self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(adjusted_offset.into()), ), Some(span), )?; // Load saved SP value self.write_instruction( Instruction::Get( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; // Move to r15 for caller self.write_instruction( Instruction::Move( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; } } // Record the tuple return size for validation at call sites if let Some(func_name) = &self.function_meta.current_name { self.function_meta .tuple_return_sizes .insert(func_name.clone(), tuple_size); } // Track tuple size for epilogue cleanup self.function_meta.tuple_return_size = tuple_size as u16; } _ => { return Err(Error::Unknown( format!("Unsupported `return` statement: {:?}", expr), None, )); } } } if let Some(label) = &self.function_meta.return_label { self.write_instruction(Instruction::Jump(Operand::Label(label.clone())), None)?; } else { return Err(Error::Unknown( "Return statement used outside of function context.".into(), None, )); } Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)) } // syscalls that return values will be stored in the VariableScope::RETURN_REGISTER // register fn expression_syscall_system( &mut self, expr: System<'a>, span: Span, scope: &mut VariableScope<'a, '_>, ) -> Result>, Error<'a>> { macro_rules! cleanup { ($($to_clean:expr),*) => { $( if let Some(to_clean) = $to_clean { scope.free_temp(to_clean, None)?; } )* }; } match expr { System::Yield => { self.write_instruction(Instruction::Yield, Some(span))?; Ok(None) } System::Sleep(amt) => { let (op, var_cleanup) = self.compile_operand(*amt, scope)?; self.write_instruction(Instruction::Sleep(op), Some(span))?; cleanup!(var_cleanup); Ok(None) } System::Clr(device) => { let (op, var_cleanup) = self.compile_operand(*device, scope)?; self.write_instruction(Instruction::Clr(op), Some(span))?; cleanup!(var_cleanup); Ok(None) } System::Hash(hash_arg) => { let Spanned { node: Literal::String(str_lit), .. } = hash_arg else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a string literal.".into(), span, )); }; let loc = VariableLocation::Constant(Literal::Number(Number::Integer( crc_hash_signed(&str_lit), Unit::None, ))); Ok(Some(CompileLocation { location: loc, temp_name: None, })) } System::SetOnDevice(device, logic_type, variable) => { let (variable, var_cleanup) = self.compile_operand(*variable, scope)?; let Spanned { node: LiteralOrVariable::Variable(device_spanned), .. } = device else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, )); }; let device_name = device_spanned.node; if !self.devices.contains_key(&device_name) { self.errors.push(Error::InvalidDevice( device_name.clone(), device_spanned.span, )); } let device_val = self .devices .get(&device_name) .cloned() .unwrap_or(Cow::from("d0")); let Spanned { node: Literal::String(logic_type), .. } = logic_type else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, )); }; self.write_instruction( Instruction::Store( Operand::Device(device_val), Operand::LogicType(logic_type), variable, ), Some(span), )?; cleanup!(var_cleanup); Ok(None) } System::SetOnDeviceBatched(device_hash, logic_type, variable) => { let (var, var_cleanup) = self.compile_operand(*variable, scope)?; let (device_hash_val, device_hash_cleanup) = self.compile_literal_or_variable(device_hash.node, scope)?; let Spanned { node: Literal::String(logic_type), .. } = logic_type else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, )); }; self.write_instruction( Instruction::StoreBatch(device_hash_val, Operand::LogicType(logic_type), var), Some(span), )?; cleanup!(var_cleanup, device_hash_cleanup); Ok(None) } System::SetOnDeviceBatchedNamed(device_hash, name_hash, logic_type, val_expr) => { let (value, value_cleanup) = self.compile_operand(*val_expr, scope)?; let (device_hash, device_hash_cleanup) = self.compile_literal_or_variable(device_hash.node, scope)?; let (name_hash, name_hash_cleanup) = self.compile_literal_or_variable(name_hash.node, scope)?; let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; self.write_instruction( Instruction::StoreBatchNamed(device_hash, name_hash, logic_type, value), Some(span), )?; cleanup!( value_cleanup, device_hash_cleanup, name_hash_cleanup, logic_type_cleanup ); Ok(None) } System::LoadFromDevice(device, logic_type) => { let Spanned { node: LiteralOrVariable::Variable(device_spanned), .. } = device else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, )); }; let device_name = device_spanned.node; if !self.devices.contains_key(&device_name) { self.errors.push(Error::InvalidDevice( device_name.clone(), device_spanned.span, )); } let device_val = self .devices .get(&device_name) .cloned() .unwrap_or(Cow::from("d0")); let Spanned { node: Literal::String(logic_type), .. } = logic_type else { return Err(Error::AgrumentMismatch( "Arg2 expected to be a string".into(), span, )); }; self.write_instruction( Instruction::Load( Operand::Register(VariableScope::RETURN_REGISTER), Operand::Device(device_val), Operand::LogicType(logic_type), ), Some(span), )?; Ok(Some(CompileLocation { location: VariableLocation::Temporary(VariableScope::RETURN_REGISTER), temp_name: None, })) } System::LoadBatch(device_hash, logic_type, batch_mode) => { let (device_hash, device_hash_cleanup) = self.compile_literal_or_variable(device_hash.node, scope)?; let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; let (batch_mode, batch_mode_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(batch_mode.node), scope, )?; self.write_instruction( Instruction::LoadBatch( Operand::Register(VariableScope::RETURN_REGISTER), device_hash, logic_type, batch_mode, ), Some(span), )?; cleanup!(device_hash_cleanup, logic_type_cleanup, batch_mode_cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } System::LoadBatchNamed(device_hash, name_hash, logic_type, batch_mode) => { let (device_hash, device_hash_cleanup) = self.compile_literal_or_variable(device_hash.node, scope)?; let (name_hash, name_hash_cleanup) = self.compile_literal_or_variable(name_hash.node, scope)?; let (logic_type, logic_type_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; let (batch_mode, batch_mode_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(batch_mode.node), scope, )?; self.write_instruction( Instruction::LoadBatchNamed( Operand::Register(VariableScope::RETURN_REGISTER), device_hash, name_hash, logic_type, batch_mode, ), Some(span), )?; cleanup!( device_hash_cleanup, name_hash_cleanup, logic_type_cleanup, batch_mode_cleanup ); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } System::LoadSlot(dev_name, slot_index, logic_type) => { let (dev_hash, hash_cleanup) = self.compile_literal_or_variable(dev_name.node, scope)?; let (slot_index, slot_cleanup) = self.compile_operand(*slot_index, scope)?; let (logic_type, logic_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; self.write_instruction( Instruction::LoadSlot( Operand::Register(VariableScope::RETURN_REGISTER), dev_hash, slot_index, logic_type, ), Some(span), )?; cleanup!(hash_cleanup, slot_cleanup, logic_cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } System::SetSlot(dev_name, slot_index, logic_type, var) => { let (dev_name, name_cleanup) = self.compile_literal_or_variable(dev_name.node, scope)?; let (slot_index, index_cleanup) = self.compile_operand(*slot_index, scope)?; let (logic_type, type_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; let (var, var_cleanup) = self.compile_operand(*var, scope)?; self.write_instruction( Instruction::StoreSlot(dev_name, slot_index, logic_type, var), Some(span), )?; cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup); Ok(None) } System::LoadReagent(device, reagent_mode, reagent_hash) => { let Spanned { node: LiteralOrVariable::Variable(device_spanned), .. } = device else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, )); }; let (device, device_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Variable(device_spanned), scope, )?; let (reagent_mode, reagent_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(reagent_mode.node), scope, )?; let (reagent_hash, reagent_hash_cleanup) = self.compile_operand(*reagent_hash, scope)?; self.write_instruction( Instruction::LoadReagent( Operand::Register(VariableScope::RETURN_REGISTER), device, reagent_mode, reagent_hash, ), Some(span), )?; cleanup!(reagent_cleanup, reagent_hash_cleanup, device_cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } System::Rmap(device, reagent_hash) => { let Spanned { node: LiteralOrVariable::Variable(device_spanned), .. } = device else { return Err(Error::AgrumentMismatch( "Arg1 expected to be a variable".into(), span, )); }; let (device, device_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Variable(device_spanned), scope, )?; let (reagent_hash, reagent_hash_cleanup) = self.compile_operand(*reagent_hash, scope)?; self.write_instruction( Instruction::Rmap( Operand::Register(VariableScope::RETURN_REGISTER), device, reagent_hash, ), Some(span), )?; cleanup!(reagent_hash_cleanup, device_cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } } } fn expression_syscall_math( &mut self, expr: Math<'a>, span: Span, scope: &mut VariableScope<'a, '_>, ) -> Result>, Error<'a>> { macro_rules! cleanup { ($($to_clean:expr),*) => { $( if let Some(to_clean) = $to_clean { scope.free_temp(to_clean, None)?; } )* }; } match expr { Math::Acos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Acos(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Asin(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Asin(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Atan(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Atan(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Atan2(expr1, expr2) => { let (var1, var1_cleanup) = self.compile_operand(*expr1, scope)?; let (var2, var2_cleanup) = self.compile_operand(*expr2, scope)?; self.write_instruction( Instruction::Atan2( Operand::Register(VariableScope::RETURN_REGISTER), var1, var2, ), Some(span), )?; cleanup!(var1_cleanup, var2_cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Abs(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Abs(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Ceil(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Ceil(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Cos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Cos(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Floor(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Floor(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Log(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Log(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(cleanup); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Max(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; self.write_instruction( Instruction::Max( Operand::Register(VariableScope::RETURN_REGISTER), var1, var2, ), Some(span), )?; cleanup!(clean1, clean2); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Min(expr1, expr2) => { let (var1, clean1) = self.compile_operand(*expr1, scope)?; let (var2, clean2) = self.compile_operand(*expr2, scope)?; self.write_instruction( Instruction::Min( Operand::Register(VariableScope::RETURN_REGISTER), var1, var2, ), Some(span), )?; cleanup!(clean1, clean2); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Rand => { self.write_instruction( Instruction::Rand(Operand::Register(VariableScope::RETURN_REGISTER)), Some(span), )?; Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sin(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Sin(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(clean); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sqrt(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Sqrt(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(clean); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Tan(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Tan(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(clean); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Trunc(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_instruction( Instruction::Trunc(Operand::Register(VariableScope::RETURN_REGISTER), var), Some(span), )?; cleanup!(clean); Ok(Some(CompileLocation { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } } } /// Compile a function declaration. /// Calees are responsible for backing up any registers they wish to use. fn expression_function( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let FunctionExpression { name, arguments, body, } = expr.node; let span = expr.span; if self.function_meta.locations.contains_key(&name.node) { self.errors .push(Error::DuplicateIdentifier(name.node.clone(), name.span)); // Fallthrough to allow compiling the body anyway? // It might be useful to check body for errors. } self.function_meta.params.insert( name.node.clone(), arguments.iter().map(|a| a.node.clone()).collect(), ); // Set the current function being compiled self.function_meta.current_name = Some(name.node.clone()); // Declare the function as a line identifier self.write_instruction(Instruction::LabelDef(name.node.clone()), Some(span))?; self.function_meta .locations .insert(name.node.clone(), self.current_line); // Create a new block scope for the function body let mut block_scope = VariableScope::scoped(scope); let mut saved_variables = 0; // do a reverse pass to pop variables from the stack and put them into registers for var_name in arguments .iter() .rev() .take(VariableScope::PERSIST_REGISTER_COUNT as usize) { let loc = block_scope.add_variable( var_name.node.clone(), LocationRequest::Persist, Some(var_name.span), )?; // we don't need to imcrement the stack offset as it's already on the stack from the // previous scope match loc { VariableLocation::Persistant(loc) => { self.write_instruction( Instruction::Pop(Operand::Register(loc)), Some(var_name.span), )?; } VariableLocation::Stack(_) => { return Err(Error::Unknown( "Attempted to save to stack without tracking in scope".into(), Some(var_name.span), )); } _ => { return Err(Error::Unknown( "Attempted to return a Temporary scoped variable from a Persistant request" .into(), Some(var_name.span), )); } } saved_variables += 1; } // now do a forward pass in case we have spilled into the stack. We don't need to push // anything as they already exist on the stack, but we DO need to let our block_scope be // aware that the variables exist on the stack (left to right) for var_name in arguments.iter().take(arguments.len() - saved_variables) { block_scope.add_variable( var_name.node.clone(), LocationRequest::Stack, Some(var_name.span), )?; } // Save the caller's stack pointer FIRST (before any pushes modify it) // This is crucial for proper stack unwinding in tuple returns let sp_backup_name = self.next_temp_name(); block_scope.add_variable( sp_backup_name.clone(), LocationRequest::Stack, Some(name.span), )?; self.write_instruction(Instruction::Push(Operand::StackPointer), Some(span))?; self.function_meta.sp_backup_var = Some(sp_backup_name); self.function_meta.sp_saved = true; // Generate return label name and track it before pushing ra let return_label = self.next_label_name(); let prev_return_label = self .function_meta .return_label .replace(return_label.clone()); block_scope.add_variable( return_label.clone(), LocationRequest::Stack, Some(name.span), )?; self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?; for expr in body.0 { match expr.node { Expression::Return(ret_expr) => { self.expression_return(ret_expr, &mut block_scope)?; } _ => { // Swallow internal errors if let Err(e) = self.expression(expr, &mut block_scope).and_then(|result| { if let Some(comp_res) = result && let Some(name) = comp_res.temp_name { block_scope.free_temp(name, None)?; } Ok(()) }) { self.errors.push(e); } } } } // Get the saved return address and save it back into `ra` let ra_res = block_scope.get_location_of(&return_label, Some(name.span)); let ra_stack_offset = match ra_res { Ok(VariableLocation::Stack(offset)) => { block_scope.free_temp(return_label.clone(), None)?; offset } _ => { // If we can't find RA, we can't return properly. // This usually implies a compiler bug or scope tracking error. return Err(Error::Unknown( "Stored return address not in stack as expected".into(), Some(name.span), )); } }; self.function_meta.return_label = prev_return_label; // Write the return label and epilogue self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; // Handle stack cleanup based on whether this is a tuple-returning function let is_tuple_return = self.function_meta.tuple_return_size > 0; // For tuple returns, account for tuple values pushed onto the stack let adjusted_ra_offset = if is_tuple_return { ra_stack_offset + self.function_meta.tuple_return_size as u16 } else { ra_stack_offset }; // Load return address from stack if adjusted_ra_offset == 1 && !is_tuple_return { // Simple case: RA is at top, and we're not returning a tuple // Just pop ra, then pop sp to restore self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; self.write_instruction(Instruction::Pop(Operand::StackPointer), Some(span))?; } else { // RA is deeper in stack, or we're returning a tuple // Load ra from offset self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(adjusted_ra_offset.into()), ), Some(span), )?; self.write_instruction( Instruction::Get( Operand::ReturnAddress, Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; if !is_tuple_return { // Non-tuple return: restore SP from saved value to clean up let sp_offset = adjusted_ra_offset - 1; self.write_instruction( Instruction::Sub( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, Operand::Number(sp_offset.into()), ), Some(span), )?; self.write_instruction( Instruction::Get( Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::Device(Cow::from("db")), Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; self.write_instruction( Instruction::Move( Operand::StackPointer, Operand::Register(VariableScope::TEMP_STACK_REGISTER), ), Some(span), )?; } // else: Tuple return - leave tuple values on stack for caller to pop } self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; // Reset the flags for the next function self.function_meta.tuple_return_size = 0; self.function_meta.sp_saved = false; self.function_meta.sp_backup_var = None; self.function_meta.current_name = None; Ok(()) } }