Решение на Basic BASIC от Петър Атанасов
Резултати
- 16 точки от тестове
- 0 бонус точки
- 16 точки общо
- 12 успешни тест(а)
- 3 неуспешни тест(а)
Код
use std::{
collections::{BTreeMap, HashMap},
fmt::{self, Display},
io::{self, BufRead, BufReader, Read, Write},
ops::Bound,
};
fn pure<A, B>(a: A) -> Result<A, B> {
Ok(a)
}
#[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,
current_line: u16,
instructions: BTreeMap<u16, Command>,
variables: HashMap<String, u16>, // Каквито други полета ви трябват
}
pub enum Operator {
GT,
EQ,
LT,
}
pub enum Value {
Variable(String),
Number(u32),
}
#[derive(Debug)]
pub enum Command {
Print {
value: String,
},
Read {
value: String,
},
Goto {
line: u16,
},
If {
value1: String,
value2: String,
op: String,
line: u16,
},
End,
}
impl fmt::Display for Command {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Command::Print { value } => write!(f, "PRINT: {}", value),
Command::Read { value } => write!(f, "READ: {}", value),
Command::Goto { line } => write!(f, "GOTO: {}", line),
Command::If {
value1,
value2,
op,
line,
} => write!(f, "IF: {} {} {} {}", value1, value2, op, line),
Command::End => write!(f, "END"),
}
}
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
/// Конструира нов интерпретатор, който чете вход от `input` чрез `READ` командата и пише в
/// `output` чрез `PRINT`.
///
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter {
input: BufReader::new(input),
output,
current_line: 0,
instructions: BTreeMap::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 mut iter = code.split_whitespace();
let give_syntax_error = || InterpreterError::SyntaxError {
code: code.to_string(),
};
let line = iter.next().ok_or(give_syntax_error())?;
let num: u16 = line
.to_string()
.parse()
.map_err(|_| InterpreterError::SyntaxError {
code: code.to_string(),
})?;
match iter.next().ok_or(give_syntax_error())? {
"PRINT" => match iter.collect::<Vec<_>>()[..] {
[value] => {
self.instructions.insert(
num,
Command::Print {
value: value.to_string(),
},
);
Ok(())
}
_ => Err(give_syntax_error()),
},
"READ" => match iter.collect::<Vec<_>>()[..] {
[var] => {
if !char::is_uppercase(var.clone().chars().next().unwrap()) {
Err(give_syntax_error())
} else {
self.instructions.insert(
num,
Command::Read {
value: var.to_string(),
},
);
Ok(())
}
}
_ => Err(give_syntax_error()),
},
"GOTO" => match iter.collect::<Vec<_>>()[..] {
[line_index] => {
self.instructions.insert(
num,
Command::Goto {
line: line_index.to_string().parse().map_err(|_| {
InterpreterError::SyntaxError {
code: line_index.to_string(),
}
})?,
},
);
Ok(())
}
_ => Err(give_syntax_error()),
},
"IF" => match iter.collect::<Vec<_>>()[..] {
[value1, op, value2, "GOTO", line_index] => {
let parsed_line = line_index.to_string().parse::<u16>().map_err(|_| {
InterpreterError::SyntaxError {
code: line_index.to_string(),
}
})?;
self.instructions.insert(
num,
Command::If {
value1: value1.to_string(),
value2: value2.to_string(),
op: op.to_string(),
line: parsed_line,
},
);
Ok(())
}
_ => Err(give_syntax_error()),
},
_ => Err(give_syntax_error()),
}
}
/// Оценява `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().next().unwrap()) {
match self.variables.get(&value.clone().to_string()) {
Some(val) => Ok(val.clone()),
None => Err(InterpreterError::UnknownVariable {
name: value.clone().to_string(),
}),
}
} else {
value
.to_string()
.parse()
.map_err(|_| InterpreterError::NotANumber {
value: value.to_string(),
})
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let give_runtime_error = |x: u16, y: String| InterpreterError::RuntimeError {
line_number: (x),
message: (y),
};
self.instructions.insert(
self.instructions.last_key_value().unwrap().0 + 1,
Command::End,
);
// self.instructions
// .iter()
// .for_each(|x| println!("INSTRUCTION {}", x.1));
self.current_line = self.instructions.first_key_value().unwrap().0.clone();
// println!(
// "LAST INSTRUCTION INDEX: {}",
// self.instructions.last_key_value().unwrap().0
// );
while self.current_line != *self.instructions.last_key_value().unwrap().0 {
match self.instructions.get(&self.current_line).unwrap() {
Command::Print { value } => {
if char::is_uppercase(value.chars().next().unwrap()) {
let mut to_print = self.eval_value(&value)?.to_string();
to_print.push('\n');
self.output
.write(&to_print.as_bytes())
.map_err(InterpreterError::IoError)?;
} else {
let parsed_value = value.parse::<u16>();
// let mut to_print = parsed_value.unwrap().to_string();
// to_print.push('\n');
if value.parse::<u16>().is_ok() {
let mut to_print = parsed_value.unwrap().to_string();
to_print.push('\n');
self.output.write(&to_print.as_bytes());
} else {
let mut to_print = value.clone();
to_print.push('\n');
self.output.write(to_print.as_bytes());
}
}
self.current_line = self
.instructions
.range((
Bound::Excluded(&self.current_line),
Bound::Included(self.instructions.last_key_value().unwrap().0),
))
.next()
.unwrap()
.0
.clone();
}
Command::Read { value } => {
let mut var = String::new();
self.input
.read_line(&mut var)
.map_err(|err| InterpreterError::IoError(err))?;
var.pop();
let insert: u16 = var.parse::<u16>().map_err(|err| {
println!("ERROR: {}", err);
return InterpreterError::RuntimeError {
line_number: self.current_line,
message: "Not a valid u16".to_string(),
};
})?;
self.current_line = self
.instructions
.range((
Bound::Excluded(&self.current_line),
Bound::Included(self.instructions.last_key_value().unwrap().0),
))
.next()
.unwrap()
.0
.clone();
self.variables.insert(value.clone(), insert);
}
Command::Goto { line } => match self.instructions.get(line) {
Some(_) => {
self.current_line = line.clone();
}
None => {
return Err(InterpreterError::RuntimeError {
line_number: self.current_line,
message: "No such line exists".to_string(),
});
}
},
Command::If {
value1,
value2,
op,
line,
} => todo!(),
Command::End => (),
}
}
Ok(())
}
}
// За тестване че някакъв резултат пасва на някакъв pattern:
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
let result = $expr;
if let $pat = result {
// all good
} else {
assert!(
false,
"Expression {:?} does not match the pattern {:?}",
result,
stringify!($pat)
);
}
};
}
#[test]
fn test_basic_1() {
// Забележете `b""` -- низ от байтове
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 READ B").unwrap();
interpreter.add("30 PRINT A").unwrap();
interpreter.add("40 PRINT B").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n");
}
#[test]
fn test_basic_2() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 PRINT"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_basic_3() {
// Забележете `b""` -- низ от байтове
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 READ B").unwrap();
interpreter.add("30 PRINT 13").unwrap();
interpreter.add("40 PRINT B").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "13\n2\n");
}
#[test]
fn test_basic_4() {
// Забележете `b""` -- низ от байтове
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 READ B").unwrap();
interpreter.add("30 PRINT pavel").unwrap();
interpreter.add("40 PRINT B").unwrap();
interpreter.run().unwrap();
// assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "pavel\n2\n");
}
struct NotBuffered {}
impl std::io::Read for NotBuffered {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Ok(0)
}
}
#[test]
fn test_not_buffered() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT 10").unwrap();
interpreter.run().unwrap();
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1itn39g/solution) warning: unused import: `Display` --> src/lib.rs:3:17 | 3 | fmt::{self, Display}, | ^^^^^^^ | = note: `#[warn(unused_imports)]` on by default warning: unused macro definition: `assert_match` --> src/lib.rs:330:14 | 330 | macro_rules! assert_match { | ^^^^^^^^^^^^ | = note: `#[warn(unused_macros)]` on by default warning: unused variable: `give_runtime_error` --> src/lib.rs:228:13 | 228 | let give_runtime_error = |x: u16, y: String| InterpreterError::RuntimeError { | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_give_runtime_error` | = note: `#[warn(unused_variables)]` on by default warning: unused variable: `value1` --> src/lib.rs:317:21 | 317 | value1, | ^^^^^^ help: try ignoring the field: `value1: _` warning: unused variable: `value2` --> src/lib.rs:318:21 | 318 | value2, | ^^^^^^ help: try ignoring the field: `value2: _` warning: unused variable: `op` --> src/lib.rs:319:21 | 319 | op, | ^^ help: try ignoring the field: `op: _` warning: unused variable: `line` --> src/lib.rs:320:21 | 320 | line, | ^^^^ help: try ignoring the field: `line: _` warning: function `pure` is never used --> src/lib.rs:8:4 | 8 | fn pure<A, B>(a: A) -> Result<A, B> { | ^^^^ | = note: `#[warn(dead_code)]` on by default warning: unused `Result` that must be used --> src/lib.rs:260:29 | 260 | ... self.output.write(&to_print.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:264:29 | 264 | ... self.output.write(to_print.as_bytes()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled warning: `solution` (lib) generated 10 warnings Finished test [unoptimized + debuginfo] target(s) in 1.59s 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 ... ok test solution_test::test_basic_print ... ok test solution_test::test_basic_read ... ok test solution_test::test_erroring_goto ... ok test solution_test::test_io_error_read ... ok test solution_test::test_full_program ... FAILED 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_syntax_errors_1 ... ok test solution_test::test_runtime_errors ... FAILED test solution_test::test_syntax_errors_2 ... ok failures: ---- solution_test::test_basic_if stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1itn39g/solution/src/lib.rs:321:22 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_full_program stdout ---- thread '<unnamed>' panicked at 'not yet implemented', /tmp/d20230111-3772066-1itn39g/solution/src/lib.rs:321:22 thread 'solution_test::test_full_program' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:230:5 ---- solution_test::test_runtime_errors stdout ---- thread '<unnamed>' panicked at 'Expression Err(UnknownVariable { name: "A" }) 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 Err(UnknownVariable { name: "A" }) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 11, .. })"', tests/solution_test.rs:301:5 failures: solution_test::test_basic_if solution_test::test_full_program solution_test::test_runtime_errors test result: FAILED. 12 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`