Решение на Basic BASIC от Таня Димова
Резултати
- 8 точки от тестове
- 0 бонус точки
- 8 точки общо
- 6 успешни тест(а)
- 9 неуспешни тест(а)
Код
use std::io::{self, Write, Read, BufRead};
use std::collections::HashMap;
use std::io::BufReader;
#[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: R,
output: &'a mut W,
code: HashMap<u16, Vec<String>>,
variables: HashMap<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, output, code:HashMap::new(), variables:HashMap::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 line: Vec<&str> = code.split_whitespace().collect();
let line_code;
match line[0].parse::<u16>(){
Ok(number) => line_code = number,
Err(_) => return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
let cmd;
if line[1] == "PRINT"{
cmd = command::PRINT;
} else if line[1] == "READ"{
cmd = command::READ;
}else if line[1] == "GOTO"{
cmd = command::GOTO;
}else if line[1] == "IF"{
cmd = command::IF;
} else {
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
match cmd{
command::PRINT => {
if line.len() != 3{
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
let mut command: Vec<String> = Vec::new();
for l in 1..line.len(){
command.push(line[l].to_string());
}
self.code.insert(line_code, command);
Ok(())
},
command::READ => {
if line.len() != 3{
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
if !line[2].chars().nth(0).unwrap().is_uppercase(){
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
let mut command: Vec<String> = Vec::new();
for l in 1..line.len(){
command.push(line[l].to_string());
}
self.code.insert(line_code, command);
Ok(())
},
command::GOTO => {
if line.len() != 3{
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
match line[2].parse::<u16>(){
Ok(_) => {
let mut command: Vec<String> = Vec::new();
for l in 1..line.len(){
command.push(line[l].to_string());
}
self.code.insert(line_code, command);
Ok(())
}
Err(_) => Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
},
command::IF => {
if line.len() != 6{
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
if line[4] != "GOTO"{
return Err(InterpreterError::SyntaxError {code: line[0].to_string()})
} else {
match line[5].parse::<u16>(){
Ok(_) => {
let mut command: Vec<String> = Vec::new();
for l in 1..line.len(){
command.push(line[l].to_string());
}
self.code.insert(line_code, command);
Ok(())
}
Err(_) => Err(InterpreterError::SyntaxError {code: line[0].to_string()})
}
}
},
}
}
}
// Не забравяйте 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> {
if value.chars().nth(0).unwrap().is_uppercase(){
match self.variables.get(value) {
Some(number) => Ok(*number),
None => Err(InterpreterError::UnknownVariable {name: value.to_string()})
}
} else {
match value.parse::<u16>(){
Ok(number) => {
Ok(number)
}
Err(_) => Err(InterpreterError::NotANumber {value: value.to_string()})
}
}
}
}
pub enum command {
PRINT,
READ,
IF,
GOTO,
}
// Не забравяйте lifetime анотацията
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut sorted: Vec<_> = self.code.iter().collect();
sorted.sort_by_key(|a| a.0);
let mut keys: Vec<u16> = Vec::new();
let mut values = Vec::new();
for (key, value) in sorted.iter() {
keys.push(**key);
values.push(value);
}
let mut index = 0;
while index < keys.len() {
let value: Vec<String> = values[index].to_vec();
let cmd;
if value[0] == "PRINT"{
cmd = command::PRINT;
} else if value[0] == "READ"{
cmd = command::READ;
}else if value[0] == "GOTO"{
cmd = command::GOTO;
}else if value[0] == "IF"{
cmd = command::IF;
} else {
return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") })
}
match cmd{
command::PRINT => {
if value[1].chars().nth(0).unwrap().is_lowercase(){
let mut result = value.get(1).unwrap().to_string();
result.push('\n');
match self.output.write_all(result.as_bytes()){
Err(e) => return Err(InterpreterError::IoError(e)),
_ => index += 1
}
} else {
let &mut result;
match self.eval_value(&value[1]){
Ok(number) => result = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let mut result_str: String = result.to_string();
result_str.push('\n');
match self.output.write_all(result_str.as_bytes()){
Err(e) => return Err(InterpreterError::IoError(e)),
_ => index += 1
}
}
},
command::READ =>{
let mut buffer = String::new();
let mut reader = BufReader::new(self.input.by_ref());
let len;
match reader.read_line(&mut buffer){
Err(e) => return Err(InterpreterError::IoError(e)),
Ok(number) => len = number,
}
buffer.pop();
let result;
match value[1].parse::<u16>(){
Ok(number) => {
result = number
}
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
self.variables.insert(buffer, result);
index += 1;
},
command::GOTO =>{
let num_value;
match self.eval_value(&value[1]){
Ok(number) => num_value = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let new_line = keys.iter().position(|&x| x == num_value).unwrap();
index = new_line;
},
command::IF => {
let num1;
match self.eval_value(&value[1]){
Ok(number) => num1 = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let num2;
match self.eval_value(&value[3]){
Ok(number) => num2 = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
if value[2] == "<" && num1 < num2{
let num_value;
match self.eval_value(&value[5]){
Ok(number) => num_value = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let new_line = keys.iter().position(|&x| x == num_value).unwrap();
index = new_line;
} else if value[2] == ">" && num1 > num2{
let num_value;
match self.eval_value(&value[5]){
Ok(number) => num_value = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let new_line = keys.iter().position(|&x| x == num_value).unwrap();
index = new_line;
} else if value[2] == "=" && num1 == num2{
let num_value;
match self.eval_value(&value[5]){
Ok(number) => num_value = number,
Err(InterpreterError::UnknownVariable { name: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Unknown variable {}", variable) }),
Err(InterpreterError::NotANumber { value: variable }) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: format!("Not a number {}", variable) }),
Err(_) => return Err(InterpreterError::RuntimeError { line_number: keys[index], message: String::from("Something went wrong") }),
}
let new_line = keys.iter().position(|&x| x == num_value).unwrap();
index = new_line;
} else {
index += 1;
}
}
}
}
Ok(())
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1d4kyb7/solution) warning: type `command` should have an upper camel case name --> src/lib.rs:161:10 | 161 | pub enum command { | ^^^^^^^ help: convert the identifier to upper camel case (notice the capitalization): `Command` | = note: `#[warn(non_camel_case_types)]` on by default warning: variable `len` is assigned to, but never used --> src/lib.rs:229:25 | 229 | let len; | ^^^ | = note: consider using `_len` instead = note: `#[warn(unused_variables)]` on by default warning: value assigned to `len` is never read --> src/lib.rs:232:39 | 232 | Ok(number) => len = number, | ^^^ | = help: maybe it is overwritten before being read? = note: `#[warn(unused_assignments)]` on by default warning: `solution` (lib) generated 3 warnings Finished test [unoptimized + debuginfo] target(s) in 1.62s 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 ... FAILED test solution_test::test_basic_input ... FAILED test solution_test::test_basic_print ... ok test solution_test::test_basic_read ... FAILED test solution_test::test_erroring_goto ... FAILED test solution_test::test_io_error_read ... ok test solution_test::test_full_program ... FAILED test solution_test::test_io_error_write ... FAILED test solution_test::test_line_order_and_overwriting ... ok test solution_test::test_print_cyrillic ... FAILED test solution_test::test_print_vars_and_strings ... FAILED test solution_test::test_runtime_errors ... FAILED test solution_test::test_syntax_errors_1 ... ok test solution_test::test_syntax_errors_2 ... ok failures: ---- solution_test::test_basic_if stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: SyntaxError { code: "10" }', tests/solution_test.rs:218:52 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'solution_test::test_basic_if' panicked at 'called `Result::unwrap()` on an `Err` value: SyntaxError { code: "10" }', tests/solution_test.rs:212:5 ---- solution_test::test_basic_input stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:111:31 thread 'solution_test::test_basic_input' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:103:5 ---- solution_test::test_basic_read stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:131:31 thread 'solution_test::test_basic_read' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:120:5 ---- solution_test::test_erroring_goto stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /tmp/d20230111-3772066-1d4kyb7/solution/src/lib.rs:253:78 thread 'solution_test::test_erroring_goto' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:199:5 ---- solution_test::test_full_program stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: SyntaxError { code: "20" }', tests/solution_test.rs:237:58 thread 'solution_test::test_full_program' panicked at 'called `Result::unwrap()` on an `Err` value: SyntaxError { code: "20" }', tests/solution_test.rs:230:5 ---- solution_test::test_io_error_write stdout ---- thread '<unnamed>' panicked at 'Expression Err(RuntimeError { line_number: 10, message: "Something went wrong" }) does not match the pattern "Err(InterpreterError::IoError(_))"', tests/solution_test.rs:366:9 thread 'solution_test::test_io_error_write' panicked at 'Expression Err(RuntimeError { line_number: 10, message: "Something went wrong" }) does not match the pattern "Err(InterpreterError::IoError(_))"', tests/solution_test.rs:358:5 ---- solution_test::test_print_cyrillic stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:171:31 thread 'solution_test::test_print_cyrillic' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:160:5 ---- solution_test::test_print_vars_and_strings stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:151:31 thread 'solution_test::test_print_vars_and_strings' panicked at 'called `Result::unwrap()` on an `Err` value: RuntimeError { line_number: 10, message: "Something went wrong" }', tests/solution_test.rs:140:5 ---- solution_test::test_runtime_errors stdout ---- thread '<unnamed>' panicked at 'Expression Err(RuntimeError { line_number: 30, message: "Something went wrong" }) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 40, .. })"', tests/solution_test.rs:315:9 thread 'solution_test::test_runtime_errors' panicked at 'Expression Err(RuntimeError { line_number: 30, message: "Something went wrong" }) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 40, .. })"', tests/solution_test.rs:301:5 failures: solution_test::test_basic_if solution_test::test_basic_input solution_test::test_basic_read solution_test::test_erroring_goto solution_test::test_full_program solution_test::test_io_error_write solution_test::test_print_cyrillic solution_test::test_print_vars_and_strings solution_test::test_runtime_errors test result: FAILED. 6 passed; 9 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`