Решение на Reversible Interpreter от Борис Ангелов

Обратно към всички решения

Към профила на Борис Ангелов

Резултати

  • 14 точки от тестове
  • 0 бонус точки
  • 14 точки общо
  • 11 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::collections::VecDeque;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RuntimeError {
DivideByZero,
StackUnderflow,
InvalidCommand,
NoInstructions,
}
#[derive(Debug, Default)]
pub struct Interpreter {
pub instructions: VecDeque<String>,
pub stack: Vec<i32>,
reverse_stack: Vec<String>
// + още полета за поддръжка на .back() метода
}
impl Interpreter {
/// Конструира нов интерпретатор с празен стек и никакви инструкции.
pub fn new() -> Self {
Interpreter {
instructions: VecDeque::new(),
stack: Vec::new(),
reverse_stack: Vec::new()
}
}
/// Добавя инструкции от дадения списък към края на `instructions`. Примерно:
///
/// interpreter.add_instructions(&[
/// "PUSH 1",
/// "PUSH 2",
/// "ADD",
/// ]);
///
/// Инструкциите не се интерпретират, само се записват.
///
pub fn add_instructions(&mut self, instructions: &[&str]) {
for ist in instructions.iter() {
self.instructions.push_back(String::from(ist.trim()));
}
}
/// Връща mutable reference към инструкцията, която ще се изпълни при
/// следващия `.forward()` -- първата в списъка/дека.
///
pub fn current_instruction(&mut self) -> Option<&mut String> {
self.instructions.front_mut()
}
/// Интерпретира първата инструкция в `self.instructions` по правилата описани по-горе. Записва
/// някаква информация за да може успешно да се изпълни `.back()` в по-нататъшен момент.
///
/// Ако няма инструкции, връща `RuntimeError::NoInstructions`. Другите грешки идват от
/// обясненията по-горе.
///
pub fn forward(&mut self) -> Result<(), RuntimeError> {
self.instructions.pop_front().map_or_else(|| Err(RuntimeError::NoInstructions),
|val| {
match self.handle_command(&val) {
Ok(_) => Ok(()),
Err(e) => {
self.instructions.push_front(val.clone());
Err(e)
}
}
})
}
fn handle_command(&mut self, command: &String) -> Result<(), RuntimeError> {
let mut it = command.as_str().trim().split_whitespace();
it.next().map_or_else(|| Err(RuntimeError::InvalidCommand), |val| match val {
"PUSH" => {
it.next().map_or_else(|| Err(RuntimeError::InvalidCommand),
|vl| {
vl.parse::<i32>().map_or_else(|_| Err(RuntimeError::InvalidCommand),
|vl_int| {
it.next().map_or_else(|| {
self.stack.push(vl_int);
self.reverse_stack.push(format!("{} {}", val, vl));
Ok(())
},
|_| Err(RuntimeError::InvalidCommand))
})
})
},
cm => {
it.next().map_or_else(||
match cm {
"POP" => {
it.next().map_or_else(|| {
self.stack.pop().map_or_else(|| {
Err(RuntimeError::StackUnderflow)
},
|v| {
self.reverse_stack.push("POP".to_string());
self.reverse_stack.push(v.to_string());
Ok(())
})
}, |_| Err(RuntimeError::InvalidCommand))
},
"ADD" => {
self.bin_operation(|a, b| Ok(a + b), "ADD".to_string())
},
"MUL" => {
self.bin_operation(|a, b| Ok(a * b), "MUL".to_string())
},
"SUB" => {
self.bin_operation(|a, b| Ok(a - b), "SUB".to_string())
},
"DIV" => {
self.bin_operation(|a, b| {
if b == 0 {
Err(RuntimeError::DivideByZero)
} else {
Ok(a / b)
}
}, "DIV".to_string())
}
_ => {
Err(RuntimeError::InvalidCommand)
}
}, |_| Err(RuntimeError::InvalidCommand))
}
})
}
fn bin_operation(&mut self, op: impl Fn(i32, i32) -> Result<i32, RuntimeError>, op_name: String) -> Result<(), RuntimeError> {
self.stack.pop().map_or_else(|| Err(RuntimeError::StackUnderflow),
|v1| {
self.stack.pop().map_or_else(|| Err(RuntimeError::StackUnderflow),
|v2| {
self.reverse_stack.push(op_name);
self.reverse_stack.push(v1.to_string());
self.reverse_stack.push(v2.to_string());
op(v1, v2).map(|v| self.stack.push(v)).map_err(|e| {
self.stack.push(v2);
self.stack.push(v1);
e
})
}).map_err(|e| match e {
RuntimeError::StackUnderflow => {
self.stack.push(v1);
RuntimeError::StackUnderflow
}
_ => e
})
})
}
/// Вика `.forward()` докато не свършат инструкциите (може и да се имплементира по други
/// начини, разбира се) или има грешка.
///
pub fn run(&mut self) -> Result<(), RuntimeError> {
loop {
match self.forward() {
Err(RuntimeError::NoInstructions) => return Ok(()),
Err(e) => return Err(e),
_ => (),
}
}
}
/// "Обръща" последно-изпълнената инструкция с `.forward()`. Това може да се изпълнява отново и
/// отново за всяка инструкция, изпълнена с `.forward()` -- тоест, не пазим само последната
/// инструкция, а списък/стек от всичките досега.
///
/// Ако няма инструкция за връщане, очакваме `RuntimeError::NoInstructions`.
///
pub fn back(&mut self) -> Result<(), RuntimeError> {
self.reverse_stack.pop().map_or_else(|| Err(RuntimeError::NoInstructions),
|v2| {
if v2.parse::<i32>().is_ok() {
self.reverse_stack.pop().map(|v1| {
if v1.parse::<i32>().is_ok() {
// Binary op
self.stack.pop();
self.stack.push(v2.parse::<i32>().unwrap());
self.stack.push(v1.parse::<i32>().unwrap());
self.instructions.push_front(self.reverse_stack.pop().unwrap());
} else {
// POP
self.stack.push(v2.parse::<i32>().unwrap());
self.instructions.push_front(v1);
}
});
} else {
// PUSH
self.stack.pop();
self.instructions.push_front(v2);
}
Ok(())
})
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20210120-1538662-nzc76v/solution)
    Finished test [unoptimized + debuginfo] target(s) in 2.76s
     Running target/debug/deps/solution_test-8916805fc40a2dab

running 12 tests
test solution_test::test_arg_number ... ok
test solution_test::test_arithmetic_back ... ok
test solution_test::test_arithmetic_basic ... ok
test solution_test::test_div_1 ... ok
test solution_test::test_div_2 ... ok
test solution_test::test_errors_1 ... ok
test solution_test::test_errors_2 ... ok
test solution_test::test_instructions_after_error ... FAILED
test solution_test::test_invalid_args ... ok
test solution_test::test_pop ... ok
test solution_test::test_push ... ok
test solution_test::test_restoring_instructions ... ok

failures:

---- solution_test::test_instructions_after_error stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `["PUSH 2", "DIV", "DIV", "PUSH 3"]`,
 right: `["PUSH 0", "PUSH 2", "DIV", "PUSH 3"]`', tests/solution_test.rs:219:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    solution_test::test_instructions_after_error

test result: FAILED. 11 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test solution_test'

История (1 версия и 0 коментара)

Борис качи първо решение на 17.01.2021 22:29 (преди над 4 години)