Решение на Basic BASIC от Мартин Маринов
Резултати
- 15 точки от тестове
- 0 бонус точки
- 15 точки общо
- 11 успешни тест(а)
- 4 неуспешни тест(а)
Код
use std::collections::BTreeMap;
use std::io::{self, BufRead, BufReader, Read, Write};
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
input: BufReader<R>,
output: &'a mut W,
rows: BTreeMap<u16, Vec<String>>,
variables: BTreeMap<String, 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,
rows: BTreeMap::new(),
variables: BTreeMap::new(),
}
}
}
// Не забравяйте lifetime анотацията
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 splitted: Vec<&str> = code.split_whitespace().collect();
if splitted.len() < 3 || splitted.len() > 7 {
return Err(InterpreterError::SyntaxError {
code: String::from("Arguments number error"),
});
}
let first: &str = splitted.first().unwrap();
if first.parse::<u16>().is_err() {
return Err(InterpreterError::NotANumber {
value: String::from(first),
});
}
let second: &str = splitted.get(1).unwrap();
if second != "GOTO" && second != "IF" && second != "PRINT" && second != "READ" {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid command"),
});
}
if (second == "PRINT" || second == "GOTO" || second == "READ") && splitted.len() != 3 {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
if second == "READ" && !char::is_uppercase(splitted.last().unwrap().chars().nth(0).unwrap())
{
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
if second == "IF" && splitted.len() != 7 {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
let values: Vec<String> = code.split_whitespace().map(|x| String::from(x)).collect();
self.rows.insert(first.parse::<u16>().unwrap(), values);
Ok(())
}
}
// Не забравяйте lifetime анотацията
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> {
let first_symbol = value.chars().nth(0).unwrap();
if char::is_uppercase(first_symbol) {
if self.variables.contains_key(value) {
return Ok(self.variables.get(value).unwrap().clone());
} else {
return Err(InterpreterError::UnknownVariable {
name: String::from(value),
});
}
} else if value.parse::<u16>().is_ok() {
return Ok(value.parse::<u16>().unwrap());
}
Err(InterpreterError::NotANumber {
value: String::from(value),
})
}
}
// Не забравяйте lifetime анотацията
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let rows_keys: Vec<&u16> = self.rows.keys().collect();
let mut current_row_number = **rows_keys.first().unwrap();
loop {
let interpret_line = self.rows.get(¤t_row_number).unwrap();
let command = interpret_line.get(1).unwrap().to_string();
if command == "PRINT" {
let value_to_eval = interpret_line.last().unwrap();
if char::is_lowercase(value_to_eval.chars().nth(0).unwrap()) {
let result = self
.output
.write((value_to_eval.to_string() + "\n").as_bytes());
if result.is_err() {
return Err(InterpreterError::IoError(result.unwrap_err()));
}
} else {
let result_value = self.eval_value(value_to_eval).unwrap();
let result = self
.output
.write((result_value.to_string() + "\n").as_bytes());
if result.is_err() {
return Err(InterpreterError::IoError(result.unwrap_err()));
}
}
} else if command == "READ" {
let variable_name = interpret_line.get(2).unwrap();
let mut input_value = String::new();
let result = self.input.read_line(&mut input_value);
if result.is_err() {
return Err(InterpreterError::IoError(result.unwrap_err()));
}
let parsed_to_number = input_value.trim_end().parse::<u16>();
if parsed_to_number.is_ok() {
self.variables
.insert(variable_name.clone(), parsed_to_number.unwrap());
} else {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Not a number readed"),
});
}
} else if command == "GOTO" {
let row_to_explore = interpret_line.get(2).unwrap().parse::<u16>();
if row_to_explore.is_ok() {
current_row_number = row_to_explore.unwrap().clone();
continue;
} else {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Invalid goto"),
});
}
} else if command == "IF" {
let first_argument = interpret_line.get(2).unwrap();
let second_argument = interpret_line.get(4).unwrap();
let operator = interpret_line.get(3).unwrap();
if operator != "<" && operator != ">" && operator != "=" {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Invalid operator"),
});
}
let first_value = self.eval_value(&first_argument).unwrap();
let second_value = self.eval_value(&second_argument).unwrap();
if operator == "<" && (first_value < second_value)
|| (operator == ">" && (first_value > second_value))
|| (operator == "=" && (first_value == second_value))
{
let row_to_explore = interpret_line.get(6).unwrap().parse::<u16>();
if row_to_explore.is_ok() {
current_row_number = row_to_explore.unwrap().clone();
continue;
} else {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid argument row number"),
});
}
}
} else {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid arguments"),
});
}
let curr_row_index = rows_keys
.iter()
.position(|&x| x == ¤t_row_number)
.unwrap();
if curr_row_index + 1 >= rows_keys.len() {
break;
}
current_row_number = **rows_keys.get(curr_row_index + 1).unwrap();
}
Ok(())
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1bi75z5/solution) Finished test [unoptimized + debuginfo] target(s) in 1.61s Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01) running 15 tests test solution_test::test_basic_if ... ok test solution_test::test_basic_goto ... 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_full_program ... ok test solution_test::test_io_error_write ... ok test solution_test::test_io_error_read ... 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 'called `Option::unwrap()` on a `None` value', /tmp/d20230111-3772066-1bi75z5/solution/src/lib.rs:148:69 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'solution_test::test_erroring_goto' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:199:5 ---- solution_test::test_runtime_errors stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: UnknownVariable { name: "A" }', /tmp/d20230111-3772066-1bi75z5/solution/src/lib.rs:163:71 thread 'solution_test::test_runtime_errors' panicked at 'called `Result::unwrap()` on an `Err` value: UnknownVariable { name: "A" }', 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_runtime_errors solution_test::test_syntax_errors_1 solution_test::test_syntax_errors_2 test result: FAILED. 11 passed; 4 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:31 (преди над 2 години)
use std::collections::BTreeMap;
use std::io::{self, BufRead, BufReader, Read, Write};
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
input: BufReader<R>,
output: &'a mut W,
rows: BTreeMap<u16, Vec<String>>,
variables: BTreeMap<String, 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,
rows: BTreeMap::new(),
variables: BTreeMap::new(),
}
}
}
// Не забравяйте lifetime анотацията
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 splitted: Vec<&str> = code.split_whitespace().collect();
if splitted.len() < 3 || splitted.len() > 7 {
return Err(InterpreterError::SyntaxError {
code: String::from("Arguments number error"),
});
}
let first: &str = splitted.first().unwrap();
if first.parse::<u16>().is_err() {
return Err(InterpreterError::NotANumber {
value: String::from(first),
});
}
let second: &str = splitted.get(1).unwrap();
if second != "GOTO" && second != "IF" && second != "PRINT" && second != "READ" {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid command"),
});
}
if (second == "PRINT" || second == "GOTO" || second == "READ") && splitted.len() != 3 {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
if second == "READ" && !char::is_uppercase(splitted.last().unwrap().chars().nth(0).unwrap())
{
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
if second == "IF" && splitted.len() != 7 {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid number of arguments"),
});
}
let values: Vec<String> = code.split_whitespace().map(|x| String::from(x)).collect();
self.rows.insert(first.parse::<u16>().unwrap(), values);
Ok(())
}
}
// Не забравяйте lifetime анотацията
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> {
let first_symbol = value.chars().nth(0).unwrap();
if char::is_uppercase(first_symbol) {
if self.variables.contains_key(value) {
return Ok(self.variables.get(value).unwrap().clone());
} else {
return Err(InterpreterError::UnknownVariable {
name: String::from(value),
});
}
} else if value.parse::<u16>().is_ok() {
return Ok(value.parse::<u16>().unwrap());
}
Err(InterpreterError::NotANumber {
value: String::from(value),
})
}
}
// Не забравяйте lifetime анотацията
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let rows_keys: Vec<&u16> = self.rows.keys().collect();
let mut current_row_number = **rows_keys.first().unwrap();
loop {
let interpret_line = self.rows.get(¤t_row_number).unwrap();
let command = interpret_line.get(1).unwrap().to_string();
if command == "PRINT" {
let value_to_eval = interpret_line.last().unwrap();
if char::is_lowercase(value_to_eval.chars().nth(0).unwrap()) {
- self.output
- .write((value_to_eval.to_string() + "\n").as_bytes())
- .unwrap();
+ let result = self
+ .output
+ .write((value_to_eval.to_string() + "\n").as_bytes());
+
+ if result.is_err() {
+ return Err(InterpreterError::IoError(result.unwrap_err()));
+ }
} else {
let result_value = self.eval_value(value_to_eval).unwrap();
- self.output
- .write((result_value.to_string() + "\n").as_bytes())
- .unwrap();
+ let result = self
+ .output
+ .write((result_value.to_string() + "\n").as_bytes());
+
+ if result.is_err() {
+ return Err(InterpreterError::IoError(result.unwrap_err()));
+ }
}
} else if command == "READ" {
let variable_name = interpret_line.get(2).unwrap();
let mut input_value = String::new();
- self.input.read_line(&mut input_value).unwrap();
+ let result = self.input.read_line(&mut input_value);
+ if result.is_err() {
+ return Err(InterpreterError::IoError(result.unwrap_err()));
+ }
+
let parsed_to_number = input_value.trim_end().parse::<u16>();
if parsed_to_number.is_ok() {
self.variables
.insert(variable_name.clone(), parsed_to_number.unwrap());
} else {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Not a number readed"),
});
}
} else if command == "GOTO" {
let row_to_explore = interpret_line.get(2).unwrap().parse::<u16>();
if row_to_explore.is_ok() {
current_row_number = row_to_explore.unwrap().clone();
continue;
} else {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Invalid goto"),
});
}
} else if command == "IF" {
let first_argument = interpret_line.get(2).unwrap();
let second_argument = interpret_line.get(4).unwrap();
let operator = interpret_line.get(3).unwrap();
if operator != "<" && operator != ">" && operator != "=" {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: String::from("Invalid operator"),
});
}
let first_value = self.eval_value(&first_argument).unwrap();
let second_value = self.eval_value(&second_argument).unwrap();
if operator == "<" && (first_value < second_value)
|| (operator == ">" && (first_value > second_value))
|| (operator == "=" && (first_value == second_value))
{
let row_to_explore = interpret_line.get(6).unwrap().parse::<u16>();
if row_to_explore.is_ok() {
current_row_number = row_to_explore.unwrap().clone();
continue;
} else {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid argument row number"),
});
}
}
} else {
return Err(InterpreterError::SyntaxError {
code: String::from("Invalid arguments"),
});
}
let curr_row_index = rows_keys
.iter()
.position(|&x| x == ¤t_row_number)
.unwrap();
if curr_row_index + 1 >= rows_keys.len() {
break;
}
current_row_number = **rows_keys.get(curr_row_index + 1).unwrap();
}
Ok(())
}
-}
+}