diff options
| author | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-01-14 23:34:03 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mikisabate@gmail.com> | 2025-01-14 23:34:03 +0100 |
| commit | 6aded1e9501634bfffc46f87f65fb605bc1c41d5 (patch) | |
| tree | 9d11ecac9526164af523dcf5d8db9945ae31d948 | |
| parent | 4de5d3b70e1e8ba33e6cb49f94cc77e56a21750f (diff) | |
| download | tools.nes-6aded1e9501634bfffc46f87f65fb605bc1c41d5.tar.gz tools.nes-6aded1e9501634bfffc46f87f65fb605bc1c41d5.zip | |
Implement .asciiz control statement
Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
| -rw-r--r-- | lib/xixanta/src/assembler.rs | 79 | ||||
| -rw-r--r-- | lib/xixanta/src/node.rs | 2 | ||||
| -rw-r--r-- | lib/xixanta/src/opcodes.rs | 1 |
3 files changed, 72 insertions, 10 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index a58c45e..32d7c14 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1244,7 +1244,7 @@ impl<'a> Assembler<'a> { } } - fn evaluate_control_statement(&mut self, node: &PNode) -> Result<(), Error> { + fn evaluate_control_statement(&mut self, node: &'a PNode) -> Result<(), Error> { // This might just be a statement that changes the context (e.g. // ".macro", ".proc", etc.). In this case change the context and leave // early. @@ -1272,6 +1272,7 @@ impl<'a> Assembler<'a> { self.push_evaluated_arguments(node, 2) } NodeType::Control(ControlType::ReserveMemory) => self.reserve_memory(node), + NodeType::Control(ControlType::Asciiz) => self.push_ascii_string(node), NodeType::Control(ControlType::Segment) => self.switch_to_segment(node), NodeType::Control(ControlType::IncBin) => { self.incbin(node.args.as_ref().unwrap().first().unwrap()) @@ -1622,18 +1623,26 @@ impl<'a> Assembler<'a> { Ok(()) } - fn switch_to_segment(&mut self, node: &PNode) -> Result<(), Error> { - // First of all, fetch the argument for the ".segment" statement and - // validate that it has some basic format. Note that the existence of - // exactly one argument is guaranteed by the parser and, thus, - // `unwrap()` calls are not dangerous in this context. + // Fetch the first argument from `node`, validate that it's a quoted string, + // and return its inner value. Note that before calling this function there + // must be the guarantee that there's at least one argument inside of the + // node. + fn fetch_quoted_first_argument(&mut self, node: &'a PNode) -> Result<&'a str, Error> { let arg = &node.args.as_ref().unwrap().first().unwrap(); let val = &arg.value.value; - if val.len() < 3 || !val.starts_with('"') || !val.ends_with('"') { + if val.len() < 3 { + return Err(Error { + line: node.value.line, + message: "unpermitted empty argument".to_string(), + source: self.source_for(node), + global: false, + }); + } + if !val.starts_with('"') || !val.ends_with('"') { return Err(Error { line: node.value.line, message: format!( - "segment declaration has to be written inside of double quotes ('{}' given instead)", + "declaration has to be written inside of double quotes ('{}' given instead)", val, ), source: self.source_for(node), @@ -1641,8 +1650,34 @@ impl<'a> Assembler<'a> { }); } - // Validate the segment name. - let name = &val[1..val.len() - 1]; + Ok(&val[1..val.len() - 1]) + } + + // Push fill bundles which correspond to the string referenced in `node`. An + // 0x00 bundle will also be pushed at the end. + fn push_ascii_string(&mut self, node: &'a PNode) -> Result<(), Error> { + let string = self.fetch_quoted_first_argument(node)?; + + for ch in string.chars() { + if !ch.is_ascii() { + return Err(Error { + line: node.value.line, + message: "string can only contain ASCII characters".to_string(), + source: self.source_for(node), + global: false, + }); + } + + self.push_bundle(Bundle::fill(ch as u8), node)?; + } + self.push_bundle(Bundle::fill(0x00), node)?; + + Ok(()) + } + + // Change the current segment to the one referenced in `node`. + fn switch_to_segment(&mut self, node: &'a PNode) -> Result<(), Error> { + let name = self.fetch_quoted_first_argument(node)?; if name .chars() .any(|ch| !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')) @@ -3096,6 +3131,30 @@ jsr Movement::update } #[test] + fn asciiz() { + let res = just_bundles(".asciiz \"hello\""); + + assert_eq!(res.len(), 6); + assert_eq!(res[0].value(), 0x68); + assert_eq!(res[1].value(), 0x65); + assert_eq!(res[2].value(), 0x6C); + assert_eq!(res[3].value(), 0x6C); + assert_eq!(res[4].value(), 0x6F); + assert_eq!(res[5].value(), 0x00); + } + + #[test] + fn bad_asciiz() { + let res = just_assemble(".asciiz \"\""); + + assert_eq!(res.errors.len(), 1); + assert_eq!( + res.errors[0].to_string(), + "unpermitted empty argument (line 4)" + ); + } + + #[test] fn hi_lo_byte() { let res = just_bundles( r#" diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs index 8fc3c73..04655cb 100644 --- a/lib/xixanta/src/node.rs +++ b/lib/xixanta/src/node.rs @@ -142,6 +142,7 @@ pub enum ControlType { EndRepeat, IncludeSource, ReserveMemory, + Asciiz, } impl fmt::Display for ControlType { @@ -164,6 +165,7 @@ impl fmt::Display for ControlType { ControlType::EndRepeat => write!(f, ".endrepeat"), ControlType::IncludeSource => write!(f, ".include"), ControlType::ReserveMemory => write!(f, ".res"), + ControlType::Asciiz => write!(f, ".asciiz"), } } } diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs index 455a8ea..7f9da45 100644 --- a/lib/xixanta/src/opcodes.rs +++ b/lib/xixanta/src/opcodes.rs @@ -750,6 +750,7 @@ lazy_static! { functions.insert(String::from(".endrepeat"), Control { control_type: ControlType::EndRepeat, has_identifier: None, required_args: None, touches_context: true }); functions.insert(String::from(".include"), Control { control_type: ControlType::IncludeSource, has_identifier: None, required_args: Some((1, 1)), touches_context: false }); functions.insert(String::from(".res"), Control { control_type: ControlType::ReserveMemory, has_identifier: None, required_args: Some((1, 2)), touches_context: false }); + functions.insert(String::from(".asciiz"), Control { control_type: ControlType::Asciiz, has_identifier: None, required_args: Some((1, 1)), touches_context: false }); functions }; |
