Решение на Basic BASIC от Билян Хаджи
Резултати
- 3 точки от тестове
- 0 бонус точки
- 3 точки общо
- 2 успешни тест(а)
- 13 неуспешни тест(а)
Код
use std::io::{self, Write, Read};
use std::collections::{BTreeMap, HashMap};
use std::io::{BufRead, BufReader, BufWriter};
use std::str::FromStr;
use std::collections::hash_map::Entry::Vacant;
use std::collections::hash_map::Entry::Occupied;
use std::collections::btree_map::Entry::Vacant as BTreeEntryVacant;
use std::collections::btree_map::Entry::Vacant as BTreeEntryOccupied;
// Така и не ми стигна време да направя run(), така че самата програма няма да работи :(
// Опитах се да разделя валидацията от самото изпълнение вместо да са на едно място,
// но просто накрая всичко стана мазало.
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
#[derive(Debug, PartialEq, Clone)]
enum InterpreterCommand {
Print { value: String },
Read { variable_name: String },
Goto { line_number: String },
IfGoto { condition: String, line_number: String }
}
impl FromStr for InterpreterCommand {
type Err = ();
fn from_str(input: &str) -> Result<InterpreterCommand, Self::Err> {
let tokens = input.split_whitespace().collect::<Vec<_>>();
if tokens.len() < 2 {
return Err(());
}
let command = tokens[0];
match command {
"PRINT" => {
if tokens.len() != 2 {
Err(())
} else {
Ok(InterpreterCommand::Print { value: tokens[1].to_string() })
}
},
"READ" => {
if tokens.len() != 2 {
Err(())
} else {
Ok(InterpreterCommand::Read { variable_name: tokens[1].to_string() })
}
},
"GOTO" => {
if tokens.len() != 2 {
Err(())
} else {
Ok(InterpreterCommand::Goto { line_number: tokens[1].to_string() })
}
},
"IF" => {
if tokens.len() != 6 {
Err(())
} else {
if tokens[4] != "GOTO" {
return Err(());
}
let condition = &tokens[1..4].to_vec().join(" ");
Ok(InterpreterCommand::IfGoto { condition: condition.to_string(), line_number: tokens[5].to_string() })
}
},
_ => Err(())
}
}
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: BufWriter<&'a mut W>,
// Каквито полета ви трябват
lines: BTreeMap<u16, InterpreterCommand>,
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 {
let lines = BTreeMap::new();
let buf_input = BufReader::new(input);
let buf_output = BufWriter::new(output);
let variables = HashMap::new();
Interpreter {
input: buf_input,
output: buf_output,
lines,
variables
}
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
fn validate_command(&self, command: InterpreterCommand, code: String) -> Result<(), InterpreterError> {
match command {
InterpreterCommand::Print{ value } => {
let first_char = value.chars().nth(0).expect("At least one char");
if !first_char.is_alphanumeric() {
return Err(InterpreterError::SyntaxError{ code: code.to_string() } );
}
if first_char.is_numeric() {
match value.to_string().parse::<u16>() {
Err(_) => Err(InterpreterError::NotANumber{ value: value.to_string() }),
Ok(_) => Ok(())
}
} else {
Ok(())
}
},
InterpreterCommand::Read { variable_name } => {
let first_char = variable_name.chars().nth(0).expect("At least one char");
if !first_char.is_uppercase() {
Err(InterpreterError::SyntaxError{ code })
} else {
Ok(())
}
},
InterpreterCommand::Goto { line_number } => {
match line_number.parse::<u16>() {
Err(_) => Err(InterpreterError::SyntaxError{ code }),
Ok(_) => Ok(())
}
},
InterpreterCommand::IfGoto { condition, line_number } => {
match line_number.parse::<u16>() {
Err(_) => Err(InterpreterError::SyntaxError{ code }),
Ok(_) => Ok(())
}
}
}
}
}
// Не забравяйте 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 tokens = code.split_whitespace().collect::<Vec<_>>();
if tokens.len() < 1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
let first_token = tokens[0].parse::<u16>();
match first_token {
Ok(line_number) => {
if tokens.len() == 1 {
return Ok(());
}
let command_res = tokens[1..].to_vec().join(" ").parse::<InterpreterCommand>();
match command_res {
Err(_) => Err(InterpreterError::SyntaxError { code: code.to_string() }),
Ok(command) => {
let validation_result = self.validate_command(command.clone(), code.to_string());
match validation_result {
Err(err) => Err(err),
Ok(_) => {
self.lines.insert(line_number, command.clone());
Ok(())
}
}
}
}
},
Err(_) => Err(InterpreterError::SyntaxError { code: code.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).expect("At least one char").is_uppercase() {
match self.variables.get(value) {
None => Err(InterpreterError::UnknownVariable{ name: value.to_string() }),
Some(&result) => Ok(result.clone())
}
} else {
match value.to_string().parse::<u16>() {
Err(_) => Err(InterpreterError::NotANumber{ value: value.to_string() }),
Ok(result) => Ok(result)
}
}
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
fn generate_order_of_execution(&mut self) -> BTreeMap<&u16, usize> {
self.lines.keys().enumerate().map(|(i, j)| (j, i)).into_iter().collect()
}
}
// Не забравяйте lifetime анотацията
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
if self.lines.len() < 1 {
return Ok(());
}
let order_of_execution = self.generate_order_of_execution();
let mut prev_line = 0;
while let Some(current_line_entry) = self.lines.range(prev_line..).next() {
let current_command = self.lines.get(current_line_entry.0).unwrap();
match current_command {
InterpreterCommand::Print { value } => {
let first_char = value.chars().nth(0).expect("At least one char");
if first_char.is_lowercase() {
self.output.write(value.as_bytes());
} else {
match self.eval_value(value) {
Err(err) => return Err(err),
Ok(result) => {
self.output.write(result.to_string().as_bytes());
return Ok(());
}
}
}
}
InterpreterCommand::Read { variable_name } => {
todo!()
// let first_char = variable_name.chars().nth(0).expect("At least one char");
// let variable_value_res = self.input.lines().next().unwrap().unwrap().clone().parse::<u16>();
// match variable_value_res {
// Err(_) => return Err(InterpreterError::RuntimeError{ line_number: current_line_entry.0.clone(), message: "Cannot read value".to_string() }),
// Ok(variable_value) => {
// self.variables.insert(variable_name.clone(), variable_value);
// return Ok(());
// }
// }
}
InterpreterCommand::Goto { line_number } => {
let line_number_val = line_number.parse::<u16>().unwrap();
todo!()
// match self.lines.entry(line_number_val) {
// BTreeEntryVacant(_) => return Err(InterpreterError::RuntimeError{ line_number: line_number_val, message: "Line not found".to_string() }),
// BTreeEntryOccupied(mut entry) => {
// prev_line = self.lines.range(..line_number_val).next_back().unwrap().0.clone();
// return Ok(());
// },
// }
}
InterpreterCommand::IfGoto { condition, line_number } => {
todo!()
}
}
}
Ok(())
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1jtd67l/solution) warning: unused import: `std::collections::hash_map::Entry::Vacant` --> src/lib.rs:5:5 | 5 | use std::collections::hash_map::Entry::Vacant; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default warning: unused import: `std::collections::hash_map::Entry::Occupied` --> src/lib.rs:6:5 | 6 | use std::collections::hash_map::Entry::Occupied; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused import: `std::collections::btree_map::Entry::Vacant as BTreeEntryVacant` --> src/lib.rs:7:5 | 7 | use std::collections::btree_map::Entry::Vacant as BTreeEntryVacant; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused import: `std::collections::btree_map::Entry::Vacant as BTreeEntryOccupied` --> src/lib.rs:8:5 | 8 | use std::collections::btree_map::Entry::Vacant as BTreeEntryOccupied; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: unused import: `BufRead` --> src/lib.rs:3:15 | 3 | use std::io::{BufRead, BufReader, BufWriter}; | ^^^^^^^ warning: unused variable: `condition` --> src/lib.rs:141:42 | 141 | InterpreterCommand::IfGoto { condition, line_number } => { | ^^^^^^^^^ help: try ignoring the field: `condition: _` | = note: `#[warn(unused_variables)]` on by default warning: unused variable: `order_of_execution` --> src/lib.rs:246:13 | 246 | let order_of_execution = self.generate_order_of_execution(); | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_order_of_execution` warning: unused variable: `variable_name` --> src/lib.rs:268:44 | 268 | InterpreterCommand::Read { variable_name } => { | ^^^^^^^^^^^^^ help: try ignoring the field: `variable_name: _` warning: unused variable: `line_number_val` --> src/lib.rs:283:25 | 283 | let line_number_val = line_number.parse::<u16>().unwrap(); | ^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_line_number_val` warning: unused variable: `condition` --> src/lib.rs:293:46 | 293 | InterpreterCommand::IfGoto { condition, line_number } => { | ^^^^^^^^^ help: try ignoring the field: `condition: _` warning: unused variable: `line_number` --> src/lib.rs:293:57 | 293 | InterpreterCommand::IfGoto { condition, line_number } => { | ^^^^^^^^^^^ help: try ignoring the field: `line_number: _` warning: variable does not need to be mutable --> src/lib.rs:248:13 | 248 | let mut prev_line = 0; | ----^^^^^^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default warning: field `input` is never read --> src/lib.rs:83:5 | 82 | pub struct Interpreter<'a, R: Read, W: Write> { | ----------- field in this struct 83 | input: BufReader<R>, | ^^^^^ | = note: `#[warn(dead_code)]` on by default warning: unused `Result` that must be used --> src/lib.rs:257:25 | 257 | self.output.write(value.as_bytes()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled = note: `#[warn(unused_must_use)]` on by default warning: unused `Result` that must be used --> src/lib.rs:262:33 | 262 | ... self.output.write(result.to_string().as_bytes()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled warning: `solution` (lib) generated 15 warnings Finished test [unoptimized + debuginfo] target(s) in 1.81s Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01) running 15 tests test solution_test::test_basic_goto ... FAILED test solution_test::test_basic_if ... FAILED test solution_test::test_basic_input ... FAILED test solution_test::test_basic_print ... FAILED test solution_test::test_basic_read ... FAILED test solution_test::test_erroring_goto ... FAILED test solution_test::test_full_program ... FAILED test solution_test::test_io_error_write ... FAILED test solution_test::test_line_order_and_overwriting ... FAILED 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 test solution_test::test_io_error_read ... FAILED failures: ---- solution_test::test_basic_goto stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"1"`, right: `"1\n3\n"`', tests/solution_test.rs:193:9 thread 'solution_test::test_basic_goto' panicked at 'assertion failed: `(left == right)` left: `"1"`, right: `"1\n3\n"`', tests/solution_test.rs:180:5 ---- solution_test::test_basic_if stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:294:21 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'solution_test::test_basic_if' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:212:5 ---- solution_test::test_basic_input stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_basic_input' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:103:5 ---- solution_test::test_basic_print stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"42"`, right: `"42\n24\n"`', tests/solution_test.rs:78:9 thread 'solution_test::test_basic_print' panicked at 'assertion failed: `(left == right)` left: `"42"`, right: `"42\n24\n"`', tests/solution_test.rs:67:5 ---- solution_test::test_basic_read stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_basic_read' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:120:5 ---- solution_test::test_erroring_goto stdout ---- thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 20, .. })"', tests/solution_test.rs:206:9 thread 'solution_test::test_erroring_goto' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 20, .. })"', tests/solution_test.rs:199:5 ---- solution_test::test_full_program stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_full_program' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:230:5 ---- solution_test::test_io_error_write stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_io_error_write' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:358:5 ---- solution_test::test_line_order_and_overwriting stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"1"`, right: `"1\n2\n4\n"`', tests/solution_test.rs:97:9 thread 'solution_test::test_line_order_and_overwriting' panicked at 'assertion failed: `(left == right)` left: `"1"`, right: `"1\n2\n4\n"`', tests/solution_test.rs:84:5 ---- solution_test::test_print_cyrillic stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_print_cyrillic' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:160:5 ---- solution_test::test_print_vars_and_strings stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1jtd67l/solution/src/lib.rs:269:21 thread 'solution_test::test_print_vars_and_strings' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:140:5 ---- solution_test::test_runtime_errors stdout ---- thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 11, .. })"', tests/solution_test.rs:307:9 thread 'solution_test::test_runtime_errors' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 11, .. })"', tests/solution_test.rs:301:5 ---- solution_test::test_io_error_read stdout ---- thread 'solution_test::test_io_error_read' panicked at 'called `Result::unwrap()` on an `Err` value: Timeout', tests/solution_test.rs:344:5 failures: solution_test::test_basic_goto solution_test::test_basic_if solution_test::test_basic_input solution_test::test_basic_print solution_test::test_basic_read solution_test::test_erroring_goto solution_test::test_full_program solution_test::test_io_error_read solution_test::test_io_error_write solution_test::test_line_order_and_overwriting solution_test::test_print_cyrillic solution_test::test_print_vars_and_strings solution_test::test_runtime_errors test result: FAILED. 2 passed; 13 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.00s error: test failed, to rerun pass `--test solution_test`