// SPDX-License-Identifier: MIT // Copyright Murad Karammaev, Nikita Kuzmin use crate::instruction::{decode, encode, Instruction, Register, ShiftMode}; use regex::Regex; use std::{ error::Error, fmt::{Display, Formatter}, num, ops::RangeInclusive, }; use ux::{u2, u3, u4}; type InstructionDecoder = fn(&str, &Regex) -> Result; const REGEX_REG: &str = r"(R[01])"; const REGEX_REG_HALF: &str = r"(R[01]\.[lh])"; const REGEX_NUM: &str = r"(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})"; const REGEX_INUM: &str = r"(-?\d{1,3}|-?0b[01]{1,8}|-?0o[0-7]{1,3}|-?0x[\da-fA-F]{1,2})"; const REGEX_ADDR: &str = r"\[(\d{1,3}|0b[01]{1,8}|0o[0-7]{1,3}|0x[\da-fA-F]{1,2})\]"; const REGEX_COMMA: &str = r"\s*,\s*"; const REGEX_CARRY: &str = r"([01])"; macro_rules! asm_entry { ($regex:expr, $handler:expr) => { (Regex::new(&$regex).unwrap(), $handler) }; } #[derive(Debug, Clone)] enum AssemblerErrorKind { TooMuchInstructions, UnknownInstruction { instruction: String, }, InvalidRegister { register: String, }, InvalidRegisterHalf { register_half: String, }, InvalidCarry { carry: String, }, NumberOutOfRange { number: i128, range: RangeInclusive, }, InvalidInteger { parse_int_error: num::ParseIntError, }, InvalidRelativeJumpOffset { jump_offset: i8, }, MalformedRegisterToRegisterCopy, } impl AssemblerErrorKind { fn unknown_instruction(instruction: &str) -> AssemblerErrorKind { AssemblerErrorKind::UnknownInstruction { instruction: instruction.to_string(), } } fn invalid_register(register: &str) -> AssemblerErrorKind { AssemblerErrorKind::InvalidRegister { register: register.to_string(), } } fn invalid_register_half(register_half: &str) -> AssemblerErrorKind { AssemblerErrorKind::InvalidRegisterHalf { register_half: register_half.to_string(), } } fn invalid_carry(carry: &str) -> AssemblerErrorKind { AssemblerErrorKind::InvalidCarry { carry: carry.to_string(), } } fn number_out_of_range + Copy>( number: T, range: RangeInclusive, ) -> AssemblerErrorKind { AssemblerErrorKind::NumberOutOfRange { number: number.into(), // am I doing this right? range: (*range.start()).into()..=(*range.end()).into(), } } fn invalid_relative_jump_offset(jump_offset: i8) -> AssemblerErrorKind { AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } } fn malformed_register_to_register_copy() -> AssemblerErrorKind { AssemblerErrorKind::MalformedRegisterToRegisterCopy } fn to_assembler_error(&self, line: usize) -> AssemblerError { AssemblerError { line, error: self.clone(), } } } impl From for AssemblerErrorKind { fn from(parse_int_error: num::ParseIntError) -> AssemblerErrorKind { AssemblerErrorKind::InvalidInteger { parse_int_error } } } impl Display for AssemblerErrorKind { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!( f, "{}", match &self { AssemblerErrorKind::TooMuchInstructions => String::from("too much instructions"), AssemblerErrorKind::UnknownInstruction { instruction } => format!("unknown instruction: '{}'", instruction), AssemblerErrorKind::InvalidRegister { register } => format!("invalid register: '{}'", register), AssemblerErrorKind::InvalidRegisterHalf { register_half } => format!("invalid register half: '{}'", register_half), AssemblerErrorKind::InvalidCarry { carry } => format!("invalid carry: '{}'", carry), AssemblerErrorKind::NumberOutOfRange { number, range } => format!( "number {} is out of range [{}, {}]", number, range.start(), range.end() ), AssemblerErrorKind::InvalidInteger { parse_int_error } => format!("failed to parse integer: {}", parse_int_error), AssemblerErrorKind::InvalidRelativeJumpOffset { jump_offset } => format!("{} is not a valid relative jump offset", jump_offset), AssemblerErrorKind::MalformedRegisterToRegisterCopy => String::from("malformed register to register copy"), } ) } } impl Error for AssemblerErrorKind { fn description(&self) -> &str { match self { AssemblerErrorKind::TooMuchInstructions => "Too much instructions", AssemblerErrorKind::UnknownInstruction { .. } => "Unknown instruction", AssemblerErrorKind::InvalidRegister { .. } => "Invalid register", AssemblerErrorKind::InvalidRegisterHalf { .. } => "Invalid register half", AssemblerErrorKind::InvalidCarry { .. } => "Invalid carry", AssemblerErrorKind::NumberOutOfRange { .. } => "Number out of range", AssemblerErrorKind::InvalidInteger { .. } => "Failed to parse integer", AssemblerErrorKind::InvalidRelativeJumpOffset { .. } => "Invalid relative jump offset", AssemblerErrorKind::MalformedRegisterToRegisterCopy => { "Malformed register to register copy" } } } } #[derive(Debug)] pub struct AssemblerError { line: usize, error: AssemblerErrorKind, } type AssemblerResult = Result; impl Display for AssemblerError { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!(f, "Failed to assemble, line {}: {}", self.line, self.error) } } impl Error for AssemblerError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.error) } } impl AssemblerError { fn too_much_instructions(line: usize) -> AssemblerError { AssemblerError { line, error: AssemblerErrorKind::TooMuchInstructions, } } } pub struct Assembler { table: Vec<(Regex, InstructionDecoder)>, } impl Assembler { pub fn new() -> Self { Assembler { table: vec![ asm_entry!(format!(r"^BYTE\s{}$", REGEX_NUM), parse_byte), asm_entry!( format!(r"^MOV\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_ADDR), parse_mov_reg_mem ), asm_entry!( format!(r"^MOV\s{}{}{}$", REGEX_ADDR, REGEX_COMMA, REGEX_REG), parse_mov_mem_reg ), asm_entry!( format!(r"^MOV\s{}{}{}$", REGEX_REG_HALF, REGEX_COMMA, REGEX_NUM), parse_mov_reg_half_imm ), asm_entry!( format!(r"^MOV\s{}{}{}", REGEX_REG, REGEX_COMMA, REGEX_REG), parse_mov_reg_reg ), asm_entry!(format!(r"^JMP\s{}$", REGEX_INUM), parse_jmp), asm_entry!(format!(r"^JMPC\s{}$", REGEX_INUM), parse_jmpc), asm_entry!(format!(r"^AJMP\s{}$", REGEX_REG), parse_ajmp), asm_entry!(format!(r"^AJMPC\s{}$", REGEX_REG), parse_ajmpc), asm_entry!(r"^EQ$", |_, _| Ok(Instruction::CmpEqual)), asm_entry!(r"^GT$", |_, _| Ok(Instruction::CmpGreater)), asm_entry!(r"^LE$", |_, _| Ok(Instruction::CmpLess)), asm_entry!(r"^NOT$", |_, _| Ok(Instruction::Not)), asm_entry!(r"^SHR$", |_, _| Ok(Instruction::BitShiftVar { mode: ShiftMode::Logical, right: true })), asm_entry!(r"^SHL$", |_, _| Ok(Instruction::BitShiftVar { mode: ShiftMode::Logical, right: false })), asm_entry!(r"^ROTR$", |_, _| Ok(Instruction::BitShiftVar { mode: ShiftMode::Circular, right: true })), asm_entry!(r"^ROTL$", |_, _| Ok(Instruction::BitShiftVar { mode: ShiftMode::Circular, right: false })), asm_entry!( format!(r"^SHR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), parse_shr ), asm_entry!( format!(r"^SHL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), parse_shl ), asm_entry!( format!(r"^ROTR\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), parse_rotr ), asm_entry!( format!(r"^ROTL\s{}{}{}$", REGEX_REG, REGEX_COMMA, REGEX_NUM), parse_rotl ), asm_entry!(format!(r"^INC\s{}$", REGEX_REG), parse_inc), asm_entry!(format!(r"^DEC\s{}$", REGEX_REG), parse_dec), asm_entry!(r"^ADD$", |_, _| Ok(Instruction::Add)), asm_entry!(r"^SUB$", |_, _| Ok(Instruction::Sub)), asm_entry!(r"^AND$", |_, _| Ok(Instruction::And)), asm_entry!(r"^OR$", |_, _| Ok(Instruction::Or)), asm_entry!(r"^XOR$", |_, _| Ok(Instruction::Xor)), asm_entry!(r"^XNOR$", |_, _| Ok(Instruction::Xnor)), asm_entry!(format!(r"^COMPL\s{}$", REGEX_REG), parse_compl), asm_entry!(format!(r"^SETC\s{}$", REGEX_CARRY), parse_setc), asm_entry!(r"^NOP$", |_, _| Ok(Instruction::Nop)), asm_entry!(r"^HALT$", |_, _| Ok(Instruction::Halt)), asm_entry!(format!(r"^CLOAD\s{}$", REGEX_REG), parse_cload), asm_entry!(format!(r"^ZERO\s{}$", REGEX_REG), parse_zero), asm_entry!(r"^DIV$", |_, _| Ok(Instruction::Div)), asm_entry!(r"^MUL$", |_, _| Ok(Instruction::Mul)), asm_entry!(r"^SWAP$", |_, _| Ok(Instruction::Swap)), asm_entry!(r"^PRTCHK$", |_, _| Ok(Instruction::PortReady)), asm_entry!(r"^PRTRD$", |_, _| Ok(Instruction::ReadPort)), asm_entry!(r"^PRTWR$", |_, _| Ok(Instruction::WritePort)), ], } } pub fn assemble(&self, input: &str) -> AssemblerResult<[u8; 0x100]> { let mut ret = [0u8; 0x100]; let mut i: usize = 0; for (line_num, line) in input .lines() .map(|line| strip_comment(line).trim()) .enumerate() { match line { "" => (), line => { if i > 0xFF { return Err(AssemblerError::too_much_instructions(line_num)); } let insn = self .line_to_insn(line) .map_err(|e| e.to_assembler_error(line_num))?; ret[i] = encode(insn); i += 1; } } } Ok(ret) } fn line_to_insn(&self, line: &str) -> Result { for (regex, handler) in &self.table { if regex.is_match(line) { return handler(line, regex); } } Err(AssemblerErrorKind::unknown_instruction(line)) } } impl Default for Assembler { fn default() -> Self { Self::new() } } fn strip_comment(input: &str) -> &str { match input.find('#') { Some(x) => &input[0..x], None => input, } } fn parse_u128(src: &str) -> Result { if let Some(s) = src.strip_prefix("0b") { Ok(u128::from_str_radix(s, 2)?) } else if let Some(s) = src.strip_prefix("0o") { Ok(u128::from_str_radix(s, 8)?) } else if let Some(s) = src.strip_prefix("0x") { Ok(u128::from_str_radix(s, 16)?) } else { Ok(src.parse()?) } } fn parse_u8(src: &str) -> Result { match parse_u128(src)? { x @ 0..=255 => Ok(x as u8), x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=255)), } } fn parse_i8(src: &str) -> Result { let (sign, num): (i128, u128) = match src.strip_prefix('-') { Some(s) => (-1, parse_u128(s)?), None => (1, parse_u128(src)?), }; match (sign, num) { (1, x) if x <= 127 => Ok((x as i128 * sign) as i8), (-1, x) if x <= 128 => Ok((x as i128 * sign) as i8), (_, x) => Err(AssemblerErrorKind::number_out_of_range( x as i128, -128..=127, )), } } fn parse_u4(src: &str) -> Result { match parse_u8(src)? { x @ 0..=15 => Ok(u4::new(x)), x => Err(AssemblerErrorKind::number_out_of_range(x as i128, 0..=15)), } } fn parse_u3(src: &str) -> Result { match parse_u8(src)? { x @ 0..=7 => Ok(u3::new(x)), x => Err(AssemblerErrorKind::number_out_of_range(x, 0..=7)), } } fn parse_reg(src: &str) -> Result { match src { "R0" => Ok(Register::R0), "R1" => Ok(Register::R1), x => Err(AssemblerErrorKind::invalid_register(x)), } } fn parse_reg_half(src: &str) -> Result<(Register, bool), AssemblerErrorKind> { match src { "R0.l" => Ok((Register::R0, true)), "R0.h" => Ok((Register::R0, false)), "R1.l" => Ok((Register::R1, true)), "R1.h" => Ok((Register::R1, false)), x => Err(AssemblerErrorKind::invalid_register_half(x)), } } fn parse_carry(src: &str) -> Result { match src { "0" => Ok(false), "1" => Ok(true), x => Err(AssemblerErrorKind::invalid_carry(x)), } } fn parse_byte(line: &str, regex: &Regex) -> Result { Ok(decode(parse_u8( regex.captures(line).unwrap().get(1).unwrap().as_str(), )?)) } fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result { let captures = regex.captures(line).unwrap(); let reg = parse_reg(captures.get(1).unwrap().as_str())?; let addr = parse_u4(captures.get(2).unwrap().as_str())?; Ok(Instruction::Load { reg, addr }) } fn parse_mov_mem_reg(line: &str, regex: &Regex) -> Result { let captures = regex.captures(line).unwrap(); let addr = parse_u4(captures.get(1).unwrap().as_str())?; let reg = parse_reg(captures.get(2).unwrap().as_str())?; Ok(Instruction::Store { reg, addr }) } fn parse_mov_reg_half_imm(line: &str, regex: &Regex) -> Result { let captures = regex.captures(line).unwrap(); let (reg, low) = parse_reg_half(captures.get(1).unwrap().as_str())?; let val = parse_u4(captures.get(2).unwrap().as_str())?; Ok(Instruction::LoadImmediate { low, reg, val }) } fn parse_mov_reg_reg(line: &str, regex: &Regex) -> Result { let captures = regex.captures(line).unwrap(); let reg_to = parse_reg(captures.get(1).unwrap().as_str())?; let reg_from = parse_reg(captures.get(2).unwrap().as_str())?; match (reg_to, reg_from) { (Register::R0, Register::R1) => Ok(Instruction::Copy { reg_from: Register::R1, }), (Register::R1, Register::R0) => Ok(Instruction::Copy { reg_from: Register::R0, }), _ => Err(AssemblerErrorKind::malformed_register_to_register_copy()), } } fn parse_jmp_impl( line: &str, regex: &Regex, use_carry: bool, ) -> Result { match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? { x @ -3..=-1 => Ok(Instruction::NearJumpBackward { use_carry, offset: u2::new(-x as u8), }), x @ 0..=7 => Ok(Instruction::NearJumpForward { use_carry, offset: u3::new(x as u8), }), x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)), } } fn parse_jmp(line: &str, regex: &Regex) -> Result { parse_jmp_impl(line, regex, false) } fn parse_jmpc(line: &str, regex: &Regex) -> Result { parse_jmp_impl(line, regex, true) } fn parse_ajmp_impl( line: &str, regex: &Regex, use_carry: bool, ) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::FarJump { reg, use_carry }) } fn parse_ajmp(line: &str, regex: &Regex) -> Result { parse_ajmp_impl(line, regex, false) } fn parse_ajmpc(line: &str, regex: &Regex) -> Result { parse_ajmp_impl(line, regex, true) } fn parse_shift( line: &str, regex: &Regex, right: bool, mode: ShiftMode, ) -> Result { let captures = regex.captures(line).unwrap(); let reg = parse_reg(captures.get(1).unwrap().as_str())?; let len = parse_u3(captures.get(2).unwrap().as_str())?; Ok(Instruction::BitShift { right, reg, mode, len, }) } fn parse_shr(line: &str, regex: &Regex) -> Result { parse_shift(line, regex, true, ShiftMode::Logical) } fn parse_shl(line: &str, regex: &Regex) -> Result { parse_shift(line, regex, false, ShiftMode::Logical) } fn parse_rotr(line: &str, regex: &Regex) -> Result { parse_shift(line, regex, true, ShiftMode::Circular) } fn parse_rotl(line: &str, regex: &Regex) -> Result { parse_shift(line, regex, false, ShiftMode::Circular) } fn parse_inc(line: &str, regex: &Regex) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Inc { reg }) } fn parse_dec(line: &str, regex: &Regex) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Dec { reg }) } fn parse_compl(line: &str, regex: &Regex) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Complement { reg }) } fn parse_setc(line: &str, regex: &Regex) -> Result { let carry = parse_carry(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Setc { carry }) } fn parse_cload(line: &str, regex: &Regex) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Cload { reg }) } fn parse_zero(line: &str, regex: &Regex) -> Result { let reg = parse_reg(regex.captures(line).unwrap().get(1).unwrap().as_str())?; Ok(Instruction::Zero { reg }) } #[cfg(test)] mod tests { use crate::{ assembler::{strip_comment, Assembler}, instruction::{decode, Instruction, Register, ShiftMode}, }; use lazy_static::lazy_static; use std::fmt::{Binary, Display, LowerHex, Octal}; use ux::{u2, u3, u4}; lazy_static! { static ref ASM: Assembler = Assembler::new(); } enum IntegerFormat { Decimal, Binary, Octal, Hexadecimal, None, } struct PrintIntegers { num: T, format: IntegerFormat, } impl PrintIntegers { fn new(num: T) -> Self { PrintIntegers { num, format: IntegerFormat::Decimal, } } } impl Iterator for PrintIntegers { type Item = String; fn next(&mut self) -> Option { match self.format { IntegerFormat::Decimal => { self.format = IntegerFormat::Binary; Some(format!("{}", self.num)) } IntegerFormat::Binary => { self.format = IntegerFormat::Octal; Some(format!("{:#b}", self.num)) } IntegerFormat::Octal => { self.format = IntegerFormat::Hexadecimal; Some(format!("{:#o}", self.num)) } IntegerFormat::Hexadecimal => { self.format = IntegerFormat::None; Some(format!("{:#x}", self.num)) } IntegerFormat::None => None, } } } macro_rules! make_test_insn_no_args { ($name:ident, $line:expr, $insn:expr) => { #[test] fn $name() { assert_eq!(ASM.line_to_insn($line).unwrap(), $insn); } }; } make_test_insn_no_args!(asm_line_eq, "EQ", Instruction::CmpEqual); make_test_insn_no_args!(asm_line_gt, "GT", Instruction::CmpGreater); make_test_insn_no_args!(asm_line_le, "LE", Instruction::CmpLess); make_test_insn_no_args!(asm_line_not, "NOT", Instruction::Not); make_test_insn_no_args!(asm_line_add, "ADD", Instruction::Add); make_test_insn_no_args!(asm_line_sub, "SUB", Instruction::Sub); make_test_insn_no_args!(asm_line_and, "AND", Instruction::And); make_test_insn_no_args!(asm_line_or, "OR", Instruction::Or); make_test_insn_no_args!(asm_line_xor, "XOR", Instruction::Xor); make_test_insn_no_args!(asm_line_xnor, "XNOR", Instruction::Xnor); make_test_insn_no_args!(asm_line_nop, "NOP", Instruction::Nop); make_test_insn_no_args!(asm_line_halt, "HALT", Instruction::Halt); make_test_insn_no_args!(asm_line_div, "DIV", Instruction::Div); make_test_insn_no_args!(asm_line_mul, "MUL", Instruction::Mul); make_test_insn_no_args!(asm_line_swap, "SWAP", Instruction::Swap); make_test_insn_no_args!(asm_line_prtchk, "PRTCHK", Instruction::PortReady); make_test_insn_no_args!(asm_line_prtrd, "PRTRD", Instruction::ReadPort); make_test_insn_no_args!(asm_line_prtwr, "PRTWR", Instruction::WritePort); make_test_insn_no_args!( asm_line_shr, "SHR", Instruction::BitShiftVar { mode: ShiftMode::Logical, right: true } ); make_test_insn_no_args!( asm_line_shl, "SHL", Instruction::BitShiftVar { mode: ShiftMode::Logical, right: false } ); make_test_insn_no_args!( asm_line_rotr, "ROTR", Instruction::BitShiftVar { mode: ShiftMode::Circular, right: true } ); make_test_insn_no_args!( asm_line_rotl, "ROTL", Instruction::BitShiftVar { mode: ShiftMode::Circular, right: false } ); #[test] fn asm_line_byte() { for i in 0..=255u8 { for j in PrintIntegers::new(i) { assert_eq!(ASM.line_to_insn(&format!("BYTE {}", j)).unwrap(), decode(i)); } } } #[test] fn asm_line_mov_reg_mem() { for (reg_str, reg) in [("R0", Register::R0)] { for i in 0..=15u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("MOV {}, [{}]", reg_str, j)) .unwrap(), Instruction::Load { reg: reg.clone(), addr: u4::new(i) } ); } } } } #[test] fn asm_line_mov_mem_reg() { for (reg_str, reg) in [("R0", Register::R0)] { for i in 0..=15u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("MOV [{}], {}", j, reg_str)) .unwrap(), Instruction::Store { reg: reg.clone(), addr: u4::new(i) } ); } } } } #[test] fn asm_line_mov_reg_half_imm() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { for (low_str, low) in [("l", true), ("h", false)] { for i in 0..=15 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("MOV {}.{}, {}", reg_str, low_str, j)) .unwrap(), Instruction::LoadImmediate { low, reg: reg.clone(), val: u4::new(i) } ); } } } } } #[test] fn asm_line_jmp() { for i in 0..=7u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("JMP {}", j)).unwrap(), Instruction::NearJumpForward { use_carry: false, offset: u3::new(i) } ) } } for i in 1..=3u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("JMP -{}", j)).unwrap(), Instruction::NearJumpBackward { use_carry: false, offset: u2::new(i) } ) } } } #[test] fn asm_line_jmpc() { for i in 0..=7u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("JMPC {}", j)).unwrap(), Instruction::NearJumpForward { use_carry: true, offset: u3::new(i) } ) } } for i in 1..=3u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("JMPC -{}", j)).unwrap(), Instruction::NearJumpBackward { use_carry: true, offset: u2::new(i) } ) } } } #[test] fn asm_lime_ajmp() { assert_eq!( ASM.line_to_insn("AJMP R0").unwrap(), Instruction::FarJump { reg: Register::R0, use_carry: false } ); assert_eq!( ASM.line_to_insn("AJMP R1").unwrap(), Instruction::FarJump { reg: Register::R1, use_carry: false } ); } #[test] fn asm_lime_ajmpc() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("AJMPC {}", reg_str)).unwrap(), Instruction::FarJump { reg, use_carry: true } ); } } #[test] fn asm_line_shift_rotate() { for (insn, right, mode) in [ ("SHR", true, ShiftMode::Logical), ("SHL", false, ShiftMode::Logical), ("ROTR", true, ShiftMode::Circular), ("ROTL", false, ShiftMode::Circular), ] { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { for i in 0..7u8 { for j in PrintIntegers::new(i) { assert_eq!( ASM.line_to_insn(&format!("{} {}, {}", insn, reg_str, j)) .unwrap(), Instruction::BitShift { right, reg: reg.clone(), mode: mode.clone(), len: u3::new(i) } ) } } } } } #[test] fn asm_line_inc() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("INC {}", reg_str)).unwrap(), Instruction::Inc { reg } ); } } #[test] fn asm_line_dec() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("DEC {}", reg_str)).unwrap(), Instruction::Dec { reg } ); } } #[test] fn asm_line_compl() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("COMPL {}", reg_str)).unwrap(), Instruction::Complement { reg } ); } } #[test] fn asm_line_setc() { for (i, carry) in [(0, false), (1, true)] { assert_eq!( ASM.line_to_insn(&format!("SETC {}", i)).unwrap(), Instruction::Setc { carry } ) } } #[test] fn asm_line_cload() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("CLOAD {}", reg_str)).unwrap(), Instruction::Cload { reg } ); } } #[test] fn asm_line_zero() { for (reg_str, reg) in [("R0", Register::R0), ("R1", Register::R1)] { assert_eq!( ASM.line_to_insn(&format!("ZERO {}", reg_str)).unwrap(), Instruction::Zero { reg } ); } } #[test] fn asm_assemble_empty() { assert_eq!(ASM.assemble("").unwrap(), [0u8; 256]); } #[test] fn asm_assemble() { let expected = { let mut code = [0u8; 256]; code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); code }; assert_eq!( ASM.assemble( r#" MOV R0.l, 0xF MOV R1.l, 0x3 ADD MOV [0], R0 ZERO R0 HALT "# ) .unwrap(), expected ); } #[test] fn asm_strip_comment() { assert_eq!(strip_comment("MOV [2], R1 # comment"), "MOV [2], R1 "); } #[test] fn asm_assemble_comments() { let expected = { let mut code = [0u8; 256]; code[0..6].copy_from_slice(&[0x4F, 0x53, 0xE4, 0x20, 0xF8, 0xF5]); code }; assert_eq!( ASM.assemble( r#" MOV R0.l, 0xF # R0 = 15 MOV R1.l, 0x3 # R1 = 3 ADD # R0 += R1 (18) MOV [0], R0 # MEM[0] = R0 (18) ZERO R0 # R0 = 0 HALT # EXIT "# ) .unwrap(), expected ); } }