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.
788 lines
26 KiB
788 lines
26 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;
|
|
use ux::{u2, u3, u4};
|
|
|
|
type InstructionDecoder = fn(&str, &Regex) -> Result<Instruction, Box<dyn Error>>;
|
|
|
|
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)
|
|
};
|
|
}
|
|
|
|
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) -> Result<[u8; 0x100], Box<dyn Error>> {
|
|
let mut ret = [0u8; 0x100];
|
|
let mut i: usize = 0;
|
|
for line in input.lines().map(|line| strip_comment(line).trim()) {
|
|
match line {
|
|
"" => (),
|
|
line => {
|
|
if i > 0xFF {
|
|
return Err("Too much instructions".into());
|
|
}
|
|
let insn = self.line_to_insn(line)?;
|
|
ret[i] = encode(insn);
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
Ok(ret)
|
|
}
|
|
|
|
fn line_to_insn(&self, line: &str) -> Result<Instruction, Box<dyn Error>> {
|
|
for (regex, handler) in &self.table {
|
|
if regex.is_match(line) {
|
|
return handler(line, regex);
|
|
}
|
|
}
|
|
Err("Unknown instruction".into())
|
|
}
|
|
}
|
|
|
|
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, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
match parse_u128(src)? {
|
|
x @ 0..=255 => Ok(x as u8),
|
|
x => Err(format!("{} is not in range [0, 255]", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_i8(src: &str) -> Result<i8, Box<dyn Error>> {
|
|
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(format!("{} is not in range [-128, 127]", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_u4(src: &str) -> Result<u4, Box<dyn Error>> {
|
|
match parse_u8(src)? {
|
|
x @ 0..=15 => Ok(u4::new(x)),
|
|
x => Err(format!("{} is not in range [0, 15]", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_u3(src: &str) -> Result<u3, Box<dyn Error>> {
|
|
match parse_u8(src)? {
|
|
x @ 0..=7 => Ok(u3::new(x)),
|
|
x => Err(format!("{} is not in range [0, 7]", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_reg(src: &str) -> Result<Register, Box<dyn Error>> {
|
|
match src {
|
|
"R0" => Ok(Register::R0),
|
|
"R1" => Ok(Register::R1),
|
|
x => Err(format!("'{}' is not a valid register", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_reg_half(src: &str) -> Result<(Register, bool), Box<dyn Error>> {
|
|
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(format!("'{}' is not a valid register half", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_carry(src: &str) -> Result<bool, Box<dyn Error>> {
|
|
match src {
|
|
"0" => Ok(false),
|
|
"1" => Ok(true),
|
|
x => Err(format!("'{}' is an invalid carry value", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_byte(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
Ok(decode(parse_u8(
|
|
regex.captures(line).unwrap().get(1).unwrap().as_str(),
|
|
)?))
|
|
}
|
|
|
|
fn parse_mov_reg_mem(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
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<Instruction, Box<dyn Error>> {
|
|
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<Instruction, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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("Malformed register to register copy".into()),
|
|
}
|
|
}
|
|
|
|
fn parse_jmp_impl(
|
|
line: &str,
|
|
regex: &Regex,
|
|
use_carry: bool,
|
|
) -> Result<Instruction, Box<dyn Error>> {
|
|
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(format!("{} is not a valid relative jump offset", x).into()),
|
|
}
|
|
}
|
|
|
|
fn parse_jmp(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_jmp_impl(line, regex, false)
|
|
}
|
|
|
|
fn parse_jmpc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_jmp_impl(line, regex, true)
|
|
}
|
|
|
|
fn parse_ajmp_impl(
|
|
line: &str,
|
|
regex: &Regex,
|
|
use_carry: bool,
|
|
) -> Result<Instruction, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
parse_ajmp_impl(line, regex, false)
|
|
}
|
|
|
|
fn parse_ajmpc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_ajmp_impl(line, regex, true)
|
|
}
|
|
|
|
fn parse_shift(
|
|
line: &str,
|
|
regex: &Regex,
|
|
right: bool,
|
|
mode: ShiftMode,
|
|
) -> Result<Instruction, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
parse_shift(line, regex, true, ShiftMode::Logical)
|
|
}
|
|
|
|
fn parse_shl(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_shift(line, regex, false, ShiftMode::Logical)
|
|
}
|
|
|
|
fn parse_rotr(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_shift(line, regex, true, ShiftMode::Circular)
|
|
}
|
|
|
|
fn parse_rotl(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
parse_shift(line, regex, false, ShiftMode::Circular)
|
|
}
|
|
|
|
fn parse_inc(line: &str, regex: &Regex) -> Result<Instruction, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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, Box<dyn Error>> {
|
|
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<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_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
|
|
);
|
|
}
|
|
}
|
|
|