diff options
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 204 | ||||
| -rw-r--r-- | lib/xixanta/src/context.rs | 115 | ||||
| -rw-r--r-- | lib/xixanta/src/errors.rs | 1 | ||||
| -rw-r--r-- | lib/xixanta/src/node.rs | 39 |
4 files changed, 329 insertions, 30 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 0f164d7..316343f 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -32,6 +32,8 @@ pub struct Bundle { /// 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. resolved: bool, } @@ -68,8 +70,8 @@ pub enum Stage { Init, Parsing, Context, - Unrolling, Bundling, + Crunching, } #[derive(Clone, Debug)] @@ -84,6 +86,7 @@ pub struct PendingNode { context: String, bundle_index: usize, node: PNode, + labels_seen: usize, } pub struct Assembler { @@ -95,13 +98,13 @@ pub struct Assembler { segments: Vec<Segment>, current_segment: usize, pending: Vec<PendingNode>, + labels_seen: usize, } impl Assembler { pub fn new(segments: Vec<Segment>) -> Self { assert!(!segments.is_empty()); - // TODO Self { context: Context::new(), literal_mode: None, @@ -111,6 +114,7 @@ impl Assembler { segments, current_segment: 0, pending: vec![], + labels_seen: 0, } } @@ -128,15 +132,12 @@ impl Assembler { self.stage = Stage::Context; self.eval_context(&parser.nodes)?; - // TODO: unroll macros, fill out labels, etc. - self.stage = Stage::Unrolling; - // Finally convert the relevant nodes into binary bundles which can be // used by the caller. self.stage = Stage::Bundling; self.bundle(&parser.nodes)?; - // TODO + self.stage = Stage::Crunching; self.crunch_and_resolve_pending() } @@ -147,11 +148,13 @@ impl Assembler { for (idx, node) in nodes.iter().enumerate() { match &node.node_type { NodeType::Label => { - if let Err(err) = - self.context - .set_variable(&node.value, &Bundle::default(), false) - { - errors.push(Error::Context(err)); + if !node.value.is_empty() { + if let Err(err) = + self.context + .set_variable(&node.value, &Bundle::default(), false) + { + errors.push(Error::Context(err)); + } } } NodeType::Assignment => { @@ -248,9 +251,12 @@ impl Assembler { resolved: true, }; - if let Err(err) = self.context.set_variable(&node.value, &bundle, true) { - errors.push(Error::Context(err)); + if !node.value.is_empty() { + if let Err(err) = self.context.set_variable(&node.value, &bundle, true) { + errors.push(Error::Context(err)); + } } + self.context.add_label(&bundle); } NodeType::Instruction => { if self.can_bundle { @@ -301,6 +307,7 @@ impl Assembler { let mut errors = vec![]; for pn in self.pending.clone() { + self.labels_seen = pn.labels_seen; self.context.force_context_switch(&pn.context); match self.evaluate_node(&pn.node) { @@ -414,6 +421,7 @@ impl Assembler { context: self.context.name().to_string(), bundle_index: current.bundles.len(), node: node.to_owned(), + labels_seen: self.context.labels_seen(), }); } current.bundles.push(bundle); @@ -436,9 +444,9 @@ impl Assembler { // variable), we'll assume that non-prefixed literals are // just decimal values. Ok(self.evaluate_decimal(node)?) + } else if node.value.is_anonymous_relative_reference() { + Ok(self.evaluate_anonymous_relative_reference(node)?) } else if node.value.is_valid_identifier(true).is_err() { - // TODO: relative labels - println!("NODE: {:#?}", node); // If this is not a valid identifier, just error out. Err(EvalError { message: "no prefix was given to operand".to_string(), @@ -470,6 +478,34 @@ impl Assembler { } } + fn evaluate_anonymous_relative_reference(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + self.literal_mode = Some(LiteralMode::Plain); + + match &self.stage { + Stage::Bundling => Ok(Bundle { + bytes: [0, 0, 0], + size: 2, + address: 0, + cycles: 0, + affected_on_page: false, + resolved: false, + }), + Stage::Crunching => { + match self + .context + .get_relative_label(node.value.to_isize(), self.labels_seen) + { + Ok(bundle) => Ok(bundle), + Err(e) => Err(EvalError { + line: node.value.line, + message: e.message, + }), + } + } + _ => panic!("unexpected evaluation of relative reference"), + } + } + fn evaluate_hexadecimal(&mut self, node: &PNode) -> Result<Bundle, EvalError> { let mut chars = node.value.value.chars(); let mut bytes = [0, 0, 0]; @@ -835,6 +871,8 @@ impl Assembler { } fn evaluate_instruction(&mut self, node: &PNode) -> Result<Bundle, EvalError> { + self.literal_mode = None; + let (mode, mut bundle) = match &node.left { Some(_) => self.get_addressing_mode_and_bytes(node)?, None => (AddressingMode::Implied, Bundle::new(true)), @@ -1008,7 +1046,9 @@ impl Assembler { } } Some(LiteralMode::Plain) => { - if val.size > 1 { + if base.is_branch() { + Ok((AddressingMode::RelativeOrZeropage, val)) + } else if val.size > 1 { match base.value.value.as_str() { "jmp" | "jsr" => Ok((AddressingMode::Absolute, val)), _ => Err(EvalError { @@ -1036,11 +1076,6 @@ impl Assembler { let next = (bundle.address + 2) as u16; let target = u16::from_le_bytes([bundle.bytes[1], bundle.bytes[2]]); - // println!( - // "NEXT: {:#?} -- TARGET: {:#?} -- BUNDLE: {:#?}", - // next, target, bundle - // ); - let byte = if target < next { let diff = target as i16 - next as i16; if diff < -128 { @@ -1062,6 +1097,7 @@ impl Assembler { }; bundle.bytes[1] = byte; + bundle.size = 2; Ok(()) } @@ -1599,7 +1635,131 @@ nop assert_eq!(res[2].bytes[2], 0x00); } - // TODO: anonymous jumps & branches + #[test] + fn anonymous_relative_jumps() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +nop +: + nop +@hello: + jmp :-- + jmp :+ + jmp @hello + jmp :+++ +@end: + nop +: + nop +: nop +"# + .as_bytes(), + ) + .unwrap(); + + assert_eq!(res.len(), 9); + + // First two nop's + assert_eq!(res[0].size, 1); + assert_eq!(res[0].bytes[0], 0xEA); + assert_eq!(res[1].size, 1); + assert_eq!(res[1].bytes[0], 0xEA); + + // jmp :-- + assert_eq!(res[2].size, 3); + assert_eq!(res[2].bytes[0], 0x4C); + assert_eq!(res[2].bytes[1], 0x01); + assert_eq!(res[2].bytes[2], 0x00); + + // jmp :+ + assert_eq!(res[3].size, 3); + assert_eq!(res[3].bytes[0], 0x4C); + assert_eq!(res[3].bytes[1], 0x0E); + assert_eq!(res[3].bytes[2], 0x00); + + // jmp @hello + assert_eq!(res[4].size, 3); + assert_eq!(res[4].bytes[0], 0x4C); + assert_eq!(res[4].bytes[1], 0x02); + assert_eq!(res[4].bytes[2], 0x00); + + // jmp :+++ + assert_eq!(res[5].size, 3); + assert_eq!(res[5].bytes[0], 0x4C); + assert_eq!(res[5].bytes[1], 0x10); + assert_eq!(res[5].bytes[2], 0x00); + + // Three last nop's. + assert_eq!(res[6].size, 1); + assert_eq!(res[6].bytes[0], 0xEA); + assert_eq!(res[7].size, 1); + assert_eq!(res[7].bytes[0], 0xEA); + assert_eq!(res[8].size, 1); + assert_eq!(res[8].bytes[0], 0xEA); + } + + #[test] + fn anonymous_relative_branches() { + let mut asm = Assembler::new(EMPTY.to_vec()); + let res = asm + .assemble( + r#" +nop +: + nop +@hello: + beq :-- + beq :+ + beq @hello + beq :+++ +@end: + nop +: + nop +: nop +"# + .as_bytes(), + ) + .unwrap(); + + assert_eq!(res.len(), 9); + + // First two nop's + assert_eq!(res[0].size, 1); + assert_eq!(res[0].bytes[0], 0xEA); + assert_eq!(res[1].size, 1); + assert_eq!(res[1].bytes[0], 0xEA); + + // beq :-- + assert_eq!(res[2].size, 2); + assert_eq!(res[2].bytes[0], 0xF0); + assert_eq!(res[2].bytes[1], 0xFD); + + // beq :+ + assert_eq!(res[3].size, 2); + assert_eq!(res[3].bytes[0], 0xF0); + assert_eq!(res[3].bytes[1], 0x04); + + // beq @hello + assert_eq!(res[4].size, 2); + assert_eq!(res[4].bytes[0], 0xF0); + assert_eq!(res[4].bytes[1], 0xFA); + + // beq :+++ + assert_eq!(res[5].size, 2); + assert_eq!(res[5].bytes[0], 0xF0); + assert_eq!(res[5].bytes[1], 0x02); + + // Three last nop's. + assert_eq!(res[6].size, 1); + assert_eq!(res[6].bytes[0], 0xEA); + assert_eq!(res[7].size, 1); + assert_eq!(res[7].bytes[0], 0xEA); + assert_eq!(res[8].size, 1); + assert_eq!(res[8].bytes[0], 0xEA); + } #[test] fn conditional_branch_to_labels() { diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/context.rs index 6933085..683d750 100644 --- a/lib/xixanta/src/context.rs +++ b/lib/xixanta/src/context.rs @@ -4,15 +4,35 @@ 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.. +// The name of the global context as used internally. const GLOBAL_CONTEXT: &str = "Global"; /// 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 variables 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. map: HashMap<String, HashMap<String, Bundle>>, + + /// 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). + labels: HashMap<String, Vec<Bundle>>, +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } } impl Context { @@ -21,6 +41,7 @@ impl Context { Context { stack: vec![], map: HashMap::from([(String::from(GLOBAL_CONTEXT), HashMap::new())]), + labels: HashMap::from([(String::from(GLOBAL_CONTEXT), vec![])]), } } @@ -94,6 +115,18 @@ impl Context { Ok(()) } + /// Add a new label that has the value as given by the `bundle` parameter. + /// The actual name of the label does not matter since that is already + /// referenced as a "variable". This function needs to be called whenever we + /// are sure that we have the proper address for a label and that we should + /// track it in order for relative addressing to work. + pub fn add_label(&mut self, bundle: &Bundle) { + let scope_name = self.name().to_string(); + let scope = self.labels.get_mut(&scope_name).unwrap(); + + scope.push(bundle.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. @@ -131,16 +164,87 @@ impl Context { } } + /// 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, + ) -> Result<Bundle, 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(labels[idx as usize].clone()) + } + // Pushes a new context given a `node`, which holds the identifier of the // new scope. fn context_push(&mut self, id: &PNode) { @@ -152,7 +256,8 @@ impl Context { // Actually push the name to the stack and initialize it on the variable // map. self.stack.push(name.clone()); - self.map.entry(name).or_default(); + self.map.entry(name.clone()).or_default(); + self.labels.entry(name).or_default(); } // Pops out the latest context that was pushed. @@ -194,9 +299,3 @@ impl Context { } } } - -impl Default for Context { - fn default() -> Self { - Self::new() - } -} diff --git a/lib/xixanta/src/errors.rs b/lib/xixanta/src/errors.rs index d5444df..6abf280 100644 --- a/lib/xixanta/src/errors.rs +++ b/lib/xixanta/src/errors.rs @@ -45,6 +45,7 @@ pub enum ContextErrorReason { Redefinition, UnknownVariable, BadScope, + Label, Other, } diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs index c787394..993d9c1 100644 --- a/lib/xixanta/src/node.rs +++ b/lib/xixanta/src/node.rs @@ -80,6 +80,45 @@ impl PString { Ok(()) } + + /// Returns true if this is an anonymous relative reference (i.e. the ':+' + /// in something like "beq :+"). + pub fn is_anonymous_relative_reference(&self) -> bool { + let mut it = self.value.chars(); + + // An anonymous relative reference must start with ':'. If that's not + // the case, return early. + if it.next().unwrap_or(' ') != ':' { + return false; + } + + // Get the first character of the reference. The rest of the string must + // be the same as this character. + let init = it.next().unwrap_or(' '); + if init != '+' && init != '-' { + return false; + } + + // The rest of the string should match the initial 'c' character (i.e. + // either '+' or '-', but never mixed in). + it.all(|current| init == current) + } + + // Returns the isize value that can be computed assuming that this is an + // anonymous relative reference. References to a previous label will have a + // negative value, while references to next labels have a positive one. + pub fn to_isize(&self) -> isize { + let c = self.value.chars().nth(1).unwrap_or(' '); + + // As stated from the documentation, this *has to be* a valid reference. + assert!(c == '+' || c == '-', "bad relative reference"); + + let res = self.value.chars().filter(|x| *x == c).count() as isize; + if c == '-' { + return -res; + } + res + } } /// The type of control function being used. Use this enum in order to detect |
