Решение на Reversible Interpreter от Християна Панайотова

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

Към профила на Християна Панайотова

Резултати

  • 10 точки от тестове
  • 0 бонус точки
  • 10 точки общо
  • 8 успешни тест(а)
  • 4 неуспешни тест(а)

Код

use std::{
collections::{HashMap, VecDeque},
str::SplitWhitespace,
};
#[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>,
command_config: HashMap<String, i32>,
should_update_history: bool,
history: Vec<Vec<String>>, // + още полета за поддръжка на .back() метода
}
impl Interpreter {
/// Конструира нов интерпретатор с празен стек и никакви инструкции.
pub fn new() -> Self {
let mut conf: HashMap<String, i32> = HashMap::new();
conf.insert("PUSH".to_string(), 1);
conf.insert("POP".to_string(), 0);
conf.insert("ADD".to_string(), 0);
conf.insert("MULL".to_string(), 0);
conf.insert("SUB".to_string(), 0);
conf.insert("DIV".to_string(), 0);
Interpreter {
instructions: VecDeque::new(),
stack: vec![],
command_config: conf,
history: vec![],
should_update_history: true,
}
}
/// Добавя инструкции от дадения списък към края на `instructions`. Примерно:
///
/// interpreter.add_instructions(&[
/// "PUSH 1",
/// "PUSH 2",
/// "ADD",
/// ]);
///
/// Инструкциите не се интерпретират, само се записват.
///
pub fn add_instructions(&mut self, instructions: &[&str]) {
for &instruction in instructions {
self.instructions.push_back(String::from(instruction))
}
}
/// Връща mutable reference към инструкцията, която ще се изпълни при
/// следващия `.forward()` -- първата в списъка/дека.
///
pub fn current_instruction(&mut self) -> Option<&mut String> {
let len = self.instructions.len();
if len == 0 {
return None;
} else {
let cur_instr = &mut (self.instructions[len - 1]);
return Some(cur_instr);
}
}
pub fn get_first_command(&self) -> Result<SplitWhitespace, RuntimeError> {
match self.instructions.get(0) {
None => Err(RuntimeError::NoInstructions),
Some(val) => Ok(val.split_whitespace()),
}
}
pub fn get_command_config(&self, command: &str) -> Result<&i32, RuntimeError> {
match self.command_config.get(command) {
None => Err(RuntimeError::InvalidCommand),
Some(val) => Ok(val),
}
}
fn update_history(&mut self, hist: Vec<String>) {
if self.should_update_history {
self.history.push(hist)
}
}
/// Интерпретира първата инструкция в `self.instructions` по правилата описани по-горе. Записва
/// някаква информация за да може успешно да се изпълни `.back()` в по-нататъшен момент.
///
/// Ако няма инструкции, връща `RuntimeError::NoInstructions`. Другите грешки идват от
/// обясненията по-горе.
///
pub fn forward(&mut self) -> Result<(), RuntimeError> {
let mut values = match self.get_first_command() {
Err(e) => return Err(e),
Ok(val) => val,
};
let command = match values.next() {
None => return Err(RuntimeError::InvalidCommand),
Some(value) => value,
};
let args_count = match self.get_command_config(command) {
Err(e) => return Err(e),
Ok(val) => val,
};
let mut args: Vec<i32> = vec![];
for arg in values {
match arg.parse::<i32>() {
Ok(n) => args.push(n),
Err(_) => return Err(RuntimeError::InvalidCommand),
}
}
if *args_count != args.len() as i32 {
return Err(RuntimeError::InvalidCommand);
};
match command {
"PUSH" => {
self.stack.push(args[0]);
self.instructions.pop_front();
self.update_history(vec!["POP".to_string(), format!("PUSH {}", args[0])]);
Ok(())
}
"POP" => {
if self.stack.len() == 0 {
return Err(RuntimeError::StackUnderflow);
}
let val = self.stack.pop().unwrap();
if self.should_update_history {
self.update_history(vec![format!("PUSH {}", val), "POP".to_string()]);
}
self.instructions.pop_front();
Ok(())
}
"ADD" => self.calculate(&|x, y| Ok(x + y), "ADD"),
"MULL" => self.calculate(&|x, y| Ok(x * y), "MULL"),
"SUB" => self.calculate(&|x, y| Ok(x - y), "SUB"),
"DIV" => self.calculate(
&|x, y| match y {
0 => Err(RuntimeError::DivideByZero),
_ => Ok(x / y),
},
"DIV",
),
_ => Err(RuntimeError::InvalidCommand),
}
}
fn calculate(
&mut self,
f: &dyn Fn(i32, i32) -> Result<i32, RuntimeError>,
command: &str,
) -> Result<(), RuntimeError> {
let len = self.stack.len();
if len < 2 {
return Err(RuntimeError::StackUnderflow);
}
let first = self.stack[len - 1];
let second = self.stack[len - 2];
let val = match f(first, second) {
Ok(val) => val,
Err(e) => return Err(e),
};
let val1 = self.stack.pop().unwrap();
let val2 = self.stack.pop().unwrap();
self.stack.push(val);
self.instructions.pop_front();
self.update_history(vec![
"POP".to_string(),
format!("PUSH {}", val2),
format!("PUSH {}", val1),
command.to_string(),
]);
Ok(())
}
/// Вика `.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> {
let last = match self.history.pop() {
None => return Err(RuntimeError::InvalidCommand),
Some(elem) => elem,
};
self.should_update_history = false;
for (pos, e) in last.iter().enumerate() {
self.instructions.push_front(e.to_string());
if pos == last.len() - 1 {
continue;
}
match self.forward() {
Err(e) => return Err(e),
_ => (),
}
}
self.should_update_history = true;
Ok(())
}
}

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

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

running 12 tests
test solution_test::test_arg_number ... ok
test solution_test::test_arithmetic_back ... FAILED
test solution_test::test_arithmetic_basic ... FAILED
test solution_test::test_div_1 ... ok
test solution_test::test_div_2 ... ok
test solution_test::test_errors_1 ... FAILED
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_arithmetic_back stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidCommand', tests/solution_test.rs:99:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- solution_test::test_arithmetic_basic stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidCommand', tests/solution_test.rs:64:23

---- solution_test::test_errors_1 stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Err(InvalidCommand)`,
 right: `Err(NoInstructions)`', tests/solution_test.rs:167:5

---- solution_test::test_instructions_after_error stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: DivideByZero', tests/solution_test.rs:222:23


failures:
    solution_test::test_arithmetic_back
    solution_test::test_arithmetic_basic
    solution_test::test_errors_1
    solution_test::test_instructions_after_error

test result: FAILED. 8 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Християна качи първо решение на 19.01.2021 20:42 (преди над 4 години)