Решение на Basic BASIC от Кристиян Войнски

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

Към профила на Кристиян Войнски

Резултати

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

Код

use std::collections::HashMap;
use std::io::{self, Write, Read, BufReader, BufRead};
use std::ops::Add;
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: &'a mut W,
instructions: HashMap<u16, Vec<String>>,
variables: HashMap<String, u16>,
last_instruction: u16,
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Конструира нов интерпретатор, който чете вход от `input` чрез `READ` командата и пише в
/// `output` чрез `PRINT`.
///
pub fn new(input: R, output: &'a mut W) -> Self {
Self { input: BufReader::new(input), output, instructions: HashMap::new(), variables: HashMap::new(), last_instruction: 0 }
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 16-битово
/// unsigned число, последвано от някаква команда и нейните параметри. Всички компоненти на
/// реда ще бъдат разделени точно с един интервал -- или поне така ще ги тестваме.
///
/// Примерни редове:
///
/// 10 IF 2 > 1 GOTO 30
/// 20 GOTO 10
/// 30 READ V
/// 40 PRINT V
///
/// В случай, че реда не е валидна команда (детайли по-долу), очакваме да върнете
/// `InterpreterError::SyntaxError` с кода, който е бил подаден.
///
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let commands: Vec<&str> = code.split_whitespace().collect();
if commands.len() < 3 || commands.len() > 7 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if commands.first().unwrap().parse::<u16>().is_err() {
return Err(InterpreterError::NotANumber { value: commands.first().unwrap().parse().unwrap() });
}
let command: &str = commands.get(1).unwrap();
let valid_commands: Vec<&str> = vec!["PRINT", "GOTO", "READ", "IF"];
if !valid_commands.contains(&command) {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if (command == "PRINT" || command == "GOTO") && commands.len() != 3 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "READ" && commands.len() != 3 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "READ" && !char::is_uppercase(commands.last().unwrap().chars().nth(0).unwrap()) {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "IF" && commands.len() != 7 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
let cmd: Vec<&str> = commands.as_slice()[1..].to_vec();
self.instructions.insert(commands.first().unwrap().parse::<u16>().unwrap(),
cmd.iter().map(|el| String::from(*el)).collect());
Ok(())
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if char::is_uppercase(value.chars().nth(0).unwrap()) {
let res = self.variables.get(value);
if res.is_some() {
return Ok(*res.unwrap());
}
return Err(InterpreterError::UnknownVariable { name: value.to_string() });
}
let num = value.parse::<u16>();
if num.is_ok() {
return Ok(num.unwrap());
}
return Err(InterpreterError::NotANumber { value: value.to_string() });
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<&u16> = self.instructions.keys().collect();
keys.sort();
self.last_instruction = *keys.first().unwrap().clone();
loop {
let key = self.last_instruction;
let instruction = &self.instructions[&key];
match instruction.first().unwrap().as_str() {
"PRINT" => {
let value = instruction.last().unwrap();
if char::is_uppercase(value.chars().nth(0).unwrap()) {
if self.variables.contains_key(value) {
let res = self.output.write(self.variables[instruction.last().unwrap()].to_string().add("\n").as_bytes());
if res.is_err() {
return Err(InterpreterError::IoError(res.unwrap_err()));
}
} else {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
} else {
let res = self.output.write(value.clone().add("\n").as_bytes());
if res.is_err() {
return Err(InterpreterError::IoError(res.unwrap_err()));
}
}
}
"GOTO" => {
let move_to = instruction.get(1).unwrap().parse::<u16>();
if move_to.is_err() {
return Err(InterpreterError::RuntimeError { line_number: self.last_instruction, message: "Invalid operation".to_string() });
}
self.last_instruction = move_to.unwrap();
continue;
}
"IF" => {
let var1 = instruction.get(1).unwrap();
let var2 = instruction.get(3).unwrap();
if instruction.get(4).unwrap() != "GOTO" {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid operation".to_string() });
}
let goto = instruction.get(5).unwrap().parse::<u16>();
let sign = instruction.get(2).unwrap();
if sign != "=" && sign != ">" && sign != "<" {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid operation".to_string() });
}
if goto.is_err() {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
let val1 = self.eval_value(var1);
let val2 = self.eval_value(var2);
if val1.is_err() || val2.is_err() {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
match sign.as_str() {
">" => {
if val1.unwrap() > val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
"<" => {
if val1.unwrap() < val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
"=" => {
if val1.unwrap() == val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
_ => return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid row".to_string() })
}
}
"READ" => {
let mut value = String::new();
self.input.read_line(&mut value).unwrap();
let parsed = value.trim().parse::<u16>();
if parsed.is_ok() {
self.variables.insert(instruction.last().unwrap().clone(), parsed.unwrap());
} else {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Parsing failed".to_string() });
}
}
_ => return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid row".to_string() })
};
let idx = keys.iter().position(|&k| k == &self.last_instruction).unwrap();
if idx + 1 == keys.len() {
break;
}
self.last_instruction = *keys[idx + 1];
}
Ok(())
}
}

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

Compiling solution v0.1.0 (/tmp/d20230111-3772066-15fk7py/solution)
    Finished test [unoptimized + debuginfo] target(s) in 1.54s
     Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01)

running 15 tests
test solution_test::test_basic_goto ... ok
test solution_test::test_basic_if ... ok
test solution_test::test_basic_print ... ok
test solution_test::test_basic_input ... ok
test solution_test::test_basic_read ... ok
test solution_test::test_erroring_goto ... FAILED
test solution_test::test_io_error_read ... FAILED
test solution_test::test_full_program ... ok
test solution_test::test_io_error_write ... ok
test solution_test::test_line_order_and_overwriting ... ok
test solution_test::test_print_cyrillic ... ok
test solution_test::test_print_vars_and_strings ... ok
test solution_test::test_runtime_errors ... FAILED
test solution_test::test_syntax_errors_1 ... FAILED
test solution_test::test_syntax_errors_2 ... FAILED

failures:

---- solution_test::test_erroring_goto stdout ----
thread '<unnamed>' panicked at 'no entry found for key', /tmp/d20230111-3772066-15fk7py/solution/src/lib.rs:133:32
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'solution_test::test_erroring_goto' panicked at 'no entry found for key', tests/solution_test.rs:199:5

---- solution_test::test_io_error_read stdout ----
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "read error!" }', /tmp/d20230111-3772066-15fk7py/solution/src/lib.rs:216:54
thread 'solution_test::test_io_error_read' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "read error!" }', tests/solution_test.rs:344:5

---- solution_test::test_runtime_errors stdout ----
thread '<unnamed>' panicked at 'no entry found for key', /tmp/d20230111-3772066-15fk7py/solution/src/lib.rs:133:32
thread 'solution_test::test_runtime_errors' panicked at 'no entry found for key', tests/solution_test.rs:301:5

---- solution_test::test_syntax_errors_1 stdout ----
thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:263:9
thread 'solution_test::test_syntax_errors_1' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:254:5

---- solution_test::test_syntax_errors_2 stdout ----
thread '<unnamed>' panicked at 'Expression Err(NotANumber { value: "IF" }) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:295:9
thread 'solution_test::test_syntax_errors_2' panicked at 'Expression Err(NotANumber { value: "IF" }) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:275:5


failures:
    solution_test::test_erroring_goto
    solution_test::test_io_error_read
    solution_test::test_runtime_errors
    solution_test::test_syntax_errors_1
    solution_test::test_syntax_errors_2

test result: FAILED. 10 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

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

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

Кристиян качи първо решение на 10.01.2023 16:19 (преди над 2 години)

Кристиян качи решение на 10.01.2023 16:23 (преди над 2 години)

use std::collections::HashMap;
use std::io::{self, Write, Read, BufReader, BufRead};
use std::ops::Add;
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: &'a mut W,
instructions: HashMap<u16, Vec<String>>,
variables: HashMap<String, u16>,
last_instruction: u16,
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Конструира нов интерпретатор, който чете вход от `input` чрез `READ` командата и пише в
/// `output` чрез `PRINT`.
///
pub fn new(input: R, output: &'a mut W) -> Self {
Self { input: BufReader::new(input), output, instructions: HashMap::new(), variables: HashMap::new(), last_instruction: 0 }
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 16-битово
/// unsigned число, последвано от някаква команда и нейните параметри. Всички компоненти на
/// реда ще бъдат разделени точно с един интервал -- или поне така ще ги тестваме.
///
/// Примерни редове:
///
/// 10 IF 2 > 1 GOTO 30
/// 20 GOTO 10
/// 30 READ V
/// 40 PRINT V
///
/// В случай, че реда не е валидна команда (детайли по-долу), очакваме да върнете
/// `InterpreterError::SyntaxError` с кода, който е бил подаден.
///
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let commands: Vec<&str> = code.split_whitespace().collect();
if commands.len() < 3 || commands.len() > 7 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if commands.first().unwrap().parse::<u16>().is_err() {
return Err(InterpreterError::NotANumber { value: commands.first().unwrap().parse().unwrap() });
}
let command: &str = commands.get(1).unwrap();
let valid_commands: Vec<&str> = vec!["PRINT", "GOTO", "READ", "IF"];
if !valid_commands.contains(&command) {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if (command == "PRINT" || command == "GOTO") && commands.len() != 3 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "READ" && commands.len() != 3 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "READ" && !char::is_uppercase(commands.last().unwrap().chars().nth(0).unwrap()) {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
if command == "IF" && commands.len() != 7 {
return Err(InterpreterError::SyntaxError { code: "Invalid arguments".to_string() });
}
let cmd: Vec<&str> = commands.as_slice()[1..].to_vec();
self.instructions.insert(commands.first().unwrap().parse::<u16>().unwrap(),
cmd.iter().map(|el| String::from(*el)).collect());
Ok(())
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if char::is_uppercase(value.chars().nth(0).unwrap()) {
let res = self.variables.get(value);
if res.is_some() {
return Ok(*res.unwrap());
}
return Err(InterpreterError::UnknownVariable { name: value.to_string() });
}
let num = value.parse::<u16>();
if num.is_ok() {
return Ok(num.unwrap());
}
return Err(InterpreterError::NotANumber { value: value.to_string() });
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<&u16> = self.instructions.keys().collect();
keys.sort();
self.last_instruction = *keys.first().unwrap().clone();
loop {
let key = self.last_instruction;
let instruction = &self.instructions[&key];
match instruction.first().unwrap().as_str() {
"PRINT" => {
let value = instruction.last().unwrap();
if char::is_uppercase(value.chars().nth(0).unwrap()) {
if self.variables.contains_key(value) {
- self.output.write(self.variables[instruction.last().unwrap()].to_string().add("\n").as_bytes()).unwrap();
+ let res = self.output.write(self.variables[instruction.last().unwrap()].to_string().add("\n").as_bytes());
+ if res.is_err() {
+ return Err(InterpreterError::IoError(res.unwrap_err()));
+ }
} else {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
} else {
- self.output.write(value.clone().add("\n").as_bytes()).unwrap();
+ let res = self.output.write(value.clone().add("\n").as_bytes());
+ if res.is_err() {
+ return Err(InterpreterError::IoError(res.unwrap_err()));
+ }
}
}
"GOTO" => {
let move_to = instruction.get(1).unwrap().parse::<u16>();
if move_to.is_err() {
return Err(InterpreterError::RuntimeError { line_number: self.last_instruction, message: "Invalid operation".to_string() });
}
self.last_instruction = move_to.unwrap();
continue;
}
"IF" => {
let var1 = instruction.get(1).unwrap();
let var2 = instruction.get(3).unwrap();
if instruction.get(4).unwrap() != "GOTO" {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid operation".to_string() });
}
let goto = instruction.get(5).unwrap().parse::<u16>();
let sign = instruction.get(2).unwrap();
if sign != "=" && sign != ">" && sign != "<" {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid operation".to_string() });
}
if goto.is_err() {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
let val1 = self.eval_value(var1);
let val2 = self.eval_value(var2);
if val1.is_err() || val2.is_err() {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid variable".to_string() });
}
match sign.as_str() {
">" => {
if val1.unwrap() > val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
"<" => {
if val1.unwrap() < val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
"=" => {
if val1.unwrap() == val2.unwrap() {
self.last_instruction = goto.unwrap();
continue;
}
}
_ => return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid row".to_string() })
}
}
"READ" => {
let mut value = String::new();
self.input.read_line(&mut value).unwrap();
let parsed = value.trim().parse::<u16>();
if parsed.is_ok() {
self.variables.insert(instruction.last().unwrap().clone(), parsed.unwrap());
} else {
return Err(InterpreterError::RuntimeError { line_number: key, message: "Parsing failed".to_string() });
}
}
_ => return Err(InterpreterError::RuntimeError { line_number: key, message: "Invalid row".to_string() })
};
let idx = keys.iter().position(|&k| k == &self.last_instruction).unwrap();
if idx + 1 == keys.len() {
break;
}
self.last_instruction = *keys[idx + 1];
}
Ok(())
}
}