// r15 : Return Value // r0 : Unmanaged temp variable // r1 - r7 : Temporary Variables // r8 - r14 : Persistant Variables use helpers::Span; use lsp_types::{Diagnostic, DiagnosticSeverity}; use parser::tree_node::Literal; use std::{ borrow::Cow, collections::{HashMap, VecDeque}, }; use thiserror::Error; const TEMP: [u8; 7] = [1, 2, 3, 4, 5, 6, 7]; const PERSIST: [u8; 7] = [8, 9, 10, 11, 12, 13, 14]; #[derive(Error, Debug)] pub enum Error<'a> { #[error("{0} already exists.")] DuplicateVariable(Cow<'a, str>, Option), #[error("{0} does not exist.")] UnknownVariable(Cow<'a, str>, Option), #[error("{0}")] Unknown(Cow<'a, str>, Option), } impl<'a> From> for lsp_types::Diagnostic { fn from(value: Error) -> Self { match value { Error::DuplicateVariable(_, span) | Error::UnknownVariable(_, span) | Error::Unknown(_, span) => Diagnostic { range: span.map(lsp_types::Range::from).unwrap_or_default(), severity: Some(DiagnosticSeverity::ERROR), message: value.to_string(), ..Default::default() }, } } } /// A request to store a variable at a specific register type pub enum LocationRequest { #[allow(dead_code)] /// Request to store a variable in a temprary register. Temp, /// Request to store a variable in a persistant register. Persist, /// Request to store a variable in the stack. Stack, } #[derive(Clone, Debug)] pub enum VariableLocation<'a> { /// Represents a temporary register (r1 - r7) Temporary(u8), /// Represents a persistant register (r8 - r14) Persistant(u8), /// Represents a a stack offset (current stack - offset = variable loc) Stack(u16), /// Represents a constant value and should be directly substituted as such. Constant(Literal<'a>), /// Represents a device pin. This will contain the exact `d0-d5` string Device(Cow<'a, str>), } pub struct VariableScope<'a, 'b> { temporary_vars: VecDeque, persistant_vars: VecDeque, var_lookup_table: HashMap, VariableLocation<'a>>, stack_offset: u16, parent: Option<&'b VariableScope<'a, 'b>>, } impl<'a, 'b> Default for VariableScope<'a, 'b> { fn default() -> Self { Self { parent: None, stack_offset: 0, persistant_vars: PERSIST.to_vec().into(), temporary_vars: TEMP.to_vec().into(), var_lookup_table: HashMap::new(), } } } impl<'a, 'b> VariableScope<'a, 'b> { #[allow(dead_code)] pub const TEMP_REGISTER_COUNT: u8 = 7; pub const PERSIST_REGISTER_COUNT: u8 = 7; pub const RETURN_REGISTER: u8 = 15; pub const TEMP_STACK_REGISTER: u8 = 0; pub fn registers(&self) -> Vec { let mut used = Vec::new(); for r in TEMP { if !self.temporary_vars.contains(&r) { used.push(r); } } for r in PERSIST { if !self.persistant_vars.contains(&r) { used.push(r); } } used } pub fn scoped(parent: &'b VariableScope<'a, 'b>) -> Self { Self { parent: Option::Some(parent), temporary_vars: parent.temporary_vars.clone(), persistant_vars: parent.persistant_vars.clone(), ..Default::default() } } pub fn stack_offset(&self) -> u16 { self.stack_offset } /// Adds and tracks a new scoped variable. If the location you request is full, will fall back /// to the stack. pub fn add_variable( &mut self, var_name: Cow<'a, str>, location: LocationRequest, span: Option, ) -> Result, Error<'a>> { if self.var_lookup_table.contains_key(&var_name) { return Err(Error::DuplicateVariable(var_name, span)); } let var_location = match location { LocationRequest::Temp => { if let Some(next_var) = self.temporary_vars.pop_front() { VariableLocation::Temporary(next_var) } else { let loc = VariableLocation::Stack(self.stack_offset); self.stack_offset += 1; loc } } LocationRequest::Persist => { if let Some(next_var) = self.persistant_vars.pop_front() { VariableLocation::Persistant(next_var) } else { let loc = VariableLocation::Stack(self.stack_offset); self.stack_offset += 1; loc } } LocationRequest::Stack => { let loc = VariableLocation::Stack(self.stack_offset); self.stack_offset += 1; loc } }; self.var_lookup_table.insert(var_name, var_location.clone()); Ok(var_location) } pub fn define_const( &mut self, var_name: Cow<'a, str>, value: Literal<'a>, span: Option, ) -> Result, Error<'a>> { if self.var_lookup_table.contains_key(&var_name) { return Err(Error::DuplicateVariable(var_name, span)); } let new_value = VariableLocation::Constant(value); self.var_lookup_table.insert(var_name, new_value.clone()); Ok(new_value) } pub fn get_location_of( &self, var_name: &Cow<'a, str>, span: Option, ) -> Result, Error<'a>> { // 1. Check this scope if let Some(var) = self.var_lookup_table.get(var_name) { if let VariableLocation::Stack(inserted_at_offset) = var { // Return offset relative to CURRENT sp return Ok(VariableLocation::Stack( self.stack_offset - inserted_at_offset, )); } else { return Ok(var.clone()); } } // 2. Recursively check parent if let Some(parent) = self.parent { let loc = parent.get_location_of(var_name, span)?; if let VariableLocation::Stack(parent_offset) = loc { return Ok(VariableLocation::Stack(parent_offset + self.stack_offset)); } return Ok(loc); } Err(Error::UnknownVariable(var_name.clone(), span)) } pub fn has_parent(&self) -> bool { self.parent.is_some() } #[allow(dead_code)] pub fn free_temp( &mut self, var_name: Cow<'a, str>, span: Option, ) -> Result<(), Error<'a>> { let Some(location) = self.var_lookup_table.remove(&var_name) else { return Err(Error::UnknownVariable(var_name, span)); }; match location { VariableLocation::Temporary(t) => { self.temporary_vars.push_back(t); } VariableLocation::Persistant(_) => { return Err(Error::UnknownVariable( Cow::from("Attempted to free a `let` variable."), span, )); } _ => {} }; Ok(()) } }