Решение на Basic BASIC от Ива Манчевска
Резултати
- 8 точки от тестове
- 0 бонус точки
- 8 точки общо
- 6 успешни тест(а)
- 9 неуспешни тест(а)
Код
use std::io::{self, Write, Read};
use std::collections::HashMap;
#[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)]
enum CommandType {
PRINT,
READ,
GOTO,
IF,
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
input: R,
output: &'a mut W,
lines: Vec<(u16, (CommandType, String))>,
variables: HashMap<String, u16>,
}
impl From<io::Error> for InterpreterError {
fn from(error: io::Error) -> Self {
InterpreterError::IoError(error)
}
}
impl From<Result<(), InterpreterError>> for InterpreterError {
fn from(result: Result<(), InterpreterError>) -> Self {
result.unwrap_err()
}
}
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,
lines: Vec::new(),
variables: HashMap::new(),
}
}
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let mut parts = code.split_whitespace();
let line_number = parts.next().ok_or(InterpreterError::SyntaxError {
code: code.to_string(),
})?;
let line_number = line_number.parse::<u16>().map_err(|_| InterpreterError::SyntaxError {
code: code.to_string(),
})?;
let command_string = parts.next().ok_or(InterpreterError::SyntaxError {
code: code.to_string(),
})?;
let command = match command_string {
"PRINT" => CommandType::PRINT,
"READ" => CommandType::READ,
"GOTO" => CommandType::GOTO,
"IF" => CommandType::IF,
_ => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let line = parts.collect::<Vec<_>>().join(" ");
if command == CommandType::PRINT && line.split_whitespace().count()!=1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
if command == CommandType::READ && line.split_whitespace().count() !=1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
if command == CommandType::GOTO && line.split_whitespace().count() !=1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
if command == CommandType::IF && line.split_whitespace().count() != 5 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
self.lines.push((line_number, (command, line)));
Ok(())
}
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().all(char::is_uppercase) {
let value = match self.variables.get(value) {
Some(value) => value,
None => return Err(InterpreterError::UnknownVariable { name: value.to_string() }),
};
Ok(*value)
} else {
let value = value.parse::<u16>().map_err(|_| InterpreterError::NotANumber {
value: value.to_string(),
})?;
Ok(value)
}
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
//let mut current_line: _;
let mut idx = 0;
for /*(line_number, (command, line)) */mut current_line in &self.lines {
let line_number = current_line.0;
let mut command = &self.lines[idx as usize].1.0;
let mut line = &self.lines[idx as usize].1.1;
let mut parts = line.split_whitespace();
match command {
CommandType::PRINT => {
let value = parts.next().ok_or(InterpreterError::RuntimeError {
line_number: line_number,
message: "Missing value to print".to_string(),
})?;
if value.chars().all(char::is_uppercase) {
let value = self
.variables
.get(value)
.ok_or(InterpreterError::RuntimeError {
line_number: line_number,
message: "Undefined variable".to_string(),
})?;
writeln!(self.output, "{}", value)?;
} else if value.chars().all(char::is_numeric) {
let value = value.parse::<u16>().map_err(|_| InterpreterError::RuntimeError {
line_number: line_number,
message: "Not a number".to_string(),
})?;
writeln!(self.output, "{}", value)?;
} else {
writeln!(self.output, "{}", value)?;
}
}
CommandType::READ => {
let variable = parts.next().ok_or(Err(InterpreterError::RuntimeError {
line_number: line_number,
message: "Missing variable to read".to_string(),
}))?;
let mut input = String::new();
loop {
let mut buf = [0];
match self.input.read(&mut buf) {
Ok(0) => break,
Ok(_) => {
if buf[0] == b'\n' {
break;
} else {
input.push(buf[0] as char);
}
}
Err(e) => return Err(InterpreterError::IoError(e)),
}
}
let value = input.trim().parse::<u16>().map_err(|_| Err(InterpreterError::RuntimeError {
line_number: line_number,
message: "Not a number".to_string(),
}))?;
self.variables.insert(variable.to_string(), value);
}
CommandType::GOTO => {
let mut parts = line.split_whitespace();
let target = parts.next().ok_or(Err(InterpreterError::RuntimeError {
line_number: line_number,
message: "Missing line number for GOTO command".to_string(),
}))?;
let target = target.parse::<u16>().map_err(|_| InterpreterError::RuntimeError {
line_number: line_number,
message: "Invalid line number for GOTO command".to_string(),
})?;
let mut line_index = self
.lines
.iter()
.position(|(n, _)| *n == target);
current_line = &self.lines[line_index.unwrap()];
}
CommandType::IF => {
let mut parts = line.split_whitespace();
let value1 = parts.next().ok_or(InterpreterError::SyntaxError {
code: line.to_string(),
})?;
let value1 = self.eval_value(value1)?;
let operator = parts.next().ok_or(InterpreterError::SyntaxError {
code: line.to_string(),
})?;
let value2 = parts.next().ok_or(InterpreterError::SyntaxError {
code: line.to_string(),
})?;
let value2 = self.eval_value(value2)?;
let goto_text = parts.next();
let goto_line = parts.next().ok_or(InterpreterError::SyntaxError {
code: line.to_string(),
})?;
let goto_line = self.eval_value(goto_line)?;
let goto = match operator {
"=" => value1 == value2,
">" => value1 > value2,
"<" => value1 < value2,
_ => return Err(InterpreterError::SyntaxError { code: line.to_string() }),
};
let mut line_index = self
.lines
.iter()
.position(|(n, _)| *n == goto_line);
if goto {
current_line = &self.lines[line_index.unwrap() as usize]; //goto_line as usize - 1;
}else{
current_line = &self.lines[line_index.unwrap() as usize +1];
}
}
}
idx = idx+1;
}
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 IF A = 1 GOTO 40").unwrap();
interpreter.add("40 PRINT A").unwrap();
interpreter.add("50 PRINT B").unwrap();
interpreter.run().unwrap();
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 { .. }));
}
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();
}
#[test]
fn test_if() {
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 IF A = 1 GOTO 40").unwrap();
interpreter.add("40 PRINT A").unwrap();
interpreter.add("50 PRINT B").unwrap();
interpreter.run().unwrap();
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n");
}
#[test]
fn test_if_error() {
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();
assert_match!(interpreter.add("30 IF A GOTO 20"), Err(InterpreterError::SyntaxError { .. }));
}
#[test]
fn test_if_error_goto() {
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 IF A = 1 GOTO 20").unwrap();
assert_match!(interpreter.add("40 PRINT A GOTO 30"), Err(InterpreterError::SyntaxError { .. }));
}
fn main(){
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-h5zpu/solution) warning: unused macro definition: `assert_match` --> src/lib.rs:229:14 | 229 | macro_rules! assert_match { | ^^^^^^^^^^^^ | = note: `#[warn(unused_macros)]` on by default warning: value assigned to `current_line` is never read --> src/lib.rs:179:21 | 179 | current_line = &self.lines[line_index.unwrap()]; | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? = note: `#[warn(unused_assignments)]` on by default warning: unused variable: `goto_text` --> src/lib.rs:196:25 | 196 | let goto_text = parts.next(); | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_goto_text` | = note: `#[warn(unused_variables)]` on by default warning: value assigned to `current_line` is never read --> src/lib.rs:212:25 | 212 | current_line = &self.lines[line_index.unwrap() as usize]; //goto_line as usize - 1; | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `current_line` is never read --> src/lib.rs:214:25 | 214 | current_line = &self.lines[line_index.unwrap() as usize +1]; | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: variable does not need to be mutable --> src/lib.rs:111:17 | 111 | let mut command = &self.lines[idx as usize].1.0; | ----^^^^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default warning: variable does not need to be mutable --> src/lib.rs:112:17 | 112 | let mut line = &self.lines[idx as usize].1.1; | ----^^^^ | | | help: remove this `mut` warning: variable does not need to be mutable --> src/lib.rs:175:25 | 175 | let mut line_index = self | ----^^^^^^^^^^ | | | help: remove this `mut` warning: variable does not need to be mutable --> src/lib.rs:207:25 | 207 | let mut line_index = self | ----^^^^^^^^^^ | | | help: remove this `mut` warning: function `main` is never used --> src/lib.rs:332:4 | 332 | fn main(){ | ^^^^ | = note: `#[warn(dead_code)]` on by default warning: `solution` (lib) generated 10 warnings Finished test [unoptimized + debuginfo] target(s) in 1.47s 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_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 ... ok test solution_test::test_full_program ... FAILED test solution_test::test_io_error_write ... ok 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_syntax_errors_1 ... FAILED test solution_test::test_runtime_errors ... FAILED test solution_test::test_syntax_errors_2 ... ok failures: ---- solution_test::test_basic_goto stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"1\n2\n3\n"`, right: `"1\n3\n"`', tests/solution_test.rs:193:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'solution_test::test_basic_goto' panicked at 'assertion failed: `(left == right)` left: `"1\n2\n3\n"`, right: `"1\n3\n"`', tests/solution_test.rs:180:5 ---- solution_test::test_basic_if stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"0\n1\n"`, right: `"1\n"`', tests/solution_test.rs:224:9 thread 'solution_test::test_basic_if' panicked at 'assertion failed: `(left == right)` left: `"0\n1\n"`, right: `"1\n"`', tests/solution_test.rs:212:5 ---- solution_test::test_erroring_goto stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /tmp/d20230111-3772066-h5zpu/solution/src/lib.rs:179:59 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: NotANumber { value: "Guess" }', tests/solution_test.rs:245:31 thread 'solution_test::test_full_program' panicked at 'called `Result::unwrap()` on an `Err` value: NotANumber { value: "Guess" }', tests/solution_test.rs:230:5 ---- solution_test::test_line_order_and_overwriting stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"2\n1\n3\n4\n"`, 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: `"2\n1\n3\n4\n"`, right: `"1\n2\n4\n"`', tests/solution_test.rs:84:5 ---- solution_test::test_print_cyrillic stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"37\nЮнак\nевала\n"`, right: `"37\n999\nевала\n"`', tests/solution_test.rs:174:9 thread 'solution_test::test_print_cyrillic' panicked at 'assertion failed: `(left == right)` left: `"37\nЮнак\nевала\n"`, right: `"37\n999\nевала\n"`', tests/solution_test.rs:160:5 ---- solution_test::test_print_vars_and_strings stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"1\nAbc\nabc\n"`, right: `"1\n2\nabc\n"`', tests/solution_test.rs:154:9 thread 'solution_test::test_print_vars_and_strings' panicked at 'assertion failed: `(left == right)` left: `"1\nAbc\nabc\n"`, right: `"1\n2\nabc\n"`', tests/solution_test.rs:140: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:259: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_runtime_errors stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /tmp/d20230111-3772066-h5zpu/solution/src/lib.rs:179:59 thread 'solution_test::test_runtime_errors' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:301:5 failures: solution_test::test_basic_goto solution_test::test_basic_if solution_test::test_erroring_goto solution_test::test_full_program solution_test::test_line_order_and_overwriting solution_test::test_print_cyrillic solution_test::test_print_vars_and_strings solution_test::test_runtime_errors solution_test::test_syntax_errors_1 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`