aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta/src
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mikisabate@gmail.com>2025-08-18 17:06:47 +0200
committerMiquel Sabaté Solà <mikisabate@gmail.com>2025-08-18 17:06:47 +0200
commitfe811ed479363da1fbca171fd0b16adeba5f6476 (patch)
tree287b33223e9fb7d9c321edbe724710ce6b7848ea /lib/xixanta/src
parentfb0f163ba1f870158410a3851740101ecdc9d3ff (diff)
downloadtools.nes-fe811ed479363da1fbca171fd0b16adeba5f6476.tar.gz
tools.nes-fe811ed479363da1fbca171fd0b16adeba5f6476.zip
Provide a node to the Object struct
This allows the `evaluate_variable` to pull from it in the crunching stage so to evaluate the original node in cases like macro expansion, where the connection between the macro argument and the original caller might have been lost. Signed-off-by: Miquel Sabaté Solà <mikisabate@gmail.com>
Diffstat (limited to 'lib/xixanta/src')
-rw-r--r--lib/xixanta/src/assembler.rs60
-rw-r--r--lib/xixanta/src/object.rs16
2 files changed, 75 insertions, 1 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 40d2438..2b82008 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -247,6 +247,7 @@ impl<'a> Assembler<'a> {
};
let var_value = Object {
bundle: Bundle::fill(value),
+ node: None,
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
@@ -338,6 +339,7 @@ impl<'a> Assembler<'a> {
&node.value,
&Object {
bundle: value,
+ node: None,
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
@@ -481,6 +483,7 @@ impl<'a> Assembler<'a> {
resolved: false,
negative: false,
},
+ node: None,
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Address,
@@ -774,6 +777,7 @@ impl<'a> Assembler<'a> {
self.literal_mode = None;
let obj = Object {
bundle: self.evaluate_node(arg)?,
+ node: Some(arg.clone()),
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
@@ -1571,6 +1575,7 @@ impl<'a> Assembler<'a> {
&args.last().unwrap().value,
&Object {
bundle: Bundle::fill(i as u8),
+ node: None,
mapping: self.current_mapping,
segment: self.current_segment,
object_type: ObjectType::Value,
@@ -1908,7 +1913,31 @@ impl<'a> Assembler<'a> {
fn evaluate_variable(&mut self, node: &PNode) -> Result<Bundle, Error> {
match self.context.get_variable(&node.value, &self.mappings) {
- Ok(value) => Ok(value.bundle),
+ Ok(mut value) => {
+ // If the given variable has a zero value AND we are resolving
+ // pending nodes AND the variable has a node object in it, then
+ // chances are that this is a macro call with an argument to be
+ // resolved. Hence, the value is not this empty variable but the
+ // node to be resolved which is contained on this "variable".
+ if self.stage == Stage::Crunching && value.bundle.is_zero() && value.node.is_some()
+ {
+ // Evaluate the node, which has the proper (resolved at this
+ // stage) value.
+ let bundle = self.evaluate_node(&value.node.clone().unwrap())?;
+
+ // Overwrite the variable with this new value. This way the
+ // next time this is found we don't have to evaluate it
+ // again. If the variable could not be set, then it's not
+ // that big of a deal at this stage.
+ value.bundle = bundle.clone();
+ let _ = self.context.set_variable(&node.value, &value, true);
+
+ // And return the computed bundle.
+ Ok(bundle)
+ } else {
+ Ok(value.bundle)
+ }
+ }
Err(e) => Err(Error {
message: e,
line: node.value.line,
@@ -3284,6 +3313,35 @@ jsr Movement::update
assert_eq!(res[4].bytes[2], 0x80);
}
+ #[test]
+ fn jmp_by_using_address_in_macro() {
+ let res = just_bundles(
+ r#"
+.macro JAL ADDR
+ jmp ADDR
+.endmacro
+
+JAL procedure
+
+.proc procedure
+ rts
+.endproc
+ "#,
+ );
+
+ assert_eq!(res.len(), 2);
+
+ // JAL procedure
+ assert_eq!(res[0].size, 3);
+ assert_eq!(res[0].bytes[0], 0x4C);
+ assert_eq!(res[0].bytes[1], 0x03);
+ assert_eq!(res[0].bytes[2], 0x80);
+
+ // rts
+ assert_eq!(res[1].size, 1);
+ assert_eq!(res[1].bytes[0], 0x60);
+ }
+
// Control statements
#[test]
diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs
index db7c51e..373d06c 100644
--- a/lib/xixanta/src/object.rs
+++ b/lib/xixanta/src/object.rs
@@ -48,6 +48,16 @@ impl Bundle {
}
}
+ /// Returns true of this bundle has a zero value on its `bytes` attribute
+ /// while also taking into account the size of the bundle.
+ pub fn is_zero(&self) -> bool {
+ match self.size {
+ 1 => self.bytes[0] == 0,
+ 2 => self.bytes[0] == 0 && self.bytes[1] == 0,
+ _ => self.bytes[0] == 0 && self.bytes[1] == 0 && self.bytes[2] == 0,
+ }
+ }
+
/// Create a bundle tailored for filling purposes.
pub fn fill(value: u8) -> Self {
Self {
@@ -105,6 +115,11 @@ pub struct Object {
/// Bundle representing the actual value.
pub bundle: Bundle,
+ /// Node which marks the source of the computed `bundle` attribute. This is
+ /// only provided in cases like macro calls where at the crunching stage we
+ /// might need to fetch previous context for the current `bundle` value.
+ pub node: Option<PNode>,
+
/// 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.
@@ -124,6 +139,7 @@ impl Object {
pub fn new(mapping: usize, segment: usize, object_type: ObjectType) -> Self {
Self {
bundle: Bundle::default(),
+ node: None,
mapping,
segment,
object_type,