diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-16 15:46:21 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2024-12-16 16:10:15 +0100 |
| commit | 1d0bb4d384d391b5603622d813a63cb192e0defb (patch) | |
| tree | c5800c99e8657d1dade5b5f0eb13e7ee575abecd | |
| parent | 7ade650065a2693f41e3a858bfb25881bac06e9d (diff) | |
| download | tools.nes-1d0bb4d384d391b5603622d813a63cb192e0defb.tar.gz tools.nes-1d0bb4d384d391b5603622d813a63cb192e0defb.zip | |
Fix the mapping of addresses on labels
There was a big missunderstanding on how things were to be laid out in
the end file, and so it was needed to create a proper understanding on
what's a Mapping and what's a Segment. These turned out to be
fundamental concepts that I failed to grok up until this commit.
Hence, this commit re-arranges completely how variables and labels are
stored in the Context, and how these objects can then be translated into
bundles that can be spit out to the caller.
This commit, besides introducing the new Mapping struct, also introduced
a more general Object, which abstracts things from the Bundle struct,
and allows us to pass certain metadata about the bundle at hand.
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
| -rw-r--r-- | crates/nasm/src/main.rs | 6 | ||||
| -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 |
5 files changed, 867 insertions, 328 deletions
diff --git a/crates/nasm/src/main.rs b/crates/nasm/src/main.rs index 9d97507..527455a 100644 --- a/crates/nasm/src/main.rs +++ b/crates/nasm/src/main.rs @@ -3,7 +3,7 @@ use clap::Parser as ClapParser; use std::fs::File; use std::io::{self, Read, Write}; use xixanta::assembler::Assembler; -use xixanta::mapping::{Segment, EMPTY, NROM, NROM65}; +use xixanta::mapping::{Mapping, EMPTY, NROM, NROM65}; /// Assembler for the 6502 microprocessor that targets the NES. #[derive(ClapParser, Debug)] @@ -57,7 +57,7 @@ fn main() -> Result<()> { }; // Select the linker configuration. - let segments: Vec<Segment> = match args.config { + let mapping: Vec<Mapping> = match args.config { Some(c) => match c.to_lowercase().as_str() { "empty" => EMPTY.to_vec(), "nrom" => NROM.to_vec(), @@ -71,7 +71,7 @@ fn main() -> Result<()> { }; // Initialize the assembler with the given linker configuration. - let mut assembler = Assembler::new(segments); + let mut assembler = Assembler::new(mapping); // After the parse operation, just print the results. if args.disassemble { 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 |
