Решение на 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`

История (1 версия и 0 коментара)

Ива качи първо решение на 10.01.2023 14:42 (преди над 2 години)