diff options
Diffstat (limited to 'lib/xixanta/src/assembler.rs')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 568 |
1 files changed, 371 insertions, 197 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 76bf1de..a155d3b 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1,7 +1,7 @@ -use crate::context::Context; use crate::errors::{Error, EvalError}; -use crate::mapping::Segment; +use crate::mapping::Mapping; use crate::node::{ControlType, NodeType, PNode, PString}; +pub use crate::object::{Bundle, Context, Object, ObjectType}; use crate::opcodes::{AddressingMode, INSTRUCTIONS}; use crate::parser::Parser; use std::cmp::Ordering; @@ -9,54 +9,6 @@ use std::collections::HashMap; use std::io::Read; use std::ops::Range; -/// A Bundle represents a set of bytes that can be encoded as binary data. -/// TODO: maybe inside of Mapping? -#[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. - resolved: bool, -} - -impl Bundle { - pub fn new(resolved: bool) -> Self { - Self { - resolved, - ..Default::default() - } - } - - pub fn fill(value: u8) -> Self { - Self { - bytes: [value, 0, 0], - size: 1, - address: 0, - cycles: 0, - affected_on_page: false, - resolved: true, - } - } -} - #[derive(Clone, PartialEq)] pub enum LiteralMode { Hexadecimal, @@ -82,6 +34,7 @@ pub struct Macro { #[derive(Clone, Debug)] pub struct PendingNode { + mapping: usize, segment: usize, context: String, bundle_index: usize, @@ -95,15 +48,16 @@ pub struct Assembler { stage: Stage, macros: HashMap<String, Macro>, can_bundle: bool, - segments: Vec<Segment>, + mappings: Vec<Mapping>, + current_mapping: usize, current_segment: usize, pending: Vec<PendingNode>, labels_seen: usize, } impl Assembler { - pub fn new(segments: Vec<Segment>) -> Self { - assert!(!segments.is_empty()); + pub fn new(mappings: Vec<Mapping>) -> Self { + crate::mapping::assert(&mappings); Self { context: Context::new(), @@ -111,7 +65,8 @@ impl Assembler { stage: Stage::Init, macros: HashMap::new(), can_bundle: true, - segments, + mappings, + current_mapping: 0, current_segment: 0, pending: vec![], labels_seen: 0, @@ -149,10 +104,15 @@ impl Assembler { match &node.node_type { NodeType::Label => { if !node.value.is_empty() { - if let Err(err) = - self.context - .set_variable(&node.value, &Bundle::default(), false) - { + if let Err(err) = self.context.set_variable( + &node.value, + &Object::new( + self.current_mapping, + self.current_segment, + ObjectType::Address, + ), + false, + ) { errors.push(Error::Context(err)); } } @@ -170,8 +130,16 @@ impl Assembler { } match self.evaluate_node(node.left.as_ref().unwrap()) { Ok(value) => { - if let Err(err) = self.context.set_variable(&node.value, &value, false) - { + if let Err(err) = self.context.set_variable( + &node.value, + &Object { + bundle: value, + mapping: self.current_mapping, + segment: self.current_segment, + object_type: ObjectType::Value, + }, + false, + ) { errors.push(Error::Context(err)); } } @@ -241,39 +209,35 @@ impl Assembler { for node in nodes { match node.node_type { NodeType::Label => { - let segment = &self.segments[self.current_segment]; - // println!("SEGMENT: {:#?}", segment) - let value = (segment.start as usize + segment.offset).to_le_bytes(); - let bundle = Bundle { - bytes: [value[0], value[1], value[2]], - size: 2, - address: 0, - cycles: 0, - affected_on_page: false, - resolved: true, + let segment = + &self.mappings[self.current_mapping].segments[self.current_segment]; + let value = segment.offset.to_le_bytes(); + let object = Object { + bundle: Bundle { + bytes: [value[0], value[1], value[2]], + size: 2, + address: 0, + cycles: 0, + affected_on_page: false, + resolved: false, + }, + mapping: self.current_mapping, + segment: self.current_segment, + object_type: ObjectType::Address, }; if !node.value.is_empty() { - if let Err(err) = self.context.set_variable(&node.value, &bundle, true) { + if let Err(err) = self.context.set_variable(&node.value, &object, true) { errors.push(Error::Context(err)); } } - self.context.add_label(&bundle); + self.context.add_label(&object); } NodeType::Instruction => { if self.can_bundle { self.literal_mode = None; match self.evaluate_node(node) { - Ok(mut bundle) => { - if node.is_branch() { - // TODO: it's a bit of a pity... - let current = &mut self.segments[self.current_segment]; - bundle.address = current.start as usize + current.offset; - - if let Err(e) = self.to_relative_address(node, &mut bundle) { - errors.push(Error::Eval(e)); - } - } + Ok(bundle) => { if let Err(e) = self.push_bundle(bundle, node) { errors.push(Error::Eval(e)); } @@ -314,17 +278,18 @@ impl Assembler { match self.evaluate_node(&pn.node) { Ok(mut bundle) => { - // TODO: oh boy - bundle.address = self.segments[pn.segment].bundles[pn.bundle_index].address; + let current = &self.mappings[pn.mapping].segments[pn.segment]; + bundle.address = current.bundles[pn.bundle_index].address; if pn.node.is_branch() { + bundle.resolved = true; if let Err(e) = self.to_relative_address(&pn.node, &mut bundle) { errors.push(Error::Eval(e)); } } - let current = &mut self.segments[pn.segment]; - current.bundles[pn.bundle_index].bytes = bundle.bytes; + let current_mut = &mut self.mappings[pn.mapping].segments[pn.segment]; + current_mut.bundles[pn.bundle_index].bytes = bundle.bytes; } Err(e) => errors.push(Error::Eval(e)), } @@ -332,20 +297,41 @@ impl Assembler { self.context.force_context_pop(); } + // Validate the mappings that have been evaluated before spitting it + // out. + if let Err(e) = crate::mapping::validate(&self.mappings) { + return Err(vec![Error::Eval(e)]); + } + let mut res = vec![]; - for segment in &mut self.segments { - if segment.bundles.is_empty() { - errors.push(Error::Eval(EvalError { - line: 0, - message: format!("segment '{}' is empty", segment.name), - global: true, - })); + // TODO: trainer support. + + for mapping in &mut self.mappings { + for segment in mapping.segments.iter_mut() { + // TODO: return a warning instead. + if segment.is_empty() { + return Err(vec![Error::Eval(EvalError { + line: 0, + message: format!("segment '{}' is empty", segment.name), + global: true, + })]); + } + res.append(&mut segment.bundles); } - res.append(&mut segment.bundles); + let mut diff = mapping.size as isize - mapping.offset as isize; + if diff < 0 { + errors.push(Error::Eval(EvalError{ + line: 0, + message: format!( + "exceeding segment size for '{}'; expecting {} bytes and {} bytes have already been seen", + mapping.name, mapping.size, mapping.offset, + ), + global: false, + })); + } - if let Some(fill) = segment.fill { - let mut diff = segment.size - segment.offset; + if let Some(fill) = mapping.fill { while diff > 0 { res.push(Bundle::fill(fill)); diff -= 1; @@ -400,9 +386,14 @@ impl Assembler { let mut margs = mcr.args.iter(); for arg in args.unwrap().iter() { - let bundle = self.evaluate_node(arg)?; + let obj = Object { + bundle: self.evaluate_node(arg)?, + mapping: self.current_mapping, + segment: self.current_segment, + object_type: ObjectType::Value, + }; self.context - .set_variable(margs.next().unwrap(), &bundle, false)?; + .set_variable(margs.next().unwrap(), &obj, false)?; } } @@ -419,33 +410,23 @@ impl Assembler { Ok(()) } - // TODO: move fn push_bundle(&mut self, mut bundle: Bundle, node: &PNode) -> Result<(), EvalError> { - let current = &mut self.segments[self.current_segment]; + let current = &mut self.mappings[self.current_mapping]; bundle.address = current.start as usize + current.offset; // TODO: here current.offset += bundle.size as usize; - - if current.offset > current.size { - return Err(EvalError { - line: 0, - message: format!( - "exceeding segment size for '{}'; expecting {} bytes and {} bytes have already been seen", - current.name, current.size, current.offset, - ), - global: false, - }); - } + current.segments[self.current_segment].offset += bundle.size as usize; if !bundle.resolved { self.pending.push(PendingNode { + mapping: self.current_mapping, segment: self.current_segment, context: self.context.name().to_string(), - bundle_index: current.bundles.len(), + bundle_index: current.segments[self.current_segment].bundles.len(), node: node.to_owned(), labels_seen: self.context.labels_seen(), }); } - current.bundles.push(bundle); + current.segments[self.current_segment].bundles.push(bundle); Ok(()) } @@ -515,11 +496,12 @@ impl Assembler { resolved: false, }), Stage::Crunching => { - match self - .context - .get_relative_label(node.value.to_isize(), self.labels_seen) - { - Ok(bundle) => Ok(bundle), + match self.context.get_relative_label( + node.value.to_isize(), + self.labels_seen, + &self.mappings, + ) { + Ok(object) => Ok(object.bundle), Err(e) => Err(EvalError { line: node.value.line, message: e.message, @@ -953,11 +935,14 @@ impl Assembler { // Find the segment being referenced and update the // `self.current_segment` accordingly. let mut found = false; - for (idx, segment) in self.segments.iter().enumerate() { - if segment.name == name { - self.current_segment = idx; - found = true; - break; + for (mapping_idx, mapping) in self.mappings.iter().enumerate() { + for (segment_idx, segment) in mapping.segments.iter().enumerate() { + if segment.name == name { + self.current_mapping = mapping_idx; + self.current_segment = segment_idx; + found = true; + break; + } } } if !found { @@ -971,8 +956,8 @@ impl Assembler { } fn evaluate_variable(&mut self, id: &PString) -> Result<Bundle, EvalError> { - match self.context.get_variable(id) { - Ok(value) => Ok(value), + match self.context.get_variable(id, &self.mappings) { + Ok(value) => Ok(value.bundle), Err(e) => Err(EvalError { message: e.message, line: id.line, @@ -1212,10 +1197,6 @@ impl Assembler { } else { let diff = target - next; if diff > 127 { - println!( - "DIFF: {:#?} -- TARGET: {:#?} -- NEXT: {:#?} -- NODE: {:#?} -- BUNDLE: {:#?}", - diff, target, next, node, bundle - ); return Err(EvalError { line: node.value.line, message: "you cannot branch to this location: it's too far away".to_string(), @@ -1226,6 +1207,7 @@ impl Assembler { }; bundle.bytes[1] = byte; + bundle.bytes[2] = 0; bundle.size = 2; Ok(()) @@ -1235,32 +1217,71 @@ impl Assembler { #[cfg(test)] mod tests { use super::*; - use crate::mapping::EMPTY; + use crate::mapping::{SectionType, Segment, EMPTY}; - fn one_two() -> Vec<Segment> { + fn one_two() -> Vec<Mapping> { vec![ - Segment { - name: String::from("ONE"), + Mapping { + name: String::from("HEADER"), start: 0x0000, size: 0x0010, offset: 0, fill: Some(0x00), - bundles: vec![], + section_type: SectionType::Header, + segments: vec![Segment { + name: String::from("HEADER"), + len: 0, + offset: 0, + bundles: vec![], + }], }, - Segment { - name: String::from("TWO"), - start: 0x0010, - size: 0x0020, + Mapping { + name: String::from("ROM0"), + start: 0x8000, + size: 0x8000, offset: 0, fill: None, - bundles: vec![], + section_type: SectionType::PrgRom, + segments: vec![ + Segment { + name: String::from("ONE"), + len: 0, + offset: 0, + bundles: vec![], + }, + Segment { + name: String::from("TWO"), + len: 0, + offset: 0, + bundles: vec![], + }, + ], }, ] } + fn minimal_header() -> Vec<Bundle> { + vec![ + Bundle::fill(0x4E), // N + Bundle::fill(0x45), // E + Bundle::fill(0x53), // S + Bundle::fill(0x1A), // MS-DOS \0 + Bundle::fill(0x01), // 1 * 8KB of PRG ROM + Bundle::fill(0x00), // No CHR ROM + ] + } + fn assert_instruction(line: &str, hex: &[u8]) { + // Set up the empty mapper, but we have to push a minimal header + // (otherwise an early assertion will fail), and we need to point to the + // "CODE" segment (which is in the mapping indexed by 1). let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm.assemble(line.as_bytes()).unwrap(); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + + // Grab the result passed the initial header. + let res = &asm.assemble(line.as_bytes()).unwrap()[0x10..]; assert_eq!(res.len(), 1); @@ -1269,9 +1290,13 @@ mod tests { } } - fn assert_error(line: &str, id: &str, line_num: usize, message: &str) { + fn assert_error(line: &str, id: &str, line_num: usize, global: bool, message: &str) { let mut asm = Assembler::new(EMPTY.to_vec()); - assert_error_with_assembler(&mut asm, line, id, line_num, message); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + + assert_error_with_assembler(&mut asm, line, id, line_num, global, message); } fn assert_error_with_assembler( @@ -1279,19 +1304,24 @@ mod tests { line: &str, id: &str, line_num: usize, + global: bool, message: &str, ) { let res = asm.assemble(line.as_bytes()); - let msg = format!("{} error (line {}): {}.", id, line_num, message); + let msg = if global { + format!("{} error: {}.", id, message) + } else { + format!("{} error (line {}): {}.", id, line_num, message) + }; assert_eq!(res.unwrap_err().first().unwrap().to_string().as_str(), msg); } fn assert_eval_error(line: &str, message: &str) { - assert_error(line, "Evaluation", 1, message); + assert_error(line, "Evaluation", 1, false, message); } fn assert_context_error(line: &str, message: &str, line_num: usize) { - assert_error(line, "Context", line_num, message); + assert_error(line, "Context", line_num, false, message); } // Empty @@ -1299,7 +1329,7 @@ mod tests { #[test] fn empty_line() { for line in vec!["", " ", ";; Comment", " ;; Comment"].into_iter() { - assert_error(line, "Evaluation", 1, "segment 'CODE' is empty"); + assert_error(line, "Evaluation", 1, true, "segment 'CODE' is empty"); } } @@ -1320,6 +1350,7 @@ adc %Variable "#, "Evaluation", 3, + false, "you cannot use variables like 'Variable' in binary literals", ); assert_instruction("adc #%10100010", &[0x69, 0xA2]); @@ -1339,6 +1370,7 @@ adc $Variable "#, "Evaluation", 3, + false, "you cannot use variables like 'Variable' in hexadecimal literals", ); assert_error( @@ -1348,6 +1380,7 @@ adc $Four "#, "Evaluation", 3, + false, "you cannot use variables like 'Four' in hexadecimal literals", ); assert_instruction("adc $AA", &[0x65, 0xAA]); @@ -1371,7 +1404,10 @@ adc $Four #[test] fn scoped_variable() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" .scope One ; This is a comment @@ -1392,7 +1428,7 @@ adc #Another::Variable "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 4); let instrs: Vec<[u8; 2]> = vec![[0x69, 0x20], [0x69, 0x30], [0x69, 0x20], [0x69, 0x40]]; @@ -1407,7 +1443,10 @@ adc #Another::Variable #[test] fn bare_variables() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" Variable = 4 @@ -1415,7 +1454,7 @@ adc Variable "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 1); @@ -1473,6 +1512,7 @@ lda #Scope::Variable "#, "Evaluation", 4, + false, "'e' is not a decimal value and could not find variable 'Variable' in 'Scope' either", ); } @@ -1764,7 +1804,10 @@ lda #Scope::Variable #[test] fn same_segment_labels() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" nop @@ -1776,7 +1819,7 @@ nop "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 4); @@ -1784,19 +1827,22 @@ nop assert_eq!(res[1].size, 3); assert_eq!(res[1].bytes[0], 0x4C); assert_eq!(res[1].bytes[1], 0x01); - assert_eq!(res[1].bytes[2], 0x00); + assert_eq!(res[1].bytes[2], 0x80); // jmp @end assert_eq!(res[2].size, 3); assert_eq!(res[2].bytes[0], 0x4C); assert_eq!(res[2].bytes[1], 0x07); - assert_eq!(res[2].bytes[2], 0x00); + assert_eq!(res[2].bytes[2], 0x80); } #[test] fn anonymous_relative_jumps() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" nop @@ -1815,7 +1861,7 @@ nop "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 9); @@ -1829,25 +1875,25 @@ nop 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); + assert_eq!(res[2].bytes[2], 0x80); // 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); + assert_eq!(res[3].bytes[2], 0x80); // 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); + assert_eq!(res[4].bytes[2], 0x80); // 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); + assert_eq!(res[5].bytes[2], 0x80); // Three last nop's. assert_eq!(res[6].size, 1); @@ -1861,7 +1907,10 @@ nop #[test] fn anonymous_relative_branches() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" nop @@ -1880,7 +1929,7 @@ nop "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 9); @@ -1922,7 +1971,10 @@ nop #[test] fn conditional_branch_to_labels() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" nop @@ -1934,7 +1986,7 @@ nop "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 4); @@ -1957,7 +2009,10 @@ nop #[test] fn byte_literals() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" .scope Vars @@ -1969,7 +2024,7 @@ nop "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 3); @@ -1992,7 +2047,10 @@ nop #[test] fn hi_lo_byte() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" Var = $2002 @@ -2001,7 +2059,7 @@ lda #.hibyte(Var) "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 2); let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x02], [0xA9, 0x20]]; @@ -2018,7 +2076,10 @@ lda #.hibyte(Var) #[test] fn macro_no_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" lda #42 @@ -2032,7 +2093,7 @@ MACRO "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 3); let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; @@ -2047,6 +2108,9 @@ MACRO #[test] fn macro_not_enough_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; let res = asm .assemble( r#" @@ -2072,6 +2136,9 @@ MACRO #[test] fn macro_too_many_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; let res = asm .assemble( r#" @@ -2097,7 +2164,10 @@ MACRO(1, 2) #[test] fn macro_with_one_argument() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" lda #42 @@ -2111,7 +2181,7 @@ MACRO(2) "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 3); let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x2A], [0xA9, 0x01], [0xA9, 0x02]]; @@ -2126,6 +2196,9 @@ MACRO(2) #[test] fn macro_unknown_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; let res = asm .assemble( r#" @@ -2152,6 +2225,9 @@ MACRO(1) #[test] fn macro_shadow_argument() { let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; let res = asm .assemble( r#" @@ -2179,7 +2255,10 @@ MACRO(1) #[test] fn macro_multiple_arguments() { let mut asm = Assembler::new(EMPTY.to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm .assemble( r#" .macro WRITE_PPU_DATA address, value @@ -2196,7 +2275,7 @@ WRITE_PPU_DATA $20B9, $04 "# .as_bytes(), ) - .unwrap(); + .unwrap()[0x10..]; assert_eq!(res.len(), 7); @@ -2245,31 +2324,52 @@ WRITE_PPU_DATA $20B9, $04 #[test] fn error_on_unknown_segment() { let mut asm = Assembler::new(one_two().to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + let line = r#" .segment "THREE" .segment "TWO" nop "#; - assert_error_with_assembler(&mut asm, line, "Evaluation", 2, "unknown segment 'THREE'") + assert_error_with_assembler( + &mut asm, + line, + "Evaluation", + 2, + false, + "unknown segment 'THREE'", + ) } #[test] fn error_on_empty_segment() { let mut asm = Assembler::new(one_two().to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; let line = r#" .segment "ONE" .segment "TWO" nop "#; - assert_error_with_assembler(&mut asm, line, "Evaluation", 1, "segment 'ONE' is empty") + assert_error_with_assembler( + &mut asm, + line, + "Evaluation", + 1, + true, + "segment 'ONE' is empty", + ) } #[test] fn jmp_and_beq_inside_segment() { let mut asm = Assembler::new(one_two().to_vec()); - let res = asm + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + let bundles = &asm .assemble( r#" .segment "ONE" @@ -2283,7 +2383,9 @@ nop beq :-- beq :+ jmp @hello + beq @hello beq :+++ + beq @end @end: nop : @@ -2292,11 +2394,7 @@ nop "# .as_bytes(), ) - .unwrap(); - - // Let's ignore the instructions + fill of "ONE". - let bundles = &res[16..res.len()]; - println!("{:#?}", bundles); + .unwrap()[0x11..]; // Ignoring HEADER + nop from ONE // First two nop's assert_eq!(bundles[0].size, 1); @@ -2312,30 +2410,106 @@ nop // beq :+ assert_eq!(bundles[3].size, 2); assert_eq!(bundles[3].bytes[0], 0xF0); - assert_eq!(bundles[3].bytes[1], 0x04); + assert_eq!(bundles[3].bytes[1], 0x09); - // beq @hello - assert_eq!(bundles[4].size, 2); + // jmp @hello + assert_eq!(bundles[4].size, 3); assert_eq!(bundles[4].bytes[0], 0x4C); - assert_eq!(bundles[4].bytes[1], 0x02); - assert_eq!(bundles[4].bytes[2], 0x00); + assert_eq!(bundles[4].bytes[1], 0x03); + assert_eq!(bundles[4].bytes[2], 0x80); - // beq :+++ + // beq @hello assert_eq!(bundles[5].size, 2); assert_eq!(bundles[5].bytes[0], 0xF0); - assert_eq!(bundles[5].bytes[1], 0x02); + assert_eq!(bundles[5].bytes[1], 0xF7); + + // beq :+++ + assert_eq!(bundles[6].size, 2); + assert_eq!(bundles[6].bytes[0], 0xF0); + assert_eq!(bundles[6].bytes[1], 0x04); + + // beq @end + assert_eq!(bundles[7].size, 2); + assert_eq!(bundles[7].bytes[0], 0xF0); + assert_eq!(bundles[7].bytes[1], 0x00); // Three last nop's. - assert_eq!(bundles[6].size, 1); - assert_eq!(bundles[6].bytes[0], 0xEA); - assert_eq!(bundles[7].size, 1); - assert_eq!(bundles[7].bytes[0], 0xEA); assert_eq!(bundles[8].size, 1); assert_eq!(bundles[8].bytes[0], 0xEA); + assert_eq!(bundles[9].size, 1); + assert_eq!(bundles[9].bytes[0], 0xEA); + assert_eq!(bundles[10].size, 1); + assert_eq!(bundles[10].bytes[0], 0xEA); + } + + #[test] + fn jmp_on_different_segments_intertwined() { + let mut asm = Assembler::new(one_two().to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + let bundles = &asm + .assemble( + r#" +.segment "ONE" +lala: + rts + +.segment "TWO" +code: + jsr lala + jsr code + rts + +.segment "ONE" + jsr code +"# + .as_bytes(), + ) + .unwrap()[0x11..]; // Ignoring HEADER + rts from ONE + + // "jsr code" from ONE (notice that it's intertwined!) + assert_eq!(bundles[0].size, 3); + assert_eq!(bundles[0].bytes[0], 0x20); + assert_eq!(bundles[0].bytes[1], 0x04); + assert_eq!(bundles[0].bytes[2], 0x80); + + // "jsr lala" from TWO + assert_eq!(bundles[1].size, 3); + assert_eq!(bundles[1].bytes[0], 0x20); + assert_eq!(bundles[1].bytes[1], 0x00); + assert_eq!(bundles[1].bytes[2], 0x80); + + // "jsr code" from TWO (again, notice that it's intertwined) + assert_eq!(bundles[2].size, 3); + assert_eq!(bundles[2].bytes[0], 0x20); + assert_eq!(bundles[2].bytes[1], 0x04); + assert_eq!(bundles[2].bytes[2], 0x80); + } + + #[test] + fn cannot_switch_to_segment_inside_of_scope() { + let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm + .assemble( + r#" +.scope Vars +.segment "CODE" + nop +.endscope +"# + .as_bytes(), + ) + .unwrap_err(); + + assert_eq!( + res.first().unwrap().to_string(), + "Evaluation error (line 3): cannot switch to segment 'CODE' \ + if we are still inside of a scope ('Vars')." + ); } - // TODO: jmp's and beq's inside of segment - // TODO: jmp's between segments - // TODO: Error on trying segment inside of another scope - // TODO: fill data + // TODO: jmp/beq outside of allocated PRG ROM } |
