diff options
Diffstat (limited to 'lib/xixanta/src/object.rs')
| -rw-r--r-- | lib/xixanta/src/object.rs | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs new file mode 100644 index 0000000..70359f1 --- /dev/null +++ b/lib/xixanta/src/object.rs @@ -0,0 +1,418 @@ +use crate::errors::{ContextError, ContextErrorReason}; +use crate::mapping::Mapping; +use crate::node::{ControlType, NodeType, PNode, PString}; +use crate::opcodes::CONTROL_FUNCTIONS; +use std::collections::HashMap; + +/// The name of the global context as used internally. +const GLOBAL_CONTEXT: &str = "Global"; + +/// A Bundle represents a set of bytes that can be encoded as binary data. +#[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct Bundle { + /// The bytes which make up any encodable element for the application. The + /// capacity is of three bytes maximum, but the actual size is encoded in + /// the `size` property. + pub bytes: [u8; 3], + + /// The amount of bytes which have actually been set on this bundle. + pub size: u8, + + /// The address where the given bytes are to be placed on the resulting + /// binary file. + pub address: usize, + + /// If this bundle encodes an instruction, the amount of cycles it takes for + /// the CPU to actually execute it. + pub cycles: u8, + + /// Whether the cost in cycles is affected when crossing a page boundary. + pub affected_on_page: bool, + + /// Whether the bytes on `bytes` contain the final value or not. This is + /// used for internal purposes only. + pub resolved: bool, +} + +impl Bundle { + /// Create a default bundle but with the given `resolved` status. + pub fn new(resolved: bool) -> Self { + Self { + resolved, + ..Default::default() + } + } + + /// Create a bundle tailored for filling purposes. + pub fn fill(value: u8) -> Self { + Self { + bytes: [value, 0, 0], + size: 1, + address: 0, + cycles: 0, + affected_on_page: false, + resolved: true, + } + } +} + +/// The type of object being referenced, which is either a value as-is, or an +/// address that needs to be interpreted when fetching it. +#[derive(Debug, Clone)] +pub enum ObjectType { + Address, + Value, +} + +/// Bundle and metadata which is stored on the context table for a given +/// variable or label. +#[derive(Debug, Clone)] +pub struct Object { + /// Bundle representing the actual value. + pub bundle: Bundle, + + /// The mapping index where the object was found. Note that this index + /// doesn't mean much on the table, but it has to mean something by the + /// caller. + pub mapping: usize, + + /// The segment index within the referenced mapping where the object was + /// found. Note that this index doesn't mean much on the table, but it has + /// to mean something by the caller. + pub segment: usize, + + /// The type for this object. + pub object_type: ObjectType, +} + +impl Object { + /// Create a default bundle with the given metadata parameters. + pub fn new(mapping: usize, segment: usize, object_type: ObjectType) -> Self { + Self { + bundle: Bundle::default(), + mapping, + segment, + object_type, + } + } +} + +/// Context holds information about the different scopes being defined, the +/// current scope, and has a map of all the variables defined for each scope. +#[derive(Debug)] +pub struct Context { + /// Tracks the contexts that we have entered at any given point. The last + /// element is the actual context, and it will be empty if we are in the + /// global context. + stack: Vec<String>, + + /// Map of objects for any given context. The key is the name of the + /// context, and the value is another map. This inner map has the variable + /// name as the key, and the Bundle as a value. + pub map: HashMap<String, HashMap<String, Object>>, + + /// Map of labels for any given context. Note that this only keeps track of + /// the amount of labels that have been defined, which might include + /// anonymous positions. This is primarily used on relative addressing where + /// the name of the label might not be provided (e.g. anonymous relative + /// reference). + pub labels: HashMap<String, Vec<Object>>, +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + +impl Context { + /// Returns a new empty context. + pub fn new() -> Self { + Context { + stack: vec![], + map: HashMap::from([(String::from(GLOBAL_CONTEXT), HashMap::new())]), + labels: HashMap::from([(String::from(GLOBAL_CONTEXT), vec![])]), + } + } + + /// Returns the value of the object represented by the given `id`. Note that + /// this `id` can be scoped or not, and this function will try to pick the + /// variable from the right scope. The value itself will be resolved if the + /// type is ObjectType::Address. + pub fn get_variable(&self, id: &PString, mappings: &[Mapping]) -> Result<Object, ContextError> { + // First of all, figure out the name of the scope and the real name of + // the variable. If this was not scoped at all (None case when trying to + // rsplit by the "::" operator), then we assume it's a global variable. + let (scope_name, var_name) = match id.value.rsplit_once("::") { + Some((scope, name)) => (scope, name), + None => (self.name(), id.value.as_str()), + }; + + // And with that, the only thing left is to find the scope and the + // variable in it. + match self.map.get(scope_name) { + Some(scope) => match scope.get(var_name) { + Some(var) => match var.object_type { + ObjectType::Value => Ok(var.clone()), + ObjectType::Address => Ok(self.resolve_label(mappings, var)), + }, + None => Err(ContextError { + message: format!( + "could not find variable '{}' in {}", + var_name, + self.to_human_with(scope_name) + ), + line: id.line, + reason: ContextErrorReason::UnknownVariable, + }), + }, + None => Err(ContextError { + message: format!("did not find scope '{}'", scope_name), + line: id.line, + reason: ContextErrorReason::BadScope, + }), + } + } + + /// Given an `object` which is located via `mappings`, resolve the effective + /// address. + /// + /// NOTE: this function asserts that the given `object` is of type + /// ObjectType::Address, otherwise it doesn't make sense to call it. + pub fn resolve_label(&self, mappings: &[Mapping], object: &Object) -> Object { + assert!(matches!(object.object_type, ObjectType::Address)); + + let mut ret = object.clone(); + + let mapping = &mappings[ret.mapping]; + let internal_offset = u16::from_le_bytes([ret.bundle.bytes[0], ret.bundle.bytes[1]]); + let segment_offset = crate::mapping::segment_offset(mapping, ret.segment); + let addr = (mapping.start + segment_offset + internal_offset).to_le_bytes(); + + ret.bundle.bytes[0] = addr[0]; + ret.bundle.bytes[1] = addr[1]; + + ret + } + + /// Sets a value for an object identified by `id`. If `overwrite` is set to + /// true, then this value will be set even if the id already existed, + /// otherwise it will return a ContextError + pub fn set_variable( + &mut self, + id: &PString, + object: &Object, + overwrite: bool, + ) -> Result<(), ContextError> { + let scope_name = self.name().to_string(); + let scope = self.map.get_mut(&scope_name).unwrap(); + + match scope.get_mut(&id.value) { + Some(sc) => { + if !overwrite { + return Err(ContextError { + message: format!( + "'{}' already defined in {}: you cannot re-assign variables", + id.value, + self.to_human() + ), + line: id.line, + reason: ContextErrorReason::Redefinition, + }); + } + *sc = object.clone(); + } + None => { + scope.insert(id.value.clone(), object.to_owned()); + } + } + + Ok(()) + } + + /// Add a new label to the list of known labels. + pub fn add_label(&mut self, object: &Object) { + let scope_name = self.name().to_string(); + let scope = self.labels.get_mut(&scope_name).unwrap(); + + scope.push(object.clone()); + } + + /// Change the current context given a `node`. Returns a tuple which states: + /// 0. Whether the context has changed. + /// 1. Whether a caller can bundle nodes safely. + pub fn change_context(&mut self, node: &PNode) -> Result<(bool, bool), ContextError> { + // The parser already guarantees that the control node is + // from a function that we already know, so calling `unwrap` + // is not dangerous. + let control = CONTROL_FUNCTIONS + .get(&node.value.value.to_lowercase()) + .unwrap(); + + // If the control function does not touch the context, leave early. + if !control.touches_context { + return Ok((false, true)); + } + + // And push/pop the context depending on the control being used. + match node.node_type { + NodeType::Control(ControlType::StartMacro) => { + self.context_push(&node.left.clone().unwrap()); + Ok((true, false)) + } + NodeType::Control(ControlType::StartProc) + | NodeType::Control(ControlType::StartScope) => { + self.context_push(&node.left.clone().unwrap()); + Ok((true, true)) + } + NodeType::Control(ControlType::EndMacro) + | NodeType::Control(ControlType::EndProc) + | NodeType::Control(ControlType::EndScope) => { + self.context_pop(&node.value)?; + Ok((true, true)) + } + _ => Ok((false, true)), + } + } + + /// Change the current context to the given one identified by `name`, + /// disregarding any check. This is to be used when switching a context to + /// set a very specific value for that context. You should call + /// `force_context_pop` immediately. + pub fn force_context_switch(&mut self, name: &String) { + self.stack.push(name.to_owned()); + } + + /// Remove the last context being used if any. In contrast with + /// `context_pop`, this one does not error out, but does nothing in case we + /// are in the global context. This is to be used in conjunction with + /// `force_context_switch`. + pub fn force_context_pop(&mut self) { + if !self.stack.is_empty() { + self.stack.truncate(self.stack.len() - 1); + } + } + + /// Returns the amount of labels that have been submitted so far for the + /// current scope. + pub fn labels_seen(&self) -> usize { + let scope_name = self.name().to_string(); + + match self.labels.get(&scope_name) { + Some(labels) => labels.len(), + None => 0, + } + } + + /// Returns the bundle which is representative for the label being + /// referenced in a relative way. The relation is given on the `rel` + /// parameter, with a value between -4 or +4 where negative values represent + /// previous labels and positive values next ones (e.g. -2 means "2 labels + /// before"). This is in relation to `labels_seen`, which states how many + /// labels have been seen by the caller at this point. + pub fn get_relative_label( + &self, + rel: isize, + labels_seen: usize, + mappings: &[Mapping], + ) -> Result<Object, ContextError> { + // Bound check: the given 'rel' parameter has a proper value. + assert!( + rel < 5 && rel > -5 && rel != 0, + "bad parameter for relative label" + ); + + // Bound check: you cannot reference a past label that doesn't exist. + // This is the programmer's to blame, not on us, so don't assert. + if labels_seen == 0 && rel < 0 { + return Err(ContextError { + line: 0, + message: "cannot reference an unknown previous label".to_string(), + reason: ContextErrorReason::Label, + }); + } + + // Get the labels as referenced in the current context, and also the + // index that we will be using. + let scope_name = self.name().to_string(); + let labels = self.labels.get(&scope_name).unwrap(); + let idx = if rel > 0 { + labels_seen as isize + rel - 1 + } else { + labels_seen as isize + rel + }; + + // Bound check: is the programmer referencing an "out of bounds" label? + // If so then it's a mistake on their part. + if idx < 0 || idx >= labels.len() as isize { + return Err(ContextError { + line: 0, + message: "cannot reference bogus label (out of bounds)".to_string(), + reason: ContextErrorReason::Label, + }); + } + + // Everything should be fine from here on, simply return the bundle that + // was being referenced. + Ok(self.resolve_label(mappings, &labels[idx as usize])) + } + + // Pushes a new context given a `node`, which holds the identifier of the + // new scope. + fn context_push(&mut self, id: &PNode) { + let name = match self.stack.last() { + Some(n) => format!("{}::{}", n, id.value.value), + None => id.value.value.clone(), + }; + + // Actually push the name to the stack and initialize it on the variable + // map. + self.stack.push(name.clone()); + self.map.entry(name.clone()).or_default(); + self.labels.entry(name).or_default(); + } + + // Pops out the latest context that was pushed. + fn context_pop(&mut self, id: &PString) -> Result<(), ContextError> { + if self.stack.is_empty() { + return Err(ContextError { + message: format!("missplaced '{}' statement", id.value), + reason: ContextErrorReason::BadScope, + line: id.line, + }); + } + + self.stack.truncate(self.stack.len() - 1); + Ok(()) + } + + /// Returns the name of the current context. + pub fn name(&self) -> &str { + match self.stack.last() { + Some(name) => name, + None => GLOBAL_CONTEXT, + } + } + + /// Returns true if we are in the global scope. + pub fn is_global(&self) -> bool { + self.stack.is_empty() + } + + // Returns a human-readable string representing the current context. + fn to_human(&self) -> String { + match self.stack.last() { + Some(n) => format!("'{}'", n), + None => "the global scope".to_string(), + } + } + + // Returns a human-readable string representing the given context. + fn to_human_with(&self, name: &str) -> String { + if name == GLOBAL_CONTEXT { + "the global scope".to_string() + } else { + format!("'{}'", name) + } + } +} |
