4-bit virtual CPU
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

883 lines
29 KiB

// 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::{u3, u4};
type InstructionDecoder = fn(&str, &Regex) -> Result<Instruction, AssemblerErrorKind>;
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_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<i128>,
},
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<T: Into<i128> + Copy>(
number: T,
range: RangeInclusive<T>,
) -> 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<num::ParseIntError> 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<T> = Result<T, AssemblerError>;
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_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)),
asm_entry!(r"^LOAD$", |_, _| Ok(Instruction::Load)),
asm_entry!(r"^STORE$", |_, _| Ok(Instruction::Store)),
],
}
}
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<Instruction, AssemblerErrorKind> {
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<u128, num::ParseIntError> {
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<u8, AssemblerErrorKind> {
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<i8, AssemblerErrorKind> {
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<u4, AssemblerErrorKind> {
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<u3, AssemblerErrorKind> {
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<Register, AssemblerErrorKind> {
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<bool, AssemblerErrorKind> {
match src {
"0" => Ok(false),
"1" => Ok(true),
x => Err(AssemblerErrorKind::invalid_carry(x)),
}
}
fn parse_byte(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
Ok(decode(parse_u8(
regex.captures(line).unwrap().get(1).unwrap().as_str(),
)?))
}
fn parse_mov_reg_half_imm(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
match parse_i8(regex.captures(line).unwrap().get(1).unwrap().as_str())? {
x @ -16..=-1 => Ok(Instruction::NearJump {
use_carry,
offset: u4::new(-(x + 1) as u8),
forward: false,
}),
x @ 1..=16 => Ok(Instruction::NearJump {
use_carry,
offset: u4::new((x - 1) as u8),
forward: true,
}),
x => Err(AssemblerErrorKind::invalid_relative_jump_offset(x)),
}
}
fn parse_jmp(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_jmp_impl(line, regex, false)
}
fn parse_jmpc(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_jmp_impl(line, regex, true)
}
fn parse_ajmp_impl(
line: &str,
regex: &Regex,
use_carry: bool,
) -> Result<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
parse_ajmp_impl(line, regex, false)
}
fn parse_ajmpc(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_ajmp_impl(line, regex, true)
}
fn parse_shift(
line: &str,
regex: &Regex,
right: bool,
mode: ShiftMode,
) -> Result<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
parse_shift(line, regex, true, ShiftMode::Logical)
}
fn parse_shl(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_shift(line, regex, false, ShiftMode::Logical)
}
fn parse_rotr(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_shift(line, regex, true, ShiftMode::Circular)
}
fn parse_rotl(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
parse_shift(line, regex, false, ShiftMode::Circular)
}
fn parse_inc(line: &str, regex: &Regex) -> Result<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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<Instruction, AssemblerErrorKind> {
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::{u3, u4};
lazy_static! {
static ref ASM: Assembler = Assembler::new();
}
enum IntegerFormat {
Decimal,
Binary,
Octal,
Hexadecimal,
None,
}
struct PrintIntegers<T: Display + Binary + Octal + LowerHex> {
num: T,
format: IntegerFormat,
}
impl<T: Display + Binary + Octal + LowerHex> PrintIntegers<T> {
fn new(num: T) -> Self {
PrintIntegers {
num,
format: IntegerFormat::Decimal,
}
}
}
impl<T: Display + Binary + Octal + LowerHex> Iterator for PrintIntegers<T> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
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_load, "LOAD", Instruction::Load);
make_test_insn_no_args!(asm_line_store, "STORE", Instruction::Store);
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_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_jmpc() {
for (insn, use_carry, sign, forward) in [
("JMP", false, "", true),
("JMP", false, "-", false),
("JMPC", true, "", true),
("JMPC", true, "-", false),
] {
for i in 0..=15u8 {
for j in PrintIntegers::new(i + 1) {
assert_eq!(
ASM.line_to_insn(&format!("{} {}{}", insn, sign, j))
.unwrap(),
Instruction::NearJump {
use_carry,
offset: u4::new(i),
forward,
}
)
}
}
}
}
#[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..5].copy_from_slice(&[0x4F, 0x53, 0xE4, 0xF8, 0xF5]);
code
};
assert_eq!(
ASM.assemble(
r#"
MOV R0.l, 0xF
MOV R1.l, 0x3
ADD
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..5].copy_from_slice(&[0x4F, 0x53, 0xE4, 0xF8, 0xF5]);
code
};
assert_eq!(
ASM.assemble(
r#"
MOV R0.l, 0xF # R0 = 15
MOV R1.l, 0x3 # R1 = 3
ADD # R0 += R1 (18)
ZERO R0 # R0 = 0
HALT # EXIT
"#
)
.unwrap(),
expected
);
}
}