use p101_is::*;
use crate::encoding::*;
use regex::*;
#[derive(Default)]
pub struct StandardEncoding;
impl<'a> StandardEncoding {
const ADD: &'static str = "+";
const SUB: &'static str = "-";
const MUL: &'static str = "x";
const DIV: &'static str = "÷";
const SQUARE: &'static str = "√";
const DIAMOND: &'static str = "⋄";
const UP_ARROW: &'static str = "↑";
const DOWN_ARROW: &'static str = "↓";
const UP_DOWN_ARROW: &'static str = "↕";
const ASTERISK: &'static str = "*";
const COMMENT: &'static str = "#";
const OP_STOP: &'static str = "S";
const OP_ABS: &'static str = "A↕";
const OP_COPY_DECIMALS: &'static str = "/↕";
const OP_NEWLINE: &'static str = "/⋄";
const OP_DR_EXCHANGE: &'static str = "RS";
const OP_CAI_START: &'static str = "A/↑";
pub fn new() -> Self {
Self {}
}
fn encode_reg(&self, o: &Register) -> String {
match o {
Register::A => "A".into(),
Register::B => "B".into(),
Register::b => "B/".into(),
Register::C => "C".into(),
Register::c => "C/".into(),
Register::D => "D".into(),
Register::d => "D/".into(),
Register::E => "E".into(),
Register::e => "E/".into(),
Register::F => "F".into(),
Register::f => "F/".into(),
Register::M => "".into(),
Register::R => "R".into()
}
}
fn encode_jump(&self, o: &Origin) -> String {
match o {
Origin::V => "V".into(),
Origin::W => "W".into(),
Origin::Y => "Y".into(),
Origin::Z => "Z".into(),
Origin::CV => "CV".into(),
Origin::CW => "CW".into(),
Origin::CY => "CY".into(),
Origin::CZ => "CZ".into(),
Origin::DV => "DV".into(),
Origin::DW => "DW".into(),
Origin::DY => "DY".into(),
Origin::DZ => "DZ".into(),
Origin::RV => "RV".into(),
Origin::RW => "RW".into(),
Origin::RY => "RY".into(),
Origin::RZ => "RZ".into(),
}
}
fn encode_conditional_jump(&self, o: &ConditionalOrigin) -> String {
match o {
ConditionalOrigin::_V => "/V".into(),
ConditionalOrigin::_W => "/W".into(),
ConditionalOrigin::_Y => "/Y".into(),
ConditionalOrigin::_Z => "/Z".into(),
ConditionalOrigin::cV => "C/V".into(),
ConditionalOrigin::cW => "C/W".into(),
ConditionalOrigin::cY => "C/Y".into(),
ConditionalOrigin::cZ => "C/Z".into(),
ConditionalOrigin::dV => "D/V".into(),
ConditionalOrigin::dW => "D/W".into(),
ConditionalOrigin::dY => "D/Y".into(),
ConditionalOrigin::dZ => "D/Z".into(),
ConditionalOrigin::rV => "R/V".into(),
ConditionalOrigin::rW => "R/W".into(),
ConditionalOrigin::rY => "R/Y".into(),
ConditionalOrigin::rZ => "R/Z".into(),
}
}
fn encode_jump_destination(&self, d: &JumpDestination) -> String {
match d {
JumpDestination::aV => "A/V".into(),
JumpDestination::aW => "A/W".into(),
JumpDestination::aY => "A/Y".into(),
JumpDestination::aZ => "A/Z".into(),
JumpDestination::AV => "AV".into(),
JumpDestination::AW => "AW".into(),
JumpDestination::AY => "AY".into(),
JumpDestination::AZ => "AZ".into(),
JumpDestination::BV => "BV".into(),
JumpDestination::bV => "B/V".into(),
JumpDestination::BW => "BW".into(),
JumpDestination::bW => "B/W".into(),
JumpDestination::BY => "BY".into(),
JumpDestination::bY => "B/Y".into(),
JumpDestination::BZ => "BZ".into(),
JumpDestination::bZ => "B/Z".into(),
JumpDestination::EV => "EV".into(),
JumpDestination::eV => "E/V".into(),
JumpDestination::EW => "EW".into(),
JumpDestination::eW => "E/W".into(),
JumpDestination::EY => "EY".into(),
JumpDestination::eY => "E/Y".into(),
JumpDestination::EZ => "EZ".into(),
JumpDestination::eZ => "E/Z".into(),
JumpDestination::FV => "FV".into(),
JumpDestination::fV => "F/V".into(),
JumpDestination::FW => "FW".into(),
JumpDestination::fW => "F/W".into(),
JumpDestination::FY => "FY".into(),
JumpDestination::fY => "F/Y".into(),
JumpDestination::FZ => "FZ".into(),
JumpDestination::fZ => "F/Z".into()
}
}
fn add(&self, o: &Register) -> String {
match o {
Register::M => Self::ADD.into(),
_ => format!("{}{}", self.encode_reg(o), Self::ADD)
}
}
fn copy_m(&self, o: &Register) -> String {
match o {
Register::M => Self::UP_ARROW.into(),
_ => format!("{}{}", self.encode_reg(o), Self::UP_ARROW)
}
}
fn copy_to_a(&self, o: &Register) -> String {
match o {
Register::M => Self::DOWN_ARROW.into(),
_ => format!("{}{}", self.encode_reg(o), Self::DOWN_ARROW)
}
}
fn div(&self, o: &Register) -> String {
match o {
Register::M => Self::DIV.into(),
_ => format!("{}{}", self.encode_reg(o), Self::DIV)
}
}
fn mul(&self, o: &Register) -> String {
match o {
Register::M => Self::MUL.into(),
_ => format!("{}{}", self.encode_reg(o), Self::MUL)
}
}
fn print(&self, o: &Register) -> String {
match o {
Register::M => Self::DIAMOND.into(),
_ => format!("{}{}", self.encode_reg(o), Self::DIAMOND)
}
}
fn reset(&self, o: &Register) -> String {
match o {
Register::M => Self::ASTERISK.into(),
_ => format!("{}{}", self.encode_reg(o), Self::ASTERISK)
}
}
fn sqr(&self, o: &Register) -> String {
match o {
Register::M => Self::SQUARE.into(),
_ => format!("{}{}", self.encode_reg(o), Self::SQUARE)
}
}
fn sub(&self, o: &Register) -> String {
match o {
Register::M => Self::SUB.into(),
_ => format!("{}{}", self.encode_reg(o), Self::SUB)
}
}
fn swap(&self, o: &Register) -> String {
match o {
Register::M => Self::UP_DOWN_ARROW.into(),
_ => format!("{}{}", self.encode_reg(o), Self::UP_DOWN_ARROW)
}
}
fn cai(&self, sign: &CaiSign, order: &CaiOrder, digit: &CaiDigit, comma: &CaiComma) -> String {
let so = match (sign, order) {
(CaiSign::Positive, CaiOrder::Low) => "R", (CaiSign::Positive, CaiOrder::High) => "D", (CaiSign::Negative, CaiOrder::Low) => "F", (CaiSign::Negative, CaiOrder::High) => "E", };
let c = match comma {
CaiComma::No => "",
CaiComma::Yes => "/",
};
let d = match digit {
CaiDigit::N0 => Self::OP_STOP,
CaiDigit::N1 => Self::DOWN_ARROW,
CaiDigit::N2 => Self::UP_ARROW,
CaiDigit::N3 => Self::UP_DOWN_ARROW,
CaiDigit::N4 => Self::ADD,
CaiDigit::N5 => Self::SUB,
CaiDigit::N6 => Self::MUL,
CaiDigit::N7 => Self::DIV,
CaiDigit::N8 => Self::DIAMOND,
CaiDigit::N9 => Self::ASTERISK,
};
format!("{}{}{}", so, c, d)
}
fn remove_comment(&self, line: &'a str) -> &'a str {
match line.split(Self::COMMENT).next() {
Some(c) => c.trim(),
None => ""
}
}
fn parse_fli(&self, text: &str) -> Result<Option<Instruction>, String> {
let t = format!("^({}|{}|{}|{}|{}|{}).*",
Self::OP_STOP,
Self::OP_ABS,
Self::OP_COPY_DECIMALS,
Self::OP_NEWLINE,
Self::OP_DR_EXCHANGE,
Self::OP_CAI_START,
);
let re = Regex::new(&t).unwrap();
match re.captures(text) {
Some(caps) => caps.get(1).map_or(Ok(None), |m| match m.as_str() {
Self::OP_STOP => Ok(Some(Instruction::Stop)),
Self::OP_ABS => Ok(Some(Instruction::Abs)),
Self::OP_COPY_DECIMALS => Ok(Some(Instruction::CopyDecimal)),
Self::OP_NEWLINE => Ok(Some(Instruction::NewLine)),
Self::OP_DR_EXCHANGE => Ok(Some(Instruction::DrExchange)),
Self::OP_CAI_START => Ok(Some(Instruction::CaiStart)),
_ => Err("Invalid instruction".into())
}),
None => Ok(None)
}
}
fn parse_vli(&self, text: &str) -> Result<Option<Instruction>, String> {
let t = format!("(A|B|B/|C|C/|D|D/|E|E/|F|F/|R)?(\\{}|{}|{}|{}|{}|{}|{}|{}|\\{}|{}).*",
Self::ADD,
Self::SUB,
Self::MUL,
Self::DIV,
Self::SQUARE,
Self::UP_ARROW,
Self::DOWN_ARROW,
Self::DIAMOND,
Self::ASTERISK,
Self::UP_DOWN_ARROW
);
let re = Regex::new(&t).unwrap();
if let Some(caps) = re.captures(text) {
let reg = if let Some(m) = caps.get(1) {
match m.as_str() {
"A" => Register::A,
"B" => Register::B,
"B/" => Register::b,
"C" => Register::C,
"C/" => Register::c,
"D" => Register::D,
"D/" => Register::d,
"E" => Register::E,
"E/" => Register::e,
"F" => Register::F,
"F/" => Register::f,
"R" => Register::R,
_ => return Err("Invalid register".into()),
}
} else {
Register::M
};
if let Some(m) = caps.get(2) {
match m.as_str() {
Self::ADD => Ok(Some(Instruction::Add(reg))),
Self::SUB => Ok(Some(Instruction::Sub(reg))),
Self::MUL => Ok(Some(Instruction::Mul(reg))),
Self::DIV => Ok(Some(Instruction::Div(reg))),
Self::SQUARE => Ok(Some(Instruction::Sqr(reg))),
Self::UP_ARROW => Ok(Some(Instruction::CopyM(reg))),
Self::DOWN_ARROW => Ok(Some(Instruction::CopyToA(reg))),
Self::DIAMOND => Ok(Some(Instruction::Print(reg))),
Self::ASTERISK => Ok(Some(Instruction::Reset(reg))),
Self::UP_DOWN_ARROW => Ok(Some(Instruction::SwapA(reg))),
_ => Err(format!("Invalid instruction {}", m.as_str()))
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn parse_jump(&self, text: &str) -> Result<Option<Instruction>, String> {
let mut chars = text.chars();
match (chars.next(), chars.next(), chars.next()) {
(Some('V'), _, _) => Ok(Some(Instruction::Jump(Origin::V))),
(Some('W'), _, _) => Ok(Some(Instruction::Jump(Origin::W))),
(Some('Y'), _, _) => Ok(Some(Instruction::Jump(Origin::Y))),
(Some('Z'), _, _) => Ok(Some(Instruction::Jump(Origin::Z))),
(Some('C'), Some('V'), _) => Ok(Some(Instruction::Jump(Origin::CV))),
(Some('C'), Some('W'), _) => Ok(Some(Instruction::Jump(Origin::CW))),
(Some('C'), Some('Y'), _) => Ok(Some(Instruction::Jump(Origin::CY))),
(Some('C'), Some('Z'), _) => Ok(Some(Instruction::Jump(Origin::CZ))),
(Some('D'), Some('V'), _) => Ok(Some(Instruction::Jump(Origin::DV))),
(Some('D'), Some('W'), _) => Ok(Some(Instruction::Jump(Origin::DW))),
(Some('D'), Some('Y'), _) => Ok(Some(Instruction::Jump(Origin::DY))),
(Some('D'), Some('Z'), _) => Ok(Some(Instruction::Jump(Origin::DZ))),
(Some('R'), Some('V'), _) => Ok(Some(Instruction::Jump(Origin::RV))),
(Some('R'), Some('W'), _) => Ok(Some(Instruction::Jump(Origin::RW))),
(Some('R'), Some('Y'), _) => Ok(Some(Instruction::Jump(Origin::RY))),
(Some('R'), Some('Z'), _) => Ok(Some(Instruction::Jump(Origin::RZ))),
(Some('/'), Some('V'), _) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::_V))),
(Some('/'), Some('W'), _) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::_W))),
(Some('/'), Some('Y'), _) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::_Y))),
(Some('/'), Some('Z'), _) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::_Z))),
(Some('C'), Some('/'), Some('V')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::cV))),
(Some('C'), Some('/'), Some('W')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::cW))),
(Some('C'), Some('/'), Some('Y')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::cY))),
(Some('C'), Some('/'), Some('Z')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::cZ))),
(Some('D'), Some('/'), Some('V')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::dV))),
(Some('D'), Some('/'), Some('W')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::dW))),
(Some('D'), Some('/'), Some('Y')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::dY))),
(Some('D'), Some('/'), Some('Z')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::dZ))),
(Some('R'), Some('/'), Some('V')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::rV))),
(Some('R'), Some('/'), Some('W')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::rW))),
(Some('R'), Some('/'), Some('Y')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::rY))),
(Some('R'), Some('/'), Some('Z')) => Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::rZ))),
(Some('A'), Some('V'), _) => Ok(Some(Instruction::Label(JumpDestination::AV))),
(Some('A'), Some('W'), _) => Ok(Some(Instruction::Label(JumpDestination::AW))),
(Some('A'), Some('Y'), _) => Ok(Some(Instruction::Label(JumpDestination::AY))),
(Some('A'), Some('Z'), _) => Ok(Some(Instruction::Label(JumpDestination::AZ))),
(Some('B'), Some('V'), _) => Ok(Some(Instruction::Label(JumpDestination::BV))),
(Some('B'), Some('W'), _) => Ok(Some(Instruction::Label(JumpDestination::BW))),
(Some('B'), Some('Y'), _) => Ok(Some(Instruction::Label(JumpDestination::BY))),
(Some('B'), Some('Z'), _) => Ok(Some(Instruction::Label(JumpDestination::BZ))),
(Some('E'), Some('V'), _) => Ok(Some(Instruction::Label(JumpDestination::EV))),
(Some('E'), Some('W'), _) => Ok(Some(Instruction::Label(JumpDestination::EW))),
(Some('E'), Some('Y'), _) => Ok(Some(Instruction::Label(JumpDestination::EY))),
(Some('E'), Some('Z'), _) => Ok(Some(Instruction::Label(JumpDestination::EZ))),
(Some('F'), Some('V'), _) => Ok(Some(Instruction::Label(JumpDestination::FV))),
(Some('F'), Some('W'), _) => Ok(Some(Instruction::Label(JumpDestination::FW))),
(Some('F'), Some('Y'), _) => Ok(Some(Instruction::Label(JumpDestination::FY))),
(Some('F'), Some('Z'), _) => Ok(Some(Instruction::Label(JumpDestination::FZ))),
(Some('A'), Some('/'), Some('V')) => Ok(Some(Instruction::Label(JumpDestination::aV))),
(Some('A'), Some('/'), Some('W')) => Ok(Some(Instruction::Label(JumpDestination::aW))),
(Some('A'), Some('/'), Some('Y')) => Ok(Some(Instruction::Label(JumpDestination::aY))),
(Some('A'), Some('/'), Some('Z')) => Ok(Some(Instruction::Label(JumpDestination::aZ))),
(Some('B'), Some('/'), Some('V')) => Ok(Some(Instruction::Label(JumpDestination::bV))),
(Some('B'), Some('/'), Some('W')) => Ok(Some(Instruction::Label(JumpDestination::bW))),
(Some('B'), Some('/'), Some('Y')) => Ok(Some(Instruction::Label(JumpDestination::bY))),
(Some('B'), Some('/'), Some('Z')) => Ok(Some(Instruction::Label(JumpDestination::bZ))),
(Some('E'), Some('/'), Some('V')) => Ok(Some(Instruction::Label(JumpDestination::eV))),
(Some('E'), Some('/'), Some('W')) => Ok(Some(Instruction::Label(JumpDestination::eW))),
(Some('E'), Some('/'), Some('Y')) => Ok(Some(Instruction::Label(JumpDestination::eY))),
(Some('E'), Some('/'), Some('Z')) => Ok(Some(Instruction::Label(JumpDestination::eZ))),
(Some('F'), Some('/'), Some('V')) => Ok(Some(Instruction::Label(JumpDestination::fV))),
(Some('F'), Some('/'), Some('W')) => Ok(Some(Instruction::Label(JumpDestination::fW))),
(Some('F'), Some('/'), Some('Y')) => Ok(Some(Instruction::Label(JumpDestination::FY))),
(Some('F'), Some('/'), Some('Z')) => Ok(Some(Instruction::Label(JumpDestination::fZ))),
_ => Ok(None)
}
}
}
impl Encoding for StandardEncoding {
fn encode(&self, i: &Instruction) -> String {
match i {
Instruction::Abs => format!("A{}", Self::UP_DOWN_ARROW),
Instruction::Add(o) => self.add(o),
Instruction::CaiStart => Self::OP_CAI_START.into(),
Instruction::Cai(sign, order, digit, comma) => self.cai(sign, order, digit, comma),
Instruction::ConditionalJump(o) => self.encode_conditional_jump(o),
Instruction::CopyDecimal => format!("/{}", Self::UP_DOWN_ARROW),
Instruction::CopyM(o) => self.copy_m(o),
Instruction::CopyToA(o) => self.copy_to_a(o),
Instruction::Div(o) => self.div(o),
Instruction::DrExchange => "RS".into(),
Instruction::Jump(o) => self.encode_jump(o),
Instruction::Label(d) => self.encode_jump_destination(d),
Instruction::Mul(o) => self.mul(o),
Instruction::NewLine => format!("/{}", Self::DIAMOND),
Instruction::Print(o) => self.print(o),
Instruction::Reset(o) => self.reset(o),
Instruction::Sqr(o) => self.sqr(o),
Instruction::Stop => Self::OP_STOP.into(),
Instruction::Sub(o) => self.sub(o),
Instruction::SwapA(o) => self.swap(o),
}
}
fn decode_instr(&self, text: &str) -> DecodeResult {
let code = self.remove_comment(text);
match self.parse_fli(code) {
Ok(Some(i)) => return DecodeResult::Ok(i),
Err(e) => return DecodeResult::Err(e),
_ => ()
}
match self.parse_vli(code) {
Ok(Some(i)) => return DecodeResult::Ok(i),
Err(e) => return DecodeResult::Err(e),
_ => ()
}
match self.parse_jump(code) {
Ok(Some(i)) => return DecodeResult::Ok(i),
Err(e) => return DecodeResult::Err(e),
_ => ()
}
DecodeResult::Err(format!("Invalid instruction {}", code))
}
fn decode_cai(&self, text: &str) -> DecodeResult {
let mut a = text.chars();
let (s, o) = match a.next() {
Some('R') => (CaiSign::Positive, CaiOrder::Low),
Some('D') => (CaiSign::Positive, CaiOrder::High),
Some('F') => (CaiSign::Negative, CaiOrder::Low),
Some('E') => (CaiSign::Negative, CaiOrder::High),
_ => return DecodeResult::Err(format!("Invalid first character of instruction {}", text))
};
let (c, n) = match a.next() {
Some('/') => (CaiComma::Yes, a.next()),
Some(a1) => (CaiComma::No, Some(a1)),
None => return DecodeResult::Err(format!("Invalid second character of instruction {}", text))
};
let d = match n {
Some('S') => CaiDigit::N0,
Some('↓') => CaiDigit::N1,
Some('↑') => CaiDigit::N2,
Some('↕') => CaiDigit::N3,
Some('+') => CaiDigit::N4,
Some('-') => CaiDigit::N5,
Some('x') => CaiDigit::N6,
Some('÷') => CaiDigit::N7,
Some('⋄') => CaiDigit::N8,
Some('*') => CaiDigit::N9,
_ => return DecodeResult::Err(format!("Invalid third character of instruction {}", text))
};
DecodeResult::Ok(Instruction::Cai(s, o, d, c))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::*;
fn build_encoder() -> Encoder<StandardEncoding, NullAnnotator> {
Encoder::<StandardEncoding, NullAnnotator>::new()
}
#[test]
pub fn standard_encoding_of_stop_is_s() {
let program = vec!(Instruction::Stop);
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(1, lines.len());
assert_eq!("S", lines[0]);
}
#[test]
pub fn standard_encoding_of_reset_is_reg_and_asterisk() {
let program = vec!(Instruction::Reset(Register::A),
Instruction::Reset(Register::B),
Instruction::Reset(Register::b),
Instruction::Reset(Register::M));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(4, lines.len());
assert_eq!(String::from("A*"), lines[0]);
assert_eq!(String::from("B*"), lines[1]);
assert_eq!(String::from("B/*"), lines[2]);
assert_eq!(String::from("*"), lines[3]);
}
#[test]
pub fn standard_encoding_of_print_is_reg_and_diamond() {
let program = vec!(Instruction::Print(Register::A),
Instruction::Print(Register::B),
Instruction::Print(Register::b),
Instruction::Print(Register::M));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(4, lines.len());
assert_eq!(String::from("A") + StandardEncoding::DIAMOND, lines[0]);
assert_eq!(String::from("B") + StandardEncoding::DIAMOND, lines[1]);
assert_eq!(String::from("B/") + StandardEncoding::DIAMOND, lines[2]);
assert_eq!(String::from(StandardEncoding::DIAMOND), lines[3]);
}
#[test]
pub fn standard_encoding_of_newline_is_slash_diamond() {
let program = vec!(Instruction::NewLine);
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(1, lines.len());
assert_eq!(String::from("/") + StandardEncoding::DIAMOND, lines[0]);
}
#[test]
pub fn standard_encoding_of_copy_to_a_is_reg_and_down_arrow() {
let program = vec!(Instruction::CopyToA(Register::M),
Instruction::CopyToA(Register::B),
Instruction::CopyToA(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!(StandardEncoding::DOWN_ARROW, lines[0]);
assert_eq!(String::from("B") + StandardEncoding::DOWN_ARROW, lines[1]);
assert_eq!(String::from("B/") + StandardEncoding::DOWN_ARROW, lines[2]);
}
#[test]
pub fn standard_encoding_of_copy_m_is_reg_and_up_arrow() {
let program = vec!(Instruction::CopyM(Register::M),
Instruction::CopyM(Register::B),
Instruction::CopyM(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!(StandardEncoding::UP_ARROW, lines[0]);
assert_eq!(String::from("B") + StandardEncoding::UP_ARROW, lines[1]);
assert_eq!(String::from("B/") + StandardEncoding::UP_ARROW, lines[2]);
}
#[test]
pub fn standard_encoding_of_exchange_is_reg_and_up_down_arrow() {
let program = vec!(Instruction::SwapA(Register::M),
Instruction::SwapA(Register::B),
Instruction::SwapA(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!(StandardEncoding::UP_DOWN_ARROW, lines[0]);
assert_eq!(String::from("B") + StandardEncoding::UP_DOWN_ARROW, lines[1]);
assert_eq!(String::from("B/") + StandardEncoding::UP_DOWN_ARROW, lines[2]);
}
#[test]
pub fn standard_encoding_of_d_r_exchange_is_rs() {
let program = vec!(Instruction::DrExchange);
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(1, lines.len());
assert_eq!("RS", lines[0]);
}
#[test]
pub fn standard_encoding_of_decimal_part_to_is_slash_up_down_arrow() {
let program = vec!(Instruction::CopyDecimal);
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(1, lines.len());
assert_eq!(String::from("/") + StandardEncoding::UP_DOWN_ARROW, lines[0]);
}
#[test]
pub fn standard_encoding_of_add_is_reg_plus() {
let program = vec!(Instruction::Add(Register::M),
Instruction::Add(Register::B),
Instruction::Add(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("+", lines[0]);
assert_eq!(String::from("B+"), lines[1]);
assert_eq!(String::from("B/+"), lines[2]);
}
#[test]
pub fn standard_encoding_of_sub_is_reg_minus() {
let program = vec!(Instruction::Sub(Register::M),
Instruction::Sub(Register::B),
Instruction::Sub(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("-", lines[0]);
assert_eq!(String::from("B-"), lines[1]);
assert_eq!(String::from("B/-"), lines[2]);
}
#[test]
pub fn standard_encoding_of_mul_is_reg_x() {
let program = vec!(Instruction::Mul(Register::M),
Instruction::Mul(Register::B),
Instruction::Mul(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("x", lines[0]);
assert_eq!(String::from("Bx"), lines[1]);
assert_eq!(String::from("B/x"), lines[2]);
}
#[test]
pub fn standard_encoding_of_div_is_reg_divided_by() {
let program = vec!(Instruction::Div(Register::M),
Instruction::Div(Register::B),
Instruction::Div(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("÷", lines[0]);
assert_eq!(String::from("B÷"), lines[1]);
assert_eq!(String::from("B/÷"), lines[2]);
}
#[test]
pub fn standard_encoding_of_sqr() {
let program = vec!(Instruction::Sqr(Register::M),
Instruction::Sqr(Register::B),
Instruction::Sqr(Register::b));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!(StandardEncoding::SQUARE, lines[0]);
assert_eq!(String::from("B") + StandardEncoding::SQUARE, lines[1]);
assert_eq!(String::from("B/") + StandardEncoding::SQUARE, lines[2]);
}
#[test]
pub fn standard_encoding_of_abs_is_accumulator_up_down_arrows() {
let program = vec!(Instruction::Abs);
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(String::from("A") + StandardEncoding::UP_DOWN_ARROW, lines[0]);
}
#[test]
pub fn standard_encoding_of_jump_is_the_point_of_origin() {
let program = vec!(Instruction::Jump(Origin::V),
Instruction::Jump(Origin::RZ),
Instruction::Jump(Origin::CW));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("V", lines[0]);
assert_eq!(String::from("RZ"), lines[1]);
assert_eq!(String::from("CW"), lines[2]);
}
#[test]
pub fn standard_encoding_of_conditional_jump_the_point_of_origin() {
let program = vec!(Instruction::ConditionalJump(ConditionalOrigin::_V),
Instruction::ConditionalJump(ConditionalOrigin::rV),
Instruction::ConditionalJump(ConditionalOrigin::dY));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(3, lines.len());
assert_eq!("/V", lines[0]);
assert_eq!(String::from("R/V"), lines[1]);
assert_eq!(String::from("D/Y"), lines[2]);
}
#[test]
pub fn standard_encoding_of_cai() {
let program = vec!(Instruction::CaiStart,
Instruction::Cai(CaiSign::Negative, CaiOrder::Low, CaiDigit::N0, CaiComma::No),
Instruction::Cai(CaiSign::Negative, CaiOrder::Low, CaiDigit::N7, CaiComma::No),
Instruction::Cai(CaiSign::Negative, CaiOrder::High, CaiDigit::N0, CaiComma::Yes));
let encoder = build_encoder();
let text = encoder.encode(&program);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(4, lines.len());
assert_eq!("A/↑", lines[0]);
assert_eq!("FS", lines[1]);
assert_eq!(String::from("F÷"), lines[2]);
assert_eq!(String::from("E/S"), lines[3]);
}
#[test]
pub fn can_parse_fixed_length_instructions() {
let enc = StandardEncoding::new();
assert_eq!(Ok(Some(Instruction::Abs)), enc.parse_fli(StandardEncoding::OP_ABS));
assert_eq!(Ok(Some(Instruction::Abs)), enc.parse_fli(&format!("{}{}", StandardEncoding::OP_ABS, " ")));
assert_eq!(Ok(Some(Instruction::Abs)), enc.parse_fli(&format!("{}{}", StandardEncoding::OP_ABS, " # aaaa dd ff")));
assert_eq!(Ok(Some(Instruction::Abs)), enc.parse_fli(&format!("{}{}", StandardEncoding::OP_ABS, " aaaa dd ff")));
assert_eq!(Ok(Some(Instruction::Stop)), enc.parse_fli(StandardEncoding::OP_STOP));
assert_eq!(Ok(Some(Instruction::CopyDecimal)), enc.parse_fli(StandardEncoding::OP_COPY_DECIMALS));
assert_eq!(Ok(Some(Instruction::NewLine)), enc.parse_fli(StandardEncoding::OP_NEWLINE));
assert_eq!(Ok(Some(Instruction::DrExchange)), enc.parse_fli(StandardEncoding::OP_DR_EXCHANGE));
assert_eq!(Ok(Some(Instruction::CaiStart)), enc.parse_fli(StandardEncoding::OP_CAI_START));
assert_eq!(Ok(None), enc.parse_fli(""));
assert_eq!(Ok(None), enc.parse_fli("abkcdnlbjiojo"));
}
#[test]
pub fn can_parse_variable_length_instructions() {
let enc = StandardEncoding::new();
assert_eq!(Ok(Some(Instruction::Print(Register::M))), enc.parse_vli(StandardEncoding::DIAMOND));
assert_eq!(Ok(Some(Instruction::Print(Register::M))), enc.parse_vli(&format!("{}{}", StandardEncoding::DIAMOND, " ")));
assert_eq!(Ok(Some(Instruction::Print(Register::M))), enc.parse_vli(&format!("{}{}", StandardEncoding::DIAMOND, " # aaaa dd ff")));
assert_eq!(Ok(Some(Instruction::Print(Register::M))), enc.parse_vli(&format!("{}{}", StandardEncoding::DIAMOND, " aaaa dd ff")));
assert_eq!(Ok(Some(Instruction::Print(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::DIAMOND)));
assert_eq!(Ok(Some(Instruction::Print(Register::B))), enc.parse_vli(&format!("B{}", StandardEncoding::DIAMOND)));
assert_eq!(Ok(Some(Instruction::Print(Register::b))), enc.parse_vli(&format!("B/{}", StandardEncoding::DIAMOND)));
assert_eq!(Ok(Some(Instruction::Add(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::ADD)));
assert_eq!(Ok(Some(Instruction::Sub(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::SUB)));
assert_eq!(Ok(Some(Instruction::Mul(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::MUL)));
assert_eq!(Ok(Some(Instruction::Div(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::DIV)));
assert_eq!(Ok(Some(Instruction::Sqr(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::SQUARE)));
assert_eq!(Ok(Some(Instruction::CopyM(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::UP_ARROW)));
assert_eq!(Ok(Some(Instruction::CopyToA(Register::B))), enc.parse_vli(&format!("B{}", StandardEncoding::DOWN_ARROW)));
assert_eq!(Ok(Some(Instruction::Reset(Register::A))), enc.parse_vli(&format!("A{}", StandardEncoding::ASTERISK)));
assert_eq!(Ok(Some(Instruction::SwapA(Register::B))), enc.parse_vli(&format!("B{}", StandardEncoding::UP_DOWN_ARROW)));
assert_eq!(Ok(None), enc.parse_vli(""));
assert_eq!(Ok(None), enc.parse_vli("abkcdnlbjiojo"));
}
#[test]
pub fn can_parse_jump_instructions() {
let enc = StandardEncoding::new();
assert_eq!(Ok(None), enc.parse_jump(""));
assert_eq!(Ok(None), enc.parse_jump("abkcdnlbjiojo"));
assert_eq!(Ok(Some(Instruction::Jump(Origin::V))), enc.parse_jump("V"));
assert_eq!(Ok(Some(Instruction::Jump(Origin::CW))), enc.parse_jump("CW"));
assert_eq!(Ok(Some(Instruction::Jump(Origin::DY))), enc.parse_jump("DY"));
assert_eq!(Ok(Some(Instruction::Jump(Origin::RZ))), enc.parse_jump("RZ"));
assert_eq!(Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::_V))), enc.parse_jump("/V"));
assert_eq!(Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::cW))), enc.parse_jump("C/W"));
assert_eq!(Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::dY))), enc.parse_jump("D/Y"));
assert_eq!(Ok(Some(Instruction::ConditionalJump(ConditionalOrigin::rZ))), enc.parse_jump("R/Z"));
assert_eq!(Ok(Some(Instruction::Label(JumpDestination::AV))), enc.parse_jump("AV"));
assert_eq!(Ok(Some(Instruction::Label(JumpDestination::BW))), enc.parse_jump("BW"));
assert_eq!(Ok(Some(Instruction::Label(JumpDestination::eY))), enc.parse_jump("E/Y"));
assert_eq!(Ok(Some(Instruction::Label(JumpDestination::fZ))), enc.parse_jump("F/Z"));
}
#[test]
pub fn can_parse_cai_instructions() {
let enc = StandardEncoding::new();
let cai = enc.decode_cai("R/*");
match cai {
DecodeResult::Ok(Instruction::Cai(s, o, d, c)) => {
assert_eq!(CaiSign::Positive, s);
assert_eq!(CaiOrder::Low, o);
assert_eq!(CaiComma::Yes, c);
assert_eq!(CaiDigit::N9, d);
},
_ => assert!(false),
}
let cai = enc.decode_cai("FS");
match cai {
DecodeResult::Ok(Instruction::Cai(s, o, d, c)) => {
assert_eq!(CaiSign::Negative, s);
assert_eq!(CaiOrder::Low, o);
assert_eq!(CaiComma::No, c);
assert_eq!(CaiDigit::N0, d);
},
_ => assert!(false),
}
}
}