use bincode::{enc::Encoder, error::EncodeError, Encode};
use std::{error, fmt};
use crate::bits::{Bit, BitBuf};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
Not,
And,
Xor,
PushArg(u32),
PushLocal(u32),
PopOutput,
}
impl Encode for Operation {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
match self {
Operation::Not => 0u8.encode(encoder),
Operation::And => 1u8.encode(encoder),
Operation::Xor => 2u8.encode(encoder),
Operation::PushArg(index) => {
3u8.encode(encoder)?;
index.encode(encoder)
}
Operation::PushLocal(index) => {
4u8.encode(encoder)?;
index.encode(encoder)
}
Operation::PopOutput => 5u8.encode(encoder),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ProgramError {
InsufficientStack { instruction: usize, stack: usize },
}
impl fmt::Display for ProgramError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ProgramError::InsufficientStack { instruction, stack } => {
write!(
f,
"instr {}: insufficient stack of size {}",
instruction, stack
)
}
}
}
}
impl error::Error for ProgramError {}
#[derive(Clone, Debug, PartialEq)]
pub struct Program {
operations: Vec<Operation>,
}
impl Program {
pub fn new(operations: impl Into<Vec<Operation>>) -> Self {
Self {
operations: operations.into(),
}
}
pub fn validate(self) -> Result<ValidatedProgram, ProgramError> {
let mut required_input: u32 = 0;
let mut output_count: usize = 0;
let mut stack: usize = 0;
for (instruction, op) in self.operations.iter().enumerate() {
use Operation::*;
match op {
Not => {
if stack < 1 {
return Err(ProgramError::InsufficientStack { instruction, stack });
}
}
Xor | And => {
if stack < 2 {
return Err(ProgramError::InsufficientStack { instruction, stack });
}
stack -= 1;
}
Operation::PushArg(i) => {
required_input = u32::max(i + 1, required_input);
stack += 1;
}
Operation::PushLocal(i) => {
if (*i as usize) >= stack {
return Err(ProgramError::InsufficientStack { instruction, stack });
}
stack += 1;
}
Operation::PopOutput => {
if stack < 1 {
return Err(ProgramError::InsufficientStack { instruction, stack });
}
stack -= 1;
output_count += 1;
}
}
}
Ok(ValidatedProgram {
input_count: required_input as usize,
output_count,
operations: self.operations,
})
}
}
#[derive(Clone, Debug)]
pub struct ValidatedProgram {
pub(crate) input_count: usize,
pub(crate) output_count: usize,
pub(crate) operations: Vec<Operation>,
}
#[cfg(test)]
impl ValidatedProgram {
fn io(&self) -> (usize, usize) {
(self.input_count, self.output_count)
}
}
#[derive(Clone, Debug)]
pub(crate) struct Machine {
input: BitBuf,
stack: BitBuf,
output: BitBuf,
}
impl Machine {
pub fn new(input: BitBuf) -> Self {
Self {
input,
stack: BitBuf::new(),
output: BitBuf::new(),
}
}
pub fn pop(&mut self) -> Bit {
self.stack.pop().unwrap()
}
pub fn push(&mut self, bit: Bit) {
self.stack.push(bit);
}
pub fn not(&mut self) {
let top = self.pop();
self.push(!top);
}
pub fn xor(&mut self) {
let a = self.pop();
let b = self.pop();
self.push(a ^ b);
}
#[cfg(test)]
pub fn and(&mut self) {
let a = self.pop();
let b = self.pop();
self.push(a & b);
}
pub fn push_arg(&mut self, i: usize) {
let arg = self.input.get(i).unwrap();
self.push(arg);
}
pub fn push_local(&mut self, i: usize) {
let local = self.stack.get(i).unwrap();
self.push(local);
}
pub fn pop_output(&mut self) {
let pop = self.pop();
self.output.push(pop)
}
pub fn input_output(self) -> (BitBuf, BitBuf) {
(self.input, self.output)
}
}
#[cfg(test)]
pub(crate) mod generators {
use super::*;
use proptest::collection::vec;
use proptest::prelude::*;
fn run_operations(ops: &[Operation], input: &[u8]) -> u8 {
let mut machine = Machine::new(BitBuf::from_bytes(input));
for op in ops {
match op {
Operation::Not => machine.not(),
Operation::And => machine.and(),
Operation::Xor => machine.xor(),
Operation::PushArg(i) => machine.push_arg(*i as usize),
Operation::PushLocal(i) => machine.push_local(*i as usize),
Operation::PopOutput => machine.pop_output(),
}
}
let (_, mut output) = machine.input_output();
let mut out = 0;
for _ in 0..8 {
out <<= 1;
out |= u64::from(output.pop().unwrap()) as u8;
}
out
}
fn arb_operation_except_pop_output(
max_arg: u32,
max_local: u32,
) -> impl Strategy<Value = Operation> {
use Operation::*;
prop_oneof![
Just(Not),
Just(And),
Just(Xor),
(0..max_arg).prop_map(PushArg),
(0..max_local).prop_map(PushLocal),
]
}
prop_compose! {
pub fn arb_program_and_inputs()(input in any::<[u8; 4]>(), extra in vec(arb_operation_except_pop_output(32, 16), 0..16)) -> (ValidatedProgram, [u8; 4], u8) {
use Operation::*;
let mut operations = Vec::with_capacity(extra.len() + 32 + 8);
for i in 0..32 {
operations.push(PushArg(i));
}
operations.extend(extra.iter());
for _ in 0..8 {
operations.push(PopOutput);
}
let output = run_operations(&operations, &input);
(Program::new(operations).validate().unwrap(), input, output)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use Operation::*;
#[test]
fn test_validating_program_with_insufficient_stack() {
assert!(Program::new([Not]).validate().is_err());
assert!(Program::new([And]).validate().is_err());
assert!(Program::new([PushArg(0), And]).validate().is_err());
assert!(Program::new([PushLocal(0)]).validate().is_err());
}
#[test]
fn test_validating_program_counts_correctly() {
assert_eq!(
Ok((2, 2)),
Program::new([PushArg(0), PushArg(1), Not, PopOutput, PopOutput])
.validate()
.map(|x| x.io())
);
assert_eq!(
Ok((1, 2)),
Program::new([PushArg(0), PushArg(0), Not, PopOutput, PopOutput])
.validate()
.map(|x| x.io())
);
assert_eq!(
Ok((1, 1)),
Program::new([PushArg(0), PushArg(0), Xor, PopOutput])
.validate()
.map(|x| x.io())
);
assert_eq!(
Ok((1, 1)),
Program::new([PushArg(0), PushArg(0), And, PopOutput])
.validate()
.map(|x| x.io())
);
}
}