aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-04-24 23:12:51 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-04-24 23:12:51 +0200
commitee9d61f078b04406c5b264d4e0e5bd1ea287e6e2 (patch)
tree23dfc1e95c7e5d9ff871a044f4f4e3773f3ecbe1
parent948228f0e250d191a97359e97faa6ee4934e9e7a (diff)
downloadtools.nes-ee9d61f078b04406c5b264d4e0e5bd1ea287e6e2.tar.gz
tools.nes-ee9d61f078b04406c5b264d4e0e5bd1ea287e6e2.zip
Add the .min and the .max control expressions
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
-rw-r--r--lib/xixanta/src/assembler.rs67
-rw-r--r--lib/xixanta/src/node.rs4
-rw-r--r--lib/xixanta/src/opcodes.rs20
3 files changed, 91 insertions, 0 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index c9800d9..a25ba41 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -2391,6 +2391,8 @@ impl<'a> Assembler<'a> {
NodeType::Control(ControlType::Lobyte) => self.evaluate_byte(node, false),
NodeType::Control(ControlType::Defined) => self.evaluate_defined(node),
NodeType::Control(ControlType::Version) => self.evaluate_version(node),
+ NodeType::Control(ControlType::Max) => self.evaluate_max(node),
+ NodeType::Control(ControlType::Min) => self.evaluate_min(node),
_ => Err(Error {
line: node.value.line,
message: format!(
@@ -2404,6 +2406,47 @@ impl<'a> Assembler<'a> {
}
}
+ // Evaluate the args from 'node' and return the bundle from the node which
+ // contains the maximum value.
+ fn evaluate_max(&mut self, node: &PNode) -> Result<Bundle, Error> {
+ let Some(args) = &node.args else {
+ return Ok(Bundle::default());
+ };
+
+ let mut max = Bundle::default();
+ for arg in args {
+ self.literal_mode = None;
+ let bundle = self.evaluate_node(arg)?;
+
+ if bundle.value() > max.value() {
+ max = bundle.clone();
+ }
+ }
+ Ok(max)
+ }
+
+ // Evaluate the args from 'node' and return the bundle from the node which
+ // contains the minimum value.
+ fn evaluate_min(&mut self, node: &PNode) -> Result<Bundle, Error> {
+ let Some(args) = &node.args else {
+ return Ok(Bundle::default());
+ };
+
+ let mut min = Bundle::default();
+ min.bytes[0] = 0xFF;
+ min.bytes[1] = 0xFF;
+
+ for arg in args {
+ self.literal_mode = None;
+ let bundle = self.evaluate_node(arg)?;
+
+ if bundle.value() < min.value() {
+ min = bundle.clone();
+ }
+ }
+ Ok(min)
+ }
+
fn evaluate_byte(&mut self, node: &PNode, high: bool) -> Result<Bundle, Error> {
// The parser actually guarantees that the ".hibyte" and ".lobyte"
// functions have exactly one argument. Hence, if this is not the case,
@@ -4730,6 +4773,30 @@ nop
}
#[test]
+ fn min_max() {
+ let res = just_bundles(
+ r#"
+one = 1
+two = 2
+
+lda #.min(one, two, three)
+lda #.max(one, two, three)
+
+three = 3
+ "#,
+ );
+
+ assert_eq!(res.len(), 2);
+ let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x01], [0xA9, 0x03]];
+
+ for i in 0..instrs.len() {
+ assert_eq!(res[i].size, 2);
+ assert_eq!(res[i].bytes[0], instrs[i][0]);
+ assert_eq!(res[i].bytes[1], instrs[i][1]);
+ }
+ }
+
+ #[test]
fn warnings_on_empty_proc_scope_macro() {
let res = just_assemble(
r#".proc Proc
diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs
index ee3ae24..d91177e 100644
--- a/lib/xixanta/src/node.rs
+++ b/lib/xixanta/src/node.rs
@@ -156,6 +156,8 @@ pub enum ControlType {
Byte,
Word,
BigEndianWord,
+ Max,
+ Min,
Addr,
IncBin,
StartRepeat,
@@ -189,6 +191,8 @@ impl fmt::Display for ControlType {
ControlType::Byte => write!(f, ".byte/.db"),
ControlType::Word => write!(f, ".word/.dw"),
ControlType::BigEndianWord => write!(f, ".dbyt/.be/.bigendian"),
+ ControlType::Max => write!(f, ".max"),
+ ControlType::Min => write!(f, ".min"),
ControlType::Addr => write!(f, ".addr"),
ControlType::IncBin => write!(f, ".incbin"),
ControlType::StartRepeat => write!(f, ".repeat"),
diff --git a/lib/xixanta/src/opcodes.rs b/lib/xixanta/src/opcodes.rs
index 25b3add..bc4f1d4 100644
--- a/lib/xixanta/src/opcodes.rs
+++ b/lib/xixanta/src/opcodes.rs
@@ -1860,6 +1860,26 @@ pub static CONTROL_FUNCTIONS: LazyLock<HashMap<String, Control>> = LazyLock::new
},
);
functions.insert(
+ String::from(".max"),
+ Control {
+ control_type: ControlType::Max,
+ has_identifier: None,
+ required_args: None,
+ touches_context: false,
+ only_string: false,
+ },
+ );
+ functions.insert(
+ String::from(".min"),
+ Control {
+ control_type: ControlType::Min,
+ has_identifier: None,
+ required_args: None,
+ touches_context: false,
+ only_string: false,
+ },
+ );
+ functions.insert(
String::from(".incbin"),
Control {
control_type: ControlType::IncBin,