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
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
|
|
);
|
|
}
|
|
}
|
|
|