diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-10-31 16:10:48 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-12 07:54:58 +0100 |
| commit | b768b2c6749986b0efe8610c023b42eb0d9e2ce6 (patch) | |
| tree | 66dda2259ca11022454b0d88948ffe145fb1d090 /lib/xixanta/src/assembler.rs | |
| parent | d2d6d90acf2e9a8ccb4cbcec619d2a0d49cd462a (diff) | |
| download | tools.nes-b768b2c6749986b0efe8610c023b42eb0d9e2ce6.tar.gz tools.nes-b768b2c6749986b0efe8610c023b42eb0d9e2ce6.zip | |
Add support for anonymous labels
Allow for anonymous labels to be defined and referenced by using the
same syntax as cc65.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src/assembler.rs')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 204 |
1 files changed, 182 insertions, 22 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() { |
