From 8b5feeed96b3110967f88420d058c6e916a67f60 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Thu, 19 Dec 2024 23:12:39 +0100 Subject: assembler: Implement and add tests for operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miquel Sabaté Solà --- lib/xixanta/src/assembler.rs | 199 +++++++++++++++++++++++++++++++++++++++++-- lib/xixanta/src/object.rs | 35 ++++++++ 2 files changed, 228 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index be10404..4e33d66 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -1,6 +1,6 @@ use crate::errors::{ContextError, Error, EvalError}; use crate::mapping::Mapping; -use crate::node::{ControlType, NodeType, PNode, PString}; +use crate::node::{ControlType, NodeType, OperationType, PNode, PString}; use crate::object::{Bundle, Context, Object, ObjectType}; use crate::opcodes::{AddressingMode, INSTRUCTIONS}; use crate::parser::Parser; @@ -8,7 +8,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fs::File; use std::io::Read; -use std::ops::Range; +use std::ops::{Neg, Range}; use std::path::PathBuf; /// The mode in which a literal is expressed. @@ -278,6 +278,7 @@ impl Assembler { cycles: 0, affected_on_page: false, resolved: false, + negative: false, }, mapping: self.current_mapping, segment: self.current_segment, @@ -526,10 +527,11 @@ impl Assembler { } fn evaluate_node(&mut self, node: &PNode) -> Result { - match node.node_type { + match &node.node_type { NodeType::Instruction => Ok(self.evaluate_instruction(node)?), NodeType::Literal => Ok(self.evaluate_literal(node)?), NodeType::Control(_) => Ok(self.evaluate_control_expression(node)?), + NodeType::Operation(op) => Ok(self.evaluate_operation(node, op)?), NodeType::Value => match self.literal_mode { Some(LiteralMode::Hexadecimal) => Ok(self.evaluate_hexadecimal(node)?), Some(LiteralMode::Binary) => Ok(self.evaluate_binary(node)?), @@ -577,6 +579,112 @@ impl Assembler { } } + // Evaluate the `node` by performing the operation described in + // `operation_type` onto its arms. + fn evaluate_operation( + &mut self, + node: &PNode, + operation_type: &OperationType, + ) -> Result { + let mut right = self.evaluate_node(node.right.as_ref().unwrap())?; + let rval = right.value(); + + let res: isize = match operation_type { + OperationType::UnaryPositive => { + right.negative = false; + rval.abs() + } + OperationType::UnaryNegative => { + right.negative = true; + rval.neg() + } + OperationType::BitwiseNot => !rval, + OperationType::LoByte => { + let r = (rval as u16).to_le_bytes(); + right.bytes[0] = r[0]; + right.bytes[1] = 0; + right.size = 1; + return Ok(right); + } + OperationType::HiByte => { + let r = (rval as u16).to_le_bytes(); + right.bytes[0] = r[1]; + right.bytes[1] = 0; + right.size = 1; + return Ok(right); + } + OperationType::Add => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval + rval + } + OperationType::Sub => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval - rval + } + OperationType::Mul => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval * rval + } + OperationType::Div => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval / rval + } + OperationType::And => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval & rval + } + OperationType::Or => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval | rval + } + OperationType::Xor => { + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval ^ rval + } + OperationType::Lshift => { + if rval > 16 { + return Err(EvalError { + line: node.value.line, + global: false, + message: "shift operator too big".to_string(), + }); + } + + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval << rval + } + OperationType::Rshift => { + if rval > 16 { + return Err(EvalError { + line: node.value.line, + global: false, + message: "shift operator too big".to_string(), + }); + } + + let lval = self.evaluate_node(node.left.as_ref().unwrap())?.value(); + lval >> rval + } + }; + + // Prevent overflows. + if res > i16::MAX.into() || res < i16::MIN.into() { + return Err(EvalError { + line: node.value.line, + global: false, + message: "performing the operation would overflow a 16-bit integer".to_string(), + }); + } + + // Set the computes bytes to right since that's the node in common + // across all operations and return it. + let byte_result = (res as u16).to_le_bytes(); + right.bytes[0] = byte_result[0]; + right.bytes[1] = byte_result[1]; + + Ok(right) + } + fn evaluate_anonymous_relative_reference(&mut self, node: &PNode) -> Result { self.literal_mode = Some(LiteralMode::Plain); @@ -588,6 +696,7 @@ impl Assembler { cycles: 0, affected_on_page: false, resolved: false, + negative: false, }), Stage::Crunching => { match self.context.get_relative_label( @@ -661,6 +770,7 @@ impl Assembler { cycles: 0, affected_on_page: false, resolved: true, + negative: false, }) } @@ -719,6 +829,7 @@ impl Assembler { cycles: 0, affected_on_page: false, resolved: true, + negative: false, }) } @@ -795,6 +906,7 @@ impl Assembler { cycles: 0, affected_on_page: false, resolved: true, + negative: false, }) } @@ -1715,6 +1827,79 @@ lda #Scope::Variable ); } + #[test] + fn operations_with_variables() { + let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm + .assemble( + std::env::current_dir().unwrap().to_path_buf(), + r#" +Value = 4 +ldx #(2 + Value) +ldx #(2 - Value) +ldx #(2 * -Value) +ldx #(Value / 2) +ldx #(Value << 2) +ldx #~Value +"# + .as_bytes(), + ) + .unwrap()[0x10..]; + + assert_eq!(res.len(), 6); + + let expected = [ + [0x06, 0x00], + [0xFE, 0xFF], + [0xF8, 0xFF], + [0x02, 0x00], + [0x10, 0x00], + [0xFB, 0xFF], + ]; + for (idx, node) in res.iter().enumerate() { + assert_eq!(node.size, 2); + assert_eq!(node.bytes[0], 0xA2); + assert_eq!(node.bytes[1], expected[idx][0]); + assert_eq!(node.bytes[2], expected[idx][1]); + } + } + + #[test] + fn signed_with_variables() { + let mut asm = Assembler::new(EMPTY.to_vec()); + asm.mappings[0].segments[0].bundles = minimal_header(); + asm.mappings[0].offset = 6; + asm.current_mapping = 1; + let res = &asm + .assemble( + std::env::current_dir().unwrap().to_path_buf(), + r#" +Value = -2 +ldx #Value +ldx #+Value +"# + .as_bytes(), + ) + .unwrap()[0x10..]; + + assert_eq!(res.len(), 2); + + let neg = res.first().unwrap(); + assert_eq!(neg.size, 2); + assert_eq!(neg.bytes[0], 0xA2); + assert_eq!(neg.bytes[1], 0xFE); + assert_eq!(neg.bytes[2], 0xFF); + + let pos = res.last().unwrap(); + assert_eq!(pos.size, 2); + assert_eq!(pos.bytes[0], 0xA2); + assert_eq!(pos.bytes[1], 0x02); + assert_eq!(pos.bytes[2], 0x00); + } + // Regular instructions #[test] @@ -2295,16 +2480,18 @@ nop r#" Var = $2002 lda #.lobyte(Var) +lda #Var "# .as_bytes(), ) .unwrap()[0x10..]; - assert_eq!(res.len(), 2); - let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x02], [0xA9, 0x20]]; + assert_eq!(res.len(), 4); + let instrs: Vec<[u8; 2]> = vec![[0xA9, 0x02], [0xA9, 0x02], [0xA9, 0x20], [0xA9, 0x20]]; - for i in 0..2 { + for i in 0..4 { assert_eq!(res[i].size, 2); assert_eq!(res[i].bytes[0], instrs[i][0]); assert_eq!(res[i].bytes[1], instrs[i][1]); diff --git a/lib/xixanta/src/object.rs b/lib/xixanta/src/object.rs index 8f177a1..2708647 100644 --- a/lib/xixanta/src/object.rs +++ b/lib/xixanta/src/object.rs @@ -32,6 +32,12 @@ pub struct Bundle { /// Whether the bytes on `bytes` contain the final value or not. This is /// used for internal purposes only. pub resolved: bool, + + /// Whether we have to consider the bundle to contain a negative number. + /// This comes from the fact that we just have a bunch of signednessless + /// bytes, but using the negative unary operator will give us a hint on how + /// to interpret it when needed. + pub negative: bool, } impl Bundle { @@ -52,6 +58,35 @@ impl Bundle { cycles: 0, affected_on_page: false, resolved: true, + negative: false, + } + } + + /// Returns the `bytes` field from the bundle as interpreted as bytes from a + /// regular integer. + pub fn value(&self) -> isize { + if self.negative { + isize::from_le_bytes([ + self.bytes[0], + self.bytes[1], + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + ]) + } else { + isize::from_le_bytes([ + self.bytes[0], + self.bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) } } } -- cgit v1.2.3