aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-01-14 21:07:07 +0100
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-01-14 22:19:50 +0100
commitfb836cb4cae07b2c66a41ad1413a8cab1a080e6a (patch)
treeb55996b2fd0c7642a0dbf3c74ebefff0150bc9cf
parent52bab4714b8f9301e4ad2a8c5dddf0787b8698f2 (diff)
downloadtools.nes-fb836cb4cae07b2c66a41ad1413a8cab1a080e6a.tar.gz
tools.nes-fb836cb4cae07b2c66a41ad1413a8cab1a080e6a.zip
Implement the .res control statement
Similarly to other assemblers, this allows the programmer to write a definite amount of bytes with the same values. Compared to other assemblers there are two things to notice. First, there is a limit to it (i.e. whatever can fit in 2 bytes). Second, if the fill value is not provided, then it will default to the current mapping's fill value, or just 0x00 if the current mapping doesn't define one of its own. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
-rw-r--r--lib/xixanta/src/assembler.rs97
-rw-r--r--lib/xixanta/src/node.rs2
-rw-r--r--lib/xixanta/src/opcodes.rs1
3 files changed, 100 insertions, 0 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index c64c413..a58c45e 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1271,6 +1271,7 @@ impl<'a> Assembler<'a> {
NodeType::Control(ControlType::Addr) | NodeType::Control(ControlType::Word) => {
self.push_evaluated_arguments(node, 2)
}
+ NodeType::Control(ControlType::ReserveMemory) => self.reserve_memory(node),
NodeType::Control(ControlType::Segment) => self.switch_to_segment(node),
NodeType::Control(ControlType::IncBin) => {
self.incbin(node.args.as_ref().unwrap().first().unwrap())
@@ -1559,6 +1560,68 @@ impl<'a> Assembler<'a> {
Ok(())
}
+ // Consume the given `node` by pushing as many fill bundles as it can be
+ // parsed assuming this is a `.res` control statement.
+ fn reserve_memory(&mut self, node: &PNode) -> Result<(), Error> {
+ let args = node.args.as_ref().unwrap();
+
+ // Fetch the number or bytes to consume.
+ let n = self.evaluate_node(&args[0])?;
+ if n.size > 2 {
+ return Err(Error {
+ global: false,
+ line: node.value.line,
+ source: self.source_for(node),
+ message: "you are trying to reserve too much memory".to_string(),
+ });
+ }
+ let iterations = u16::from_le_bytes([n.bytes[0], n.bytes[1]]);
+ if iterations == 0 {
+ return Err(Error {
+ global: false,
+ line: node.value.line,
+ source: self.source_for(node),
+ message: "empty .res statement".to_string(),
+ });
+ } else if iterations == 1 {
+ self.warnings.push(Error {
+ global: false,
+ line: node.value.line,
+ source: self.source_for(node),
+ message: "pointless .res statement, prefer using .byte".to_string(),
+ });
+ }
+
+ // Check whether the fill value was provided. If that's not the case,
+ // then fetch it from the current mapping.
+ let fill = match args.get(1) {
+ Some(fill) => {
+ let bundle = self.evaluate_node(fill)?;
+ if bundle.size > 1 {
+ return Err(Error {
+ global: false,
+ line: node.value.line,
+ source: self.source_for(node),
+ message: "fill value must fit into a single byte".to_string(),
+ });
+ }
+ bundle.bytes[0]
+ }
+ None => {
+ let mapping = &self.mappings[self.current_mapping];
+ mapping.fill.unwrap_or(0x00)
+ }
+ };
+
+ // And push as many fill bundles as requested with the evaluated fill
+ // value.
+ for _i in 0..iterations {
+ self.push_bundle(Bundle::fill(fill), node)?;
+ }
+
+ 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
@@ -2999,6 +3062,40 @@ jsr Movement::update
}
#[test]
+ fn reserve_memory() {
+ let res = just_bundles(
+ r#".res 2
+.res 3, $02
+.res 2, $00"#,
+ );
+
+ assert_eq!(res.len(), 7);
+
+ assert_eq!(res[0].value(), 0);
+ assert_eq!(res[1].value(), 0);
+ assert_eq!(res[2].value(), 2);
+ assert_eq!(res[3].value(), 2);
+ assert_eq!(res[4].value(), 2);
+ assert_eq!(res[5].value(), 0);
+ assert_eq!(res[6].value(), 0);
+ }
+
+ #[test]
+ fn bad_reserve_memory() {
+ let mut res = just_assemble(".res 0, $00");
+
+ assert_eq!(res.errors.len(), 1);
+ assert_eq!(res.errors[0].to_string(), "empty .res statement (line 4)");
+
+ res = just_assemble(".res 1, $00");
+ assert_eq!(res.warnings.len(), 1);
+ assert_eq!(
+ res.warnings[0].to_string(),
+ "pointless .res statement, prefer using .byte (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 1ec2ed9..8fc3c73 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -141,6 +141,7 @@ pub enum ControlType {
StartRepeat,
EndRepeat,
IncludeSource,
+ ReserveMemory,
}
impl fmt::Display for ControlType {
@@ -162,6 +163,7 @@ impl fmt::Display for ControlType {
ControlType::StartRepeat => write!(f, ".repeat"),
ControlType::EndRepeat => write!(f, ".endrepeat"),
ControlType::IncludeSource => write!(f, ".include"),
+ ControlType::ReserveMemory => write!(f, ".res"),
}
}
}
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index 593d279..455a8ea 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -749,6 +749,7 @@ lazy_static! {
functions.insert(String::from(".repeat"), Control { control_type: ControlType::StartRepeat, has_identifier: Some(true), required_args: Some((1, 2)), touches_context: true });
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
};