aboutsummaryrefslogtreecommitdiff
path: root/lib/xixanta
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xixanta')
-rw-r--r--lib/xixanta/src/assembler.rs187
1 files changed, 136 insertions, 51 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 326d97a..96fdd01 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -1448,6 +1448,9 @@ impl<'a> Assembler<'a> {
node: &PNode,
operation_type: &OperationType,
) -> Result<Bundle, Error> {
+ // We first evaluate the node on the right, as unary operations will
+ // only have a right node but not a left one. Moreover, this 'right'
+ // object will be the one that will be updated so it can be returned.
let mut right = self.evaluate_node(node.right.as_ref().unwrap())?;
let rval = right.value();
@@ -1456,17 +1459,22 @@ impl<'a> Assembler<'a> {
// node. Hence, reset it here to avoid problems.
self.literal_mode = None;
- let res: isize = match operation_type {
+ // Now perform the operation by using the right arm and the left one if
+ // needed. If there was indeed a left value computed underneath, then
+ // return it in the 'other' option. Otherwise 'res' contains the
+ // arithmetic value, which might be bananas if the operation was done
+ // between at least one unresolved value (see more on that below).
+ let (res, other): (isize, Option<Bundle>) = match operation_type {
OperationType::UnaryPositive => {
right.negative = false;
- rval.abs()
+ (rval.abs(), None)
}
OperationType::UnaryNegative => {
right.negative = true;
- rval.neg()
+ (rval.neg(), None)
}
- OperationType::LogicalNot => (rval == 0) as isize,
- OperationType::BitwiseNot => !rval,
+ OperationType::LogicalNot => ((rval == 0) as isize, None),
+ OperationType::BitwiseNot => (!rval, None),
OperationType::LoByte => {
let r = (rval as u16).to_le_bytes();
right.bytes[0] = r[0];
@@ -1482,16 +1490,16 @@ impl<'a> Assembler<'a> {
return Ok(right);
}
OperationType::Add => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval + rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() + rval, Some(lval))
}
OperationType::Sub => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval - rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() - rval, Some(lval))
}
OperationType::Mul => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval * rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() * rval, Some(lval))
}
OperationType::Div => {
if rval == 0 {
@@ -1503,28 +1511,28 @@ impl<'a> Assembler<'a> {
message: "attempting to divide by zero".to_string(),
});
}
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval / rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() / rval, Some(lval))
}
OperationType::LogicalAnd => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- ((lval != 0) && (rval != 0)) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (((lval.value() != 0) && (rval != 0)) as isize, Some(lval))
}
OperationType::And => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval & rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() & rval, Some(lval))
}
OperationType::LogicalOr => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- ((lval != 0) || (rval != 0)) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (((lval.value() != 0) || (rval != 0)) as isize, Some(lval))
}
OperationType::Or => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval | rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() | rval, Some(lval))
}
OperationType::Xor => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval ^ rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() ^ rval, Some(lval))
}
OperationType::Lshift => {
if rval as usize > 16 {
@@ -1537,8 +1545,8 @@ impl<'a> Assembler<'a> {
});
}
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval << rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() << rval, Some(lval))
}
OperationType::Rshift => {
if rval as usize > 16 {
@@ -1551,37 +1559,66 @@ impl<'a> Assembler<'a> {
});
}
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- lval >> rval
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ (lval.value() >> rval, Some(lval))
}
OperationType::Equal => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval == rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() == rval) as isize, Some(lval))
}
OperationType::NotEqual => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval != rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() != rval) as isize, Some(lval))
}
OperationType::Less => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval < rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() < rval) as isize, Some(lval))
}
OperationType::LessEqual => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval <= rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() <= rval) as isize, Some(lval))
}
OperationType::Greater => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval > rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() > rval) as isize, Some(lval))
}
OperationType::GreaterEqual => {
- let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value();
- (lval >= rval) as isize
+ let lval = self.evaluate_node(node.left.as_ref().unwrap())?;
+ ((lval.value() >= rval) as isize, Some(lval))
}
};
- // Prevent overflows.
- if res > i16::MAX.into() || res < i16::MIN.into() {
+ // There are some corrections to be performed into the 'right' bundle if
+ // there was another bundle involved in the operation.
+ if let Some(o) = other {
+ // Only mark this bundle as resolved if both sides of the operation
+ // were resolved values.
+ right.resolved = right.resolved && o.resolved;
+
+ // If the size is different, then pick the larger one (e.g. '$2000 +
+ // 1' is the same as '1 + $2000', which have a size of '2').
+ if right.size < o.size {
+ right.size = o.size;
+ }
+ }
+
+ // Now, regardless of what we did whenever an 'other' object was
+ // involved, we can do some final adjustments on the size of the Bundle
+ // to be returned if the value is finally resolved. This is done by hand
+ // as in some edge cases the size might be wrong just because one of the
+ // sides or both of the operation might do funky things to their sizes
+ // (e.g. '300 - 260' where both arms have values larger than a byte but
+ // the end result should be one byte).
+ if right.resolved {
+ if res > 0x00FF {
+ right.size = 2;
+ } else {
+ right.size = 1;
+ }
+ }
+
+ // Prevent overflows, but only if the value is known for sure.
+ if right.resolved && (res > i16::MAX.into() || res < i16::MIN.into()) {
return Err(Error {
line: node.value.line,
global: false,
@@ -1591,19 +1628,12 @@ impl<'a> Assembler<'a> {
});
}
- // Set the computes bytes to right since that's the node in common
- // across all operations and return it.
+ // And, regardless of the size, we will set the bytes of the Bundle as
+ // if it was a 16-bit value in little-endian format.
let byte_result = (res as u16).to_le_bytes();
right.bytes[0] = byte_result[0];
right.bytes[1] = byte_result[1];
- // If the operation makes the end result bigger than what 1 byte can fit
- // (or it already was bigger before this operation), then we have to
- // assume that this is a 16-bit value.
- if res > 0x00FF {
- right.size = 3;
- }
-
Ok(right)
}
@@ -2541,7 +2571,24 @@ impl<'a> Assembler<'a> {
// And return the computed bundle.
Ok(bundle)
} else {
- Ok(value.bundle)
+ // In most cases returning the fetched bundle would just be
+ // fine, but if it's not resolved then the caller might not
+ // be able to make assumptions on the end size, which is
+ // important because we might be at a state of
+ // 'Stage::Bundling' and we _must_ know the sizes of each
+ // instruction as addresses are being computed along the
+ // way. Hence, we might not know the value right now if it's
+ // not resolved yet, but if it's an address (which is known
+ // when defining the variable at a state of
+ // 'Stage::Context'), then we definitely know the size and
+ // the caller can make this assumption (which might
+ // determine a ZeroPageX or an IndirectX instruction, with a
+ // byte in size of difference).
+ let mut bundle = value.bundle;
+ if matches!(value.object_type, ObjectType::Address) {
+ bundle.size = 2;
+ }
+ Ok(bundle)
}
}
Err(e) => Err(Error {
@@ -3917,6 +3964,24 @@ sta m_var + 1
assert_eq!(res[1].bytes[2], 0x02);
}
+ #[test]
+ fn load_arithmetic_constant() {
+ let res = just_bundles(
+ r#"
+VAL1 = 300
+lda #(VAL1 - VAL2)
+VAL2 = 260
+ "#,
+ );
+
+ assert_eq!(res.len(), 1);
+
+ assert_eq!(res[0].size, 2);
+ assert_eq!(res[0].bytes[0], 0xA9);
+ assert_eq!(res[0].bytes[1], 40);
+ assert_eq!(res[0].bytes[2], 0x00);
+ }
+
// Labels & branching
#[test]
@@ -4141,6 +4206,26 @@ sta m_var + 1
}
#[test]
+ fn label_in_instruction_addressing_with_arithmetics() {
+ let res = just_bundles(
+ r#"
+ ldx #0
+ @load_palettes_loop:
+ lda palettes + 1, x
+ palettes:
+ .byte $0F, $12, $22, $32
+ "#,
+ );
+
+ assert_instruction("ldx #0", &res[0].bytes);
+ assert_instruction("lda $8006, x", &res[1].bytes);
+ assert_eq!(&res[2].bytes, &[0x0F, 0x00, 0x00]);
+ assert_eq!(&res[3].bytes, &[0x12, 0x00, 0x00]);
+ assert_eq!(&res[4].bytes, &[0x22, 0x00, 0x00]);
+ assert_eq!(&res[5].bytes, &[0x32, 0x00, 0x00]);
+ }
+
+ #[test]
fn full_to_zeropage_optimization() {
assert_instruction("sta $0020", &[0x85, 0x20]);
assert_instruction("sty $021, x", &[0x94, 0x21]);