#![allow(clippy::result_large_err)] use crate::variable_manager::{self, LocationRequest, VariableLocation, VariableScope}; use helpers::prelude::*; use parser::{ Parser as ASTParser, sys_call::{Math, SysCall, System}, tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, ConstDeclarationExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, LiteralOr, LiteralOrVariable, LogicalExpression, LoopExpression, MemberAccessExpression, Span, Spanned, TernaryExpression, WhileExpression, }, }; use std::{ borrow::Cow, collections::HashMap, io::{BufWriter, Write}, }; use thiserror::Error; use tokenizer::token::Number; macro_rules! debug { ($self: expr, $debug_value: expr) => { if $self.config.debug { format!($debug_value) } else { "".into() } }; ($self: expr, $debug_value: expr, $args: expr) => { if $self.config.debug { format!($debug_value, $args) } else { "".into() } }; } 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) => s, Literal::Number(n) => Cow::from(n.to_string()), Literal::Boolean(b) => Cow::from(if b { "1" } else { "0" }), }) } #[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("{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) => 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 CompilationResult<'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 Compiler<'a, 'w, W: std::io::Write> { pub parser: ASTParser<'a>, function_locations: HashMap, usize>, function_metadata: HashMap, Vec>>, devices: HashMap, Cow<'a, str>>, output: &'w mut BufWriter, 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) current_return_label: Option>, pub errors: Vec>, } impl<'a, 'w, W: std::io::Write> Compiler<'a, 'w, W> { pub fn new( parser: ASTParser<'a>, writer: &'w mut BufWriter, config: Option, ) -> Self { Self { parser, function_locations: HashMap::new(), function_metadata: HashMap::new(), devices: HashMap::new(), output: writer, current_line: 1, declared_main: false, config: config.unwrap_or_default(), temp_counter: 0, label_counter: 0, loop_stack: Vec::new(), current_return_label: None, errors: Vec::new(), } } pub fn compile(mut self) -> Vec> { 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 self.errors, Err(e) => { // Should be covered by parser.errors, but just in case self.errors.push(Error::Parse(e)); return self.errors; } }; // 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_output("j main") { self.errors.push(e); return self.errors; } 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); } self.errors } fn write_output(&mut self, output: impl Into) -> Result<(), Error<'a>> { self.output.write_all(output.into().as_bytes())?; self.output.write_all(b"\n")?; self.current_line += 1; 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!("L{}", self.label_counter)) } 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), .. }) => self.expression_syscall_math(math, scope), Expression::While(expr_while) => { self.expression_while(expr_while.node, scope)?; Ok(None) } Expression::Break(_) => { self.expression_break()?; Ok(None) } Expression::Continue(_) => { self.expression_continue()?; 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) => { 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_name.clone(), &temp_loc, Cow::from(format!("r{}", VariableScope::RETURN_REGISTER)), )?; Ok(Some(CompilationResult { 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( temp_name.clone(), &loc, Cow::from(num.to_string()), )?; Ok(Some(CompilationResult { location: loc, temp_name: Some(temp_name), })) } Literal::Boolean(b) => { let val = if b { "1" } else { "0" }; let temp_name = self.next_temp_name(); let loc = scope.add_variable(temp_name.clone(), LocationRequest::Temp, None)?; self.emit_variable_assignment(temp_name.clone(), &loc, Cow::from(val))?; Ok(Some(CompilationResult { 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(CompilationResult { 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(CompilationResult { location: VariableLocation::Device(device.clone()), temp_name: None, })) } else { self.errors .push(Error::UnknownIdentifier(name.node.clone(), name.span)); Ok(Some(CompilationResult { 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_str, 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_output(format!("l {} {} {}", reg, device_str, member.node))?; // 4. Cleanup if let Some(c) = cleanup { scope.free_temp(c, None)?; } Ok(Some(CompilationResult { 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_output(format!("sub {result_reg} 0 {inner_str}"))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } Ok(Some(CompilationResult { location: result_loc, temp_name: Some(result_name), })) } _ => 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<(Cow<'a, str>, 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((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, var_name: Cow<'a, str>, location: &VariableLocation<'a>, source_value: Cow<'a, str>, ) -> Result<(), Error<'a>> { let debug_tag = if self.config.debug { format!(" #{var_name}") } else { String::new() }; match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_output(format!("move r{reg} {}{debug_tag}", source_value))?; } VariableLocation::Stack(_) => { self.write_output(format!("push {}{debug_tag}", source_value))?; } 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 if let Expression::Negation(box_expr) = &expr.node && let Expression::Literal(spanned_lit) = &box_expr.node && let Literal::Number(neg_num) = &spanned_lit.node { let loc = scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?; self.emit_variable_assignment( name_str.clone(), &loc, Cow::from(format!("-{neg_num}")), )?; return Ok(Some(CompilationResult { 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( name_str.clone(), &var_location, Cow::from(num.to_string()), )?; (var_location, None) } Literal::Boolean(b) => { let val = if b { "1" } else { "0" }; let var_location = scope.add_variable( name_str.clone(), LocationRequest::Persist, Some(name_span), )?; self.emit_variable_assignment(name_str, &var_location, Cow::from(val))?; (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( name_str, &loc, Cow::from(format!("r{}", 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, 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( name_str, &loc, Cow::from(format!("r{}", 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 CompilationResult { location: VariableLocation::Constant(Literal::Number(num)), .. } = result { self.emit_variable_assignment(name_str, &var_loc, Cow::from(num.to_string()))?; (var_loc, None) } else { // Move result from temp to new persistent variable let result_reg = self.resolve_register(&result.location)?; self.emit_variable_assignment(name_str, &var_loc, 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(name_str, &var_loc, 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_str = match src_loc { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { format!("r{r}") } VariableLocation::Stack(offset) => { self.write_output(format!( "sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER ))?; format!("r{}", VariableScope::TEMP_STACK_REGISTER) } VariableLocation::Constant(_) | VariableLocation::Device(_) => unreachable!(), }; self.emit_variable_assignment(name_str, &var_loc, Cow::from(src_str))?; (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(name_str, &var_loc, 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)?; println!("{res:?}"); 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(name_str, &var_loc, res_register)?; if let Some(name) = res.temp_name { scope.free_temp(name, None)?; } (var_loc, None) } _ => { return Err(Error::Unknown( format!("`{name_str}` declaration of this type is not supported/implemented."), Some(name_span), )); } }; Ok(Some(CompilationResult { 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))), 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(CompilationResult { 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; 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_str, cleanup) = self.compile_operand(*expression, scope)?; let debug_tag = if self.config.debug { format!(" #{}", identifier.node) } else { String::new() }; match location { VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => { self.write_output(format!("move r{reg} {val_str}{debug_tag}"))?; } VariableLocation::Stack(offset) => { // Calculate address: sp - offset self.write_output(format!( "sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER ))?; // Store value to stack/db at address self.write_output(format!( "put db r{0} {val_str}{debug_tag}", VariableScope::TEMP_STACK_REGISTER ))?; } 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_str, dev_cleanup) = self.resolve_device(*object, scope)?; let (val_str, val_cleanup) = self.compile_operand(*expression, scope)?; self.write_output(format!("s {} {} {}", device_str, member.node, val_str))?; if let Some(c) = dev_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( &mut self, invoke_expr: Spanned>, parent_scope: &mut VariableScope<'a, '_>, ) -> Result<(), Error<'a>> { let InvocationExpression { name, arguments } = invoke_expr.node; if !self.function_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_metadata.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_output(format!("push r{register}"))?; } for arg in arguments { match arg.node { Expression::Literal(spanned_lit) => match spanned_lit.node { Literal::Number(num) => { let num_str = num.to_string(); self.write_output(format!("push {num_str}"))?; } Literal::Boolean(b) => { let val = if b { "1" } else { "0" }; self.write_output(format!("push {val}"))?; } _ => {} }, 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_output(format!("push r{reg}"))?; } VariableLocation::Constant(lit) => { self.write_output(format!("push {}", extract_literal(lit, false)?))?; } VariableLocation::Stack(stack_offset) => { self.write_output(format!( "sub r{0} sp {stack_offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get r{0} db r{0}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "push r{0}", VariableScope::TEMP_STACK_REGISTER ))?; } 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) => { // Compile the binary expression to a temp register let result = self.expression_binary(bin_expr, &mut stack)?; let reg_str = self.resolve_register(&result.location)?; self.write_output(format!("push {reg_str}"))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::Logical(log_expr) => { // Compile the logical expression to a temp register let result = self.expression_logical(log_expr, &mut stack)?; let reg_str = self.resolve_register(&result.location)?; self.write_output(format!("push {reg_str}"))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } Expression::MemberAccess(access) => { // 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_output(format!("push {reg_str}"))?; if let Some(name) = result.temp_name { stack.free_temp(name, None)?; } } else { self.write_output("push 0")?; // Should fail ideally } } _ => { 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_output(format!("jal {}", name.node))?; for register in active_registers { let VariableLocation::Stack(stack_offset) = stack .get_location_of(&Cow::from(format!("temp_{register}")), None) .map_err(Error::Scope)? else { // This shouldn't happen if we just added it return Err(Error::Unknown( format!("Failed to recover temp_{register}"), Some(name.span), )); }; self.write_output(format!( "sub r{0} sp {stack_offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get r{register} db r{0}", VariableScope::TEMP_STACK_REGISTER ))?; } if stack.stack_offset() > 0 { self.write_output(format!("sub sp sp {}", stack.stack_offset()))?; } 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() }; // Compile Condition let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE (0), jump to else_label self.write_output(format!("beq {cond_str} 0 {else_label}"))?; 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 expr.else_branch.is_some() { self.write_output(format!("j {end_label}"))?; self.write_output(format!("{else_label}:"))?; match expr .else_branch .ok_or(Error::Unknown("Missing else branch. This should not happen and indicates a Compiler Error. Please report to the author.".into(), None))? .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_output(format!("{end_label}:"))?; 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_output(format!("{start_label}:"))?; // Compile Body self.expression_block(expr.body.node, scope)?; // Jump back to start self.write_output(format!("j {start_label}"))?; self.write_output(format!("{end_label}:"))?; 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())); self.write_output(format!("{start_label}:"))?; // Compile Condition let (cond_str, cleanup) = self.compile_operand(*expr.condition, scope)?; // If condition is FALSE, jump to end self.write_output(format!("beq {cond_str} 0 {end_label}"))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } // Compile Body self.expression_block(expr.body, scope)?; // Jump back to start self.write_output(format!("j {start_label}"))?; self.write_output(format!("{end_label}:"))?; self.loop_stack.pop(); Ok(()) } fn expression_break(&mut self) -> Result<(), Error<'a>> { if let Some((_, end_label)) = self.loop_stack.last() { self.write_output(format!("j {end_label}"))?; Ok(()) } else { Err(Error::Unknown( "Break statement outside of loop".into(), None, )) } } fn expression_continue(&mut self) -> Result<(), Error<'a>> { if let Some((start_label, _)) = self.loop_stack.last() { self.write_output(format!("j {start_label}"))?; 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 (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_output(format!( "select {} {} {} {}", result_reg, cond, true_val, false_val ))?; 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(CompilationResult { 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, Error<'a>> { match loc { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { Ok(Cow::from(format!("r{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<(Cow<'a, str>, Option>), Error<'a>> { // Optimization for literals if let Expression::Literal(spanned_lit) = &expr.node { if let Literal::Number(n) = spanned_lit.node { return Ok((Cow::from(n.to_string()), None)); } if let Literal::Boolean(b) = spanned_lit.node { return Ok((Cow::from(if b { "1" } else { "0" }), None)); } if let Literal::String(ref s) = spanned_lit.node { return Ok((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((Cow::from(format!("-{}", n)), 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((Cow::from("r0"), None)); } }; match result.location { VariableLocation::Temporary(r) | VariableLocation::Persistant(r) => { Ok((Cow::from(format!("r{r}")), result.temp_name)) } VariableLocation::Constant(lit) => match lit { Literal::Number(n) => Ok((Cow::from(n.to_string()), None)), Literal::Boolean(b) => Ok((Cow::from(if b { "1" } else { "0" }), None)), Literal::String(s) => Ok((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_output(format!( "sub r{0} sp {offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get {temp_reg} db r{0}", VariableScope::TEMP_STACK_REGISTER ))?; // 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((temp_reg, Some(temp_name))) } VariableLocation::Device(d) => Ok((d, None)), } } fn compile_literal_or_variable( &mut self, val: LiteralOrVariable<'a>, scope: &mut VariableScope<'a, '_>, ) -> Result<(Cow<'a, str>, 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, ) } fn expression_binary( &mut self, expr: Spanned>, scope: &mut VariableScope<'a, '_>, ) -> Result, Error<'a>> { fn fold_binary_expression<'a>(expr: &BinaryExpression<'a>) -> Option { 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) => (fold_expression(l)?, fold_expression(r)?), }; 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), _ => None, // Handle Exponent separately or implement pow } } fn fold_expression<'a>(expr: &Expression<'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), // 3. Handle Negation: Recurse, then negate Expression::Negation(inner) => { let val = fold_expression(&inner.node)?; Some(-val) // Requires impl Neg for Number } // 4. Handle Binary Ops: Recurse BOTH sides, then combine Expression::Binary(bin) => fold_binary_expression(&bin.node), // 5. Anything else (Variables, Function Calls) cannot be compile-time folded _ => None, } } if let Some(const_lit) = fold_binary_expression(&expr.node) { return Ok(CompilationResult { location: VariableLocation::Constant(Literal::Number(const_lit)), temp_name: None, }); }; let (op_str, left_expr, right_expr) = match expr.node { BinaryExpression::Add(l, r) => ("add", l, r), BinaryExpression::Multiply(l, r) => ("mul", l, r), BinaryExpression::Divide(l, r) => ("div", l, r), BinaryExpression::Subtract(l, r) => ("sub", l, r), BinaryExpression::Exponent(l, r) => ("pow", l, r), BinaryExpression::Modulo(l, r) => ("mod", l, r), }; // Compile LHS let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS let (rhs_str, 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_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; // Clean up operand temps if let Some(name) = lhs_cleanup { scope.free_temp(name, None)?; } if let Some(name) = rhs_cleanup { scope.free_temp(name, None)?; } Ok(CompilationResult { 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 (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_output(format!("seq {result_reg} {inner_str} 0"))?; if let Some(name) = cleanup { scope.free_temp(name, None)?; } Ok(CompilationResult { location: result_loc, temp_name: Some(result_name), }) } _ => { let (op_str, left_expr, right_expr) = match expr.node { LogicalExpression::And(l, r) => ("and", l, r), LogicalExpression::Or(l, r) => ("or", l, r), LogicalExpression::Equal(l, r) => ("seq", l, r), LogicalExpression::NotEqual(l, r) => ("sne", l, r), LogicalExpression::GreaterThan(l, r) => ("sgt", l, r), LogicalExpression::GreaterThanOrEqual(l, r) => ("sge", l, r), LogicalExpression::LessThan(l, r) => ("slt", l, r), LogicalExpression::LessThanOrEqual(l, r) => ("sle", l, r), LogicalExpression::Not(_) => unreachable!(), }; // Compile LHS let (lhs_str, lhs_cleanup) = self.compile_operand(*left_expr, scope)?; // Compile RHS let (rhs_str, 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_output(format!("{op_str} {result_reg} {lhs_str} {rhs_str}"))?; // Clean up operand temps if let Some(name) = lhs_cleanup { scope.free_temp(name, None)?; } if let Some(name) = rhs_cleanup { scope.free_temp(name, None)?; } Ok(CompilationResult { 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>> { // First, sort the expressions to ensure functions are hoisted expr.0.sort_by(|a, b| { if matches!( b.node, Expression::Function(_) | Expression::ConstDeclaration(_) ) && matches!( a.node, Expression::Function(_) | Expression::ConstDeclaration(_) ) { std::cmp::Ordering::Equal } else if matches!( a.node, Expression::Function(_) | Expression::ConstDeclaration(_) ) { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater } }); let mut scope = VariableScope::scoped(parent_scope); for expr in expr.0 { if !self.declared_main && !matches!(expr.node, Expression::Function(_)) && !parent_scope.has_parent() { self.write_output("main:")?; 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_output(format!("sub sp sp {}", scope.stack_offset()))?; } 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 { 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( Cow::from("returnValue"), &loc, Cow::from(format!("-{neg_num}")), )?; 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_output(format!( "move r{} r{reg} {}", VariableScope::RETURN_REGISTER, debug!(self, "#returnValue") ))?; } VariableLocation::Constant(lit) => { let str = extract_literal(lit, false)?; self.write_output(format!( "move r{} {str} {}", VariableScope::RETURN_REGISTER, debug!(self, "#returnValue") ))? } VariableLocation::Stack(offset) => { self.write_output(format!( "sub r{} sp {offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get r{} db r{}", VariableScope::RETURN_REGISTER, VariableScope::TEMP_STACK_REGISTER ))?; } 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( Cow::from("returnValue"), &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), Cow::from(num.to_string()), )?; } Literal::Boolean(b) => { let val = if b { "1" } else { "0" }; self.emit_variable_assignment( Cow::from("returnValue"), &VariableLocation::Persistant(VariableScope::RETURN_REGISTER), Cow::from(val.to_string()), )?; } _ => {} }, Expression::Binary(bin_expr) => { let result = self.expression_binary(bin_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; self.write_output(format!( "move r{} {}", VariableScope::RETURN_REGISTER, result_reg ))?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::Logical(log_expr) => { let result = self.expression_logical(log_expr, scope)?; let result_reg = self.resolve_register(&result.location)?; self.write_output(format!( "move r{} {}", VariableScope::RETURN_REGISTER, result_reg ))?; if let Some(name) = result.temp_name { scope.free_temp(name, None)?; } } Expression::MemberAccess(access) => { // 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_output(format!( "move r{} {}", VariableScope::RETURN_REGISTER, reg ))?; if let Some(temp) = res.temp_name { scope.free_temp(temp, None)?; } } } _ => { return Err(Error::Unknown( format!("Unsupported `return` statement: {:?}", expr), None, )); } } } if let Some(label) = &self.current_return_label { self.write_output(format!("j {}", label))?; } 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_output("yield")?; Ok(None) } System::Sleep(amt) => { let (var, var_cleanup) = self.compile_operand(*amt, scope)?; self.write_output(format!("sleep {var}"))?; 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), ))); Ok(Some(CompilationResult { 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_output(format!("s {} {} {}", device_val, logic_type, variable))?; 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_output(format!("sb {} {} {}", device_hash_val, logic_type, var))?; 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_output(format!( "sbn {} {} {} {}", device_hash, name_hash, logic_type, value ))?; 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_output(format!( "l r{} {} {}", VariableScope::RETURN_REGISTER, device_val, logic_type ))?; Ok(Some(CompilationResult { 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_output(format!( "lb r{} {} {} {}", VariableScope::RETURN_REGISTER, device_hash, logic_type, batch_mode ))?; cleanup!(device_hash_cleanup, logic_type_cleanup, batch_mode_cleanup); Ok(Some(CompilationResult { 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_output(format!( "lbn r{} {} {} {} {}", VariableScope::RETURN_REGISTER, device_hash, name_hash, logic_type, batch_mode ))?; cleanup!( device_hash_cleanup, name_hash_cleanup, logic_type_cleanup, batch_mode_cleanup ); Ok(Some(CompilationResult { 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_literal_or_variable( LiteralOrVariable::Literal(slot_index.node), scope, )?; let (logic_type, logic_cleanup) = self.compile_literal_or_variable( LiteralOrVariable::Literal(logic_type.node), scope, )?; self.write_output(format!( "ls r{} {} {} {}", VariableScope::RETURN_REGISTER, dev_hash, slot_index, logic_type ))?; cleanup!(hash_cleanup, slot_cleanup, logic_cleanup); Ok(Some(CompilationResult { 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_literal_or_variable( LiteralOrVariable::Literal(slot_index.node), 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_output(format!( "ss {} {} {} {}", dev_name, slot_index, logic_type, var ))?; cleanup!(name_cleanup, index_cleanup, type_cleanup, var_cleanup); Ok(None) } } } fn expression_syscall_math( &mut self, expr: Math<'a>, 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_output(format!("acos r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Asin(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("asin r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Atan(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("atan r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { 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_output(format!( "atan2 r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2 ))?; cleanup!(var1_cleanup, var2_cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Abs(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("abs r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Ceil(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("ceil r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Cos(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("cos r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Floor(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("floor r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Log(expr) => { let (var, cleanup) = self.compile_operand(*expr, scope)?; self.write_output(format!("log r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(cleanup); Ok(Some(CompilationResult { 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_output(format!( "max r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2 ))?; cleanup!(clean1, clean2); Ok(Some(CompilationResult { 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_output(format!( "min r{} {} {}", VariableScope::RETURN_REGISTER, var1, var2 ))?; cleanup!(clean1, clean2); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Rand => { self.write_output(format!("rand r{}", VariableScope::RETURN_REGISTER))?; Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sin(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_output(format!("sin r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(clean); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Sqrt(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_output(format!("sqrt r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(clean); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Tan(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_output(format!("tan r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(clean); Ok(Some(CompilationResult { location: VariableLocation::Persistant(VariableScope::RETURN_REGISTER), temp_name: None, })) } Math::Trunc(expr) => { let (var, clean) = self.compile_operand(*expr, scope)?; self.write_output(format!("trunc r{} {}", VariableScope::RETURN_REGISTER, var))?; cleanup!(clean); Ok(Some(CompilationResult { 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; if self.function_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_metadata.insert( name.node.clone(), arguments.iter().map(|a| a.node.clone()).collect(), ); // Declare the function as a line identifier self.write_output(format!("{}:", name.node))?; self.function_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_output(format!( "pop r{loc} {}", debug!(self, "#{}", var_name.node) ))?; } 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), )?; } self.write_output("push ra")?; let return_label = self.next_label_name(); let prev_return_label = self.current_return_label.replace(return_label.clone()); block_scope.add_variable( return_label.clone(), LocationRequest::Stack, Some(name.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.current_return_label = prev_return_label; self.write_output(format!("{}:", return_label))?; self.write_output(format!( "sub r{0} sp {ra_stack_offset}", VariableScope::TEMP_STACK_REGISTER ))?; self.write_output(format!( "get ra db r{0}", VariableScope::TEMP_STACK_REGISTER ))?; if block_scope.stack_offset() > 0 { self.write_output(format!("sub sp sp {}", block_scope.stack_offset()))?; } self.write_output("j ra")?; Ok(()) } }