diff options
Diffstat (limited to 'lib/xixanta')
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 568 | ||||
| -rw-r--r-- | lib/xixanta/src/lib.rs | 40 | ||||
| -rw-r--r-- | lib/xixanta/src/mapping.rs | 417 | ||||
| -rw-r--r-- | lib/xixanta/src/object.rs (renamed from lib/xixanta/src/context.rs) | 164 |
4 files changed, 864 insertions, 325 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 } diff --git a/lib/xixanta/src/lib.rs b/lib/xixanta/src/lib.rs index 0b68ac9..0ad901f 100644 --- a/lib/xixanta/src/lib.rs +++ b/lib/xixanta/src/lib.rs @@ -2,9 +2,45 @@ extern crate lazy_static; pub mod assembler; -pub mod context; pub mod errors; -pub mod mapping; pub mod node; +pub mod object; pub mod opcodes; pub mod parser; + +/// Mapping defines structures for laying out how the code will be assembled +/// both on memory and on the ROM file itself. Notice that we take a different +/// approach than 'cc65' because that compiler has to take into account a +/// myriad of machines that had the MOS 6502 processor. Here we have a clear +/// target and we can be more specific. That is, the community has settled on a +/// very specific ROM file format, and hence the "linker configuration" turns +/// out to be simpler. The code here takes it into consideration by defining +/// three regions on the ROM file: +/// +/// 1. The header (i.e. `SectionType::Header`) will be allocated exactly on +/// the first 16 bytes of the file. Moreover, the first 6 bytes are +/// absolutely mandatory to be filled by the programmer. +/// 2. The PRG ROM (i.e. `SectionType::PrgRom`) will be allocated next to the +/// header (i.e. trainer support is not available on this assembler). This +/// section is laid out in blocks of 8KB and contains the program. +/// 3. The CHR ROM (i.e. `SectionType::ChrRom`) will be allocated just after +/// the PRG ROM and is laid out in blocks of 4KB, containing the ROM data (if +/// available). +/// +/// This is the layout for the ROM file itself, but it can itself be subdivided +/// into "mappings" (i.e. `Mapping`). A Mapping is a configuration inside of +/// one of these three sections. For a simple section like the header having +/// only one mapping will be enough, but for PRG ROM we might define a mapping +/// for each bank, for example. And even in simple scenarios, it's quite usual +/// to split this section at least with "CODE" (which contains the actual +/// program), and "VECTORS" (6 bytes containing the addresses for the vectors +/// for a 6502 processor). A Mapping contains quite a lot of info, but you can +/// think of it as a way to subdivide a section, defining stuff like where it +/// starts, its size, how to fill it if the programmer did not occupy the +/// region fully, etc. +/// +/// Inside of a mapping there might be multiple segments. This is in turn a way +/// to further subdivide the memory region, and it defines contiguous space +/// inside of a mapping. This way, regardless of where you define a segment, +/// there are some guarantees on the order. +pub mod mapping; diff --git a/lib/xixanta/src/mapping.rs b/lib/xixanta/src/mapping.rs index 657a94b..0a73648 100644 --- a/lib/xixanta/src/mapping.rs +++ b/lib/xixanta/src/mapping.rs @@ -1,169 +1,388 @@ use crate::assembler::Bundle; +use crate::errors::EvalError; lazy_static! { - pub static ref EMPTY: Vec<Segment> = vec![Segment { - name: String::from("CODE"), - start: 0x0000, - size: 0xFFFF, - offset: 0, - fill: None, - bundles: vec![], - }]; - pub static ref NROM: Vec<Segment> = vec![ - Segment { + /// An empty mapper used for testing purposes. + pub static ref EMPTY: Vec<Mapping> = vec![ + 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("VECTORS"), - start: 0xFFFA, - size: 0x0006, + Mapping { + name: String::from("ROM0"), + start: 0x8000, + size: 0x8000, + offset: 0, + fill: None, + section_type: SectionType::PrgRom, + segments: vec![Segment { + name: String::from("CODE"), + len: 0, + offset: 0, + bundles: vec![], + },] + }, + ]; + + // Mapper for a simple NROM setup (e.g. Super Mario Bros). + pub static ref NROM: Vec<Mapping> = vec![ + 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("CODE"), + Mapping { + name: String::from("ROM0"), start: 0x8000, size: 0x7FFA, offset: 0, fill: Some(0x00), - bundles: vec![], + section_type: SectionType::PrgRom, + segments: vec![Segment { + name: String::from("CODE"), + len: 0, + offset: 0, + bundles: vec![], + },] + }, + Mapping { + name: String::from("ROMV"), + start: 0xFFFA, + size: 0x0006, + offset: 0, + fill: Some(0x00), + section_type: SectionType::PrgRom, + segments: vec![Segment { + name: String::from("VECTORS"), + len: 0, + offset: 0, + bundles: vec![], + },] }, - Segment { - name: String::from("CHARS"), + Mapping { + name: String::from("ROM2"), start: 0x0000, size: 0x2000, offset: 0, fill: Some(0x00), - bundles: vec![], - } + section_type: SectionType::ChrRom, + segments: vec![Segment { + name: String::from("CHARS"), + len: 0, + offset: 0, + bundles: vec![], + },] + }, ]; - pub static ref NROM65: Vec<Segment> = vec![ - Segment { + + // The same mapper as NROM, but it adds a "STARTUP" segment into the "ROM0" + // mapping so to behave the same as the default "cc65" configuration. + pub static ref NROM65: Vec<Mapping> = vec![ + 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("VECTORS"), - start: 0xFFFA, - size: 0x0006, - offset: 0, - fill: Some(0x00), - bundles: vec![], - }, - Segment { - name: String::from("STARTUP"), + Mapping { + name: String::from("ROM0"), start: 0x8000, size: 0x7FFA, offset: 0, fill: Some(0x00), - bundles: vec![], + section_type: SectionType::PrgRom, + segments: vec![ + Segment { + name: String::from("STARTUP"), + len: 0, + offset: 0, + bundles: vec![], + }, + Segment { + name: String::from("CODE"), + len: 0, + offset: 0, + bundles: vec![], + }, + ] }, - Segment { - name: String::from("CODE"), - start: 0x8000, - size: 0x7FFA, + Mapping { + name: String::from("ROMV"), + start: 0xFFFA, + size: 0x0006, offset: 0, fill: Some(0x00), - bundles: vec![], + section_type: SectionType::PrgRom, + segments: vec![Segment { + name: String::from("VECTORS"), + len: 0, + offset: 0, + bundles: vec![], + },] }, - Segment { - name: String::from("CHARS"), + Mapping { + name: String::from("ROM2"), start: 0x0000, size: 0x2000, offset: 0, fill: Some(0x00), - bundles: vec![], - } + section_type: SectionType::ChrRom, + segments: vec![Segment { + name: String::from("CHARS"), + len: 0, + offset: 0, + bundles: vec![], + },] + }, ]; } +/// The type of section that a Mapping represents. +#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] +pub enum SectionType { + /// The 16 initial bytes describing the header of the ROM file. + Header, + + /// Bank to be stored in PRG ROM with a size multiple of 8KB. + PrgRom, + + /// Bank to be stored in CHR ROM with a size multiple of 4KB. + ChrRom, +} + +/// A segment inside of a memory mapping, used to organize the code inside of a +/// given memory mapping. Note that a segment does not do anything else: it's +/// just about organizing code inside of a mapping. It doesn't deal with how to +/// fill a memory region, or where it starts in memory, or anything like that. #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] pub struct Segment { + /// Name of the segment. pub name: String, - pub start: u16, - pub size: usize, + pub offset: usize, - pub fill: Option<u8>, + pub len: usize, + + /// Bundles that have been generated when assembling the nodes that have + /// been parsed by a previous step. pub bundles: Vec<Bundle>, } -/* TODO -#[derive(Debug)] +impl Segment { + /// Returns the length of the segment by counting the bundles that have been + // pushed so far into the segment. + pub fn len(&self) -> usize { + self.bundles + .iter() + .fold(0, |acc, bundle| acc + bundle.size as usize) + } + + /// Returns true if the given segment has no bundles in it, false otherwise. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// A region in memory which has one or more segments in it, which in turn have +/// the bundles that are to be generated in the end of an assembling operation. +#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] pub struct Mapping { + /// Name of the mapping. + pub name: String, + + /// Address where the mapping will start when loaded on the + /// console/emulator. This is the address where instructions like `jmp` or + /// labels will rely on. Hence, it's not the address of the ROM file itself, + /// but the effective address where it will be loaded. + pub start: u16, + + /// Size of the mapping. Note that this depends on the `section_type` value, + /// which is: exactly 0x10 for a header, multiples of 0x4000 for prg_rom, + /// and multiples of 0x2000 for chr_rom. + pub size: usize, + + /// The total number of bytes that have been pushed across all its segments. + pub offset: usize, + + /// Optional value to fill the mapping if the end size is lower than the + /// value on `size`. Set to `None` to skip filling the memory region for + /// this mapping. + pub fill: Option<u8>, + + /// Segments for the memory region. pub segments: Vec<Segment>, - pub nodes: HashMap<String, Vec<Node>>, - pub current: String, - pub macros: HashMap<String, Vec<Node>>, - pub current_macro: Option<String>, + + /// What kind of memory region is being described by this mapping. + pub section_type: SectionType, } -impl Mapping { - pub fn new(mut segments: Vec<Segment>) -> Self { - segments.sort_by(|a, b| a.start.cmp(&b.start)); +/// Assert that the given mappings conform to a minimum standard. +pub fn assert(mappings: &[Mapping]) { + assert!( + !mappings.is_empty(), + "We need at least one segment defined, the header" + ); + assert!( + !mappings.first().unwrap().segments.is_empty(), + "We need at least one segment defined, the header" + ); + assert_eq!( + mappings.first().unwrap().section_type, + SectionType::Header, + "First mapping section must be the header" + ); + assert_eq!( + mappings.first().unwrap().size, + 0x10, + "The header must be exactly 16 bytes long" + ); - let mut nodes = HashMap::new(); - for segment in segments.iter() { - nodes.insert(segment.name.clone(), vec![]); - } + let prg_rom_len = mappings + .iter() + .filter(|m| m.section_type == SectionType::PrgRom) + .fold(0, |acc, x| acc + x.size); + assert!(prg_rom_len >= 0x4000, "PRG ROM must be at least 8KB long"); + assert!( + prg_rom_len % 0x4000 == 0, + "PRG ROM must be formed by banks of exactly 8KB" + ); +} - let current_segment = &segments.first().unwrap().name.clone(); +/// Validate some sanity checks on the given `mappings`. Only call this function +/// after all bundles have been produced. +pub fn validate(mappings: &[Mapping]) -> Result<(), EvalError> { + // Guaranteed by `crate::mapping::assert` to be the header. + let header: &Segment = mappings.first().unwrap().segments.first().unwrap(); - Mapping { - segments, - nodes, - current: current_segment.to_string(), - macros: HashMap::new(), - current_macro: None, - } + // Header must have at least six bytes with proper information provided by + // the programmer. + if header.len() < 6 { + return Err(EvalError { + line: 0, + message: String::from("The header must contain at least 6 bytes"), + global: true, + }); } - pub fn reset(&mut self) { - self.nodes = HashMap::new(); - for segment in self.segments.iter() { - self.nodes.insert(segment.name.clone(), vec![]); - } + // Now check that the length of the evaluated data matches the criteria + // stated on the ROM header that was evaluated as well. + let (header_prg_rom_size, header_chr_rom_size) = parse_header(header)?; + let prg_rom_len = mappings + .iter() + .filter(|m| m.section_type == SectionType::PrgRom) + .fold(0, |acc, x| { + acc + x.segments.iter().fold(0, |a, y| a + y.len()) + }); + let chr_rom_len = mappings + .iter() + .filter(|m| m.section_type == SectionType::ChrRom) + .fold(0, |acc, x| { + acc + x.segments.iter().fold(0, |a, y| a + y.len()) + }); - self.current = self.segments.first().unwrap().name.clone(); - - self.macros = HashMap::new(); - self.current_macro = None; + if header_prg_rom_size < prg_rom_len { + return Err(EvalError { + line: 0, + message: format!("PRG ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_prg_rom_size, prg_rom_len), + global: true, + }); + } + if header_chr_rom_size < chr_rom_len { + return Err(EvalError { + line: 0, + message: format!("CHR ROM size is expected to by {} bytes long, but a total of {} bytes were evaluated", header_chr_rom_size, chr_rom_len), + global: true, + }); } - pub fn switch(&mut self, id: &PString) -> Result<()> { - if !self.nodes.contains_key(&id.value) { - // TODO - // return Err( - // id.parser_error(format!("segment '{}' has not been defined", id.value).as_str()) - // ); - } + Ok(()) +} - id.value.clone_into(&mut self.current); - Ok(()) - } +// Returns a tuple with the sizes for PRG and CHR ROM as described from the +// computed header. This also does some sanity checks on the header. +fn parse_header(header: &Segment) -> Result<(usize, usize), EvalError> { + let mut header_it = header.bundles.clone().into_iter(); - pub fn current(&self) -> &Vec<Node> { - self.nodes.get(&self.current).unwrap() + // Validate the magic string: 'N', 'E', 'S', $1A + if header_it.next().unwrap().bytes[0] != b'N' { + return Err(EvalError { + line: 0, + message: String::from("First byte of the header must be 'N'"), + global: true, + }); } - - pub fn current_mut(&mut self) -> &mut Vec<Node> { - self.nodes.get_mut(&self.current).unwrap() + if header_it.next().unwrap().bytes[0] != b'E' { + return Err(EvalError { + line: 0, + message: String::from("Second byte of the header must be 'E'"), + global: true, + }); + } + if header_it.next().unwrap().bytes[0] != b'S' { + return Err(EvalError { + line: 0, + message: String::from("Third byte of the header must be 'S'"), + global: true, + }); } + if header_it.next().unwrap().bytes[0] != 26 { + return Err(EvalError { + line: 0, + message: String::from( + "Fourth byte of the header must be the MS-DOS termination character", + ), + global: true, + }); + } + + Ok(( + header_it.next().unwrap().bytes[0] as usize * 0x4000, + header_it.next().unwrap().bytes[0] as usize * 0x2000, + )) +} - pub fn push(&mut self, node: Node) { - match &self.current_macro { - Some(m) => self.macros.get_mut(m).unwrap().push(node), - None => self.nodes.get_mut(&self.current).unwrap().push(node), +/// Returns the offset of the segment indexed by `segment_index` inside of +/// `mapping`. That is, it returns back at which byte the given segment is going +/// to be placed inside of the given mapping. +/// +/// NOTE: this function is only useful if you already know that all the segments +/// on the given mapping have a definitive size (i.e. they will not change in +/// the future). +pub fn segment_offset(mapping: &Mapping, segment_index: usize) -> u16 { + let mut count = 0; + + for (idx, segment) in mapping.segments.iter().enumerate() { + if idx >= segment_index { + return count; } + count += segment.offset as u16; } + + count } -*/ diff --git a/lib/xixanta/src/context.rs b/lib/xixanta/src/object.rs index 000c959..70359f1 100644 --- a/lib/xixanta/src/context.rs +++ b/lib/xixanta/src/object.rs @@ -1,12 +1,102 @@ -use crate::assembler::Bundle; 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. +/// 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)] @@ -16,17 +106,17 @@ pub struct Context { /// global context. stack: Vec<String>, - /// Map of variables for any given context. The key is the name of the + /// 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. - map: HashMap<String, HashMap<String, Bundle>>, + 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). - labels: HashMap<String, Vec<Bundle>>, + pub labels: HashMap<String, Vec<Object>>, } impl Default for Context { @@ -45,10 +135,11 @@ impl Context { } } - /// Returns the value of the variable 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. - pub fn get_variable(&self, id: &PString) -> Result<Bundle, ContextError> { + /// 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. @@ -61,7 +152,10 @@ impl Context { // variable in it. match self.map.get(scope_name) { Some(scope) => match scope.get(var_name) { - Some(var) => Ok(var.clone()), + 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 {}", @@ -80,13 +174,34 @@ impl Context { } } - /// Sets a value for a variable defined in the assignment `node`. If - /// `overwrite` is set to true, then this value will be set even if the - /// variable already existed, otherwise it will return a ContextError + /// 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, - bundle: &Bundle, + object: &Object, overwrite: bool, ) -> Result<(), ContextError> { let scope_name = self.name().to_string(); @@ -105,28 +220,22 @@ impl Context { reason: ContextErrorReason::Redefinition, }); } - *sc = bundle.clone(); + *sc = object.clone(); } None => { - scope.insert(id.value.clone(), bundle.to_owned()); + scope.insert(id.value.clone(), object.to_owned()); } } 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) { + /// 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(); - // println!("PUSHING: {:#?}", bundle); - - scope.push(bundle.clone()); + scope.push(object.clone()); } /// Change the current context given a `node`. Returns a tuple which states: @@ -205,7 +314,8 @@ impl Context { &self, rel: isize, labels_seen: usize, - ) -> Result<Bundle, ContextError> { + mappings: &[Mapping], + ) -> Result<Object, ContextError> { // Bound check: the given 'rel' parameter has a proper value. assert!( rel < 5 && rel > -5 && rel != 0, @@ -244,7 +354,7 @@ impl Context { // Everything should be fine from here on, simply return the bundle that // was being referenced. - Ok(labels[idx as usize].clone()) + Ok(self.resolve_label(mappings, &labels[idx as usize])) } // Pushes a new context given a `node`, which holds the identifier of the |
