Решение на Basic BASIC от Николай Паев
Резултати
- 17 точки от тестове
- 0 бонус точки
- 17 точки общо
- 13 успешни тест(а)
- 2 неуспешни тест(а)
Код
use std::{
collections::BTreeMap,
collections::HashMap,
io::{self, BufRead, BufReader, Read, Write},
ops::Bound::Included,
ops::Bound::Unbounded,
str::SplitWhitespace,
};
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub enum Op {
Less,
Greater,
Eq,
}
pub enum Command {
Print(String),
Read(String),
Goto(u16),
If {
value1: String,
op: Op,
value2: String,
line: u16,
},
}
/// macro for short return on error
/// first arg is the operation which can result in None
/// second arg is the bad code line
/// third arg is error message
macro_rules! try_syntax {
($expr:expr, $code:expr, $msg:expr) => {
match $expr {
Some(value) => value,
None => {
return Err(InterpreterError::SyntaxError {
code: $code.to_string() + " - " + $msg,
})
}
}
};
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: &'a mut W,
vars: HashMap<String, u16>,
lines: BTreeMap<u16, Command>,
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter::<'a, R, W> {
input: BufReader::new(input),
output,
vars: HashMap::new(),
lines: BTreeMap::new(),
}
}
fn parse_print(
&mut self,
line_num: u16,
iter: &mut SplitWhitespace,
code: &str,
) -> Result<(), InterpreterError> {
let value = try_syntax!(iter.next(), code, "failed to read value");
self.lines
.insert(line_num, Command::Print(value.to_string()));
Ok(())
}
fn parse_read(
&mut self,
line_num: u16,
iter: &mut SplitWhitespace,
code: &str,
) -> Result<(), InterpreterError> {
let var = try_syntax!(iter.next(), code, "failed to read var name");
// because we tokenize by whitespace it is impossible to have zero length tokens
let first_letter = var.chars().next().unwrap();
if !first_letter.is_uppercase() {
return Err(InterpreterError::SyntaxError {
code: code.to_string() + " - var name does not start with uppercase",
});
}
self.lines.insert(line_num, Command::Read(var.to_string()));
Ok(())
}
fn parse_goto(
&mut self,
line_num: u16,
iter: &mut SplitWhitespace,
code: &str,
) -> Result<(), InterpreterError> {
let jump_num_str = try_syntax!(iter.next(), code, "failed to read line num");
let jump_num = try_syntax!(
jump_num_str.parse::<u16>().ok(),
code,
"failed to parse line num"
);
self.lines.insert(line_num, Command::Goto(jump_num));
Ok(())
}
fn parse_if(
&mut self,
line_num: u16,
iter: &mut SplitWhitespace,
code: &str,
) -> Result<(), InterpreterError> {
let value1 = try_syntax!(iter.next(), code, "failed to read value 1").to_string();
let op_str = try_syntax!(iter.next(), code, "failed to read operation");
let op = match op_str {
"<" => Op::Less,
"=" => Op::Eq,
">" => Op::Greater,
_ => {
return Err(InterpreterError::SyntaxError {
code: code.to_string() + " - unknown operator",
});
}
};
let value2 = try_syntax!(iter.next(), code, "failed to read value 2").to_string();
let goto_str = try_syntax!(iter.next(), code, "failed to read goto");
if goto_str != "GOTO" {
return Err(InterpreterError::SyntaxError {
code: code.to_string() + " - failed to read GOTO",
});
}
let jump_num_str = try_syntax!(iter.next(), code, "failed to read line num");
let jump_num = try_syntax!(
jump_num_str.parse::<u16>().ok(),
code,
"failed to parse line num"
);
self.lines.insert(
line_num,
Command::If {
value1,
op,
value2,
line: jump_num,
},
);
Ok(())
}
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let mut iter = code.split_whitespace();
let line_str = try_syntax!(iter.next(), code, "failed to read line num");
let line_num = try_syntax!(
line_str.parse::<u16>().ok(),
code,
"failed to parse line num"
);
let command = try_syntax!(iter.next(), code, "failed to read command");
match command {
"PRINT" => {
self.parse_print(line_num, &mut iter, code)?;
}
"READ" => {
self.parse_read(line_num, &mut iter, code)?;
}
"GOTO" => {
self.parse_goto(line_num, &mut iter, code)?;
}
"IF" => {
self.parse_if(line_num, &mut iter, code)?;
}
_ => {
return Err(InterpreterError::SyntaxError {
code: code.to_string() + " - unknown command",
});
}
}
Ok(())
}
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
// because we tokenize by whitespace it is impossible to have zero length tokens
let first_letter = value.chars().next().unwrap();
if first_letter.is_uppercase() {
self.vars
.get(value)
.map(|num| -> u16 { num.to_owned() })
.ok_or(InterpreterError::UnknownVariable {
name: value.to_string(),
})
} else {
value.parse::<u16>().map_err(|_| -> InterpreterError {
InterpreterError::NotANumber {
value: value.to_string(),
}
})
}
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut range = self.lines.range(std::ops::RangeFull);
while let Some((line_num, command)) = range.next() {
match command {
Command::Print(value) => match self.eval_value(&value) {
Ok(num) => writeln!(self.output, "{}", num)
.map_err(|err| -> InterpreterError { InterpreterError::IoError(err) })?,
Err(InterpreterError::NotANumber { value }) => {
writeln!(self.output, "{}", value)
.map_err(|err| -> InterpreterError { InterpreterError::IoError(err) })?
}
Err(err) => return Err(err),
},
Command::Read(var) => {
let mut line = String::new();
self.input
.read_line(&mut line)
.map_err(|err| -> InterpreterError { InterpreterError::IoError(err) })?;
let num = line
.pop()//last char is \n
.and_then(|_| -> Option<u16> { line.parse::<u16>().ok() })
.ok_or(InterpreterError::RuntimeError {
line_number: *line_num,
message: "Failed to read variable from line: ".to_string() + &line,
})?;
self.vars.insert(var.clone(), num);
}
Command::Goto(line) => {
if !self.lines.contains_key(line) {
let line_str = line.to_string();
return Err(InterpreterError::RuntimeError {
line_number: *line_num,
message: "No such line: ".to_string() + &line_str,
});
}
range = self.lines.range((Included(line), Unbounded));
}
Command::If {
value1,
op,
value2,
line,
} => {
let num1 = self.eval_value(&value1)?;
let num2 = self.eval_value(&value2)?;
let condition = match op {
Op::Eq => num1 == num2,
Op::Greater => num1 > num2,
Op::Less => num1 < num2,
};
if condition {
range = self.lines.range((Included(line), Unbounded));
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
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_example() {
let input: &[u8] = b"20\n50\n40\n45\n42\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT guess_a_number").unwrap();
interpreter.add("20 READ Guess").unwrap();
interpreter.add("30 IF Guess > 42 GOTO 100").unwrap();
interpreter.add("40 IF Guess < 42 GOTO 200").unwrap();
interpreter.add("50 IF Guess = 42 GOTO 300").unwrap();
interpreter.add("100 PRINT too_high").unwrap();
interpreter.add("110 GOTO 10").unwrap();
interpreter.add("200 PRINT too_low").unwrap();
interpreter.add("210 GOTO 10").unwrap();
interpreter.add("300 PRINT you_got_it!").unwrap();
assert_match!(interpreter.run(), Ok(()));
assert_eq!(
String::from_utf8(output).unwrap(),
"guess_a_number\ntoo_low\nguess_a_number\n".to_owned()
+ "too_high\nguess_a_number\ntoo_low\nguess_a_number\n"
+ "too_high\nguess_a_number\nyou_got_it!\n"
);
}
#[test]
fn test_print_bg() {
let input = "123\n".as_bytes();
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Щука").unwrap();
interpreter.add("20 PRINT Щука").unwrap();
interpreter.add("30 PRINT текст").unwrap();
interpreter.add("40 PRINT 1234").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("Щука").unwrap(), 123_u16);
assert_eq!(String::from_utf8(output).unwrap(), "123\nтекст\n1234\n");
}
#[test]
fn test_print_unknown_var() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT Променлива").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::UnknownVariable { .. })
);
}
#[test]
fn test_read_not_var() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 READ променлива"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_read_no_var() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 READ"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_read_no_input() {
let input: &[u8] = b"\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Променлива").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::RuntimeError {
line_number: 10,
..
})
);
}
#[test]
fn test_read_no_input_2() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Променлива").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::RuntimeError {
line_number: 10,
..
})
);
}
#[test]
fn test_read_false_input() {
let input: &[u8] = b"12num34";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Променлива").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::RuntimeError {
line_number: 10,
..
})
);
}
#[test]
fn test_goto() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT a").unwrap();
interpreter.add("20 GOTO 40").unwrap();
interpreter.add("30 PRINT no_print").unwrap();
interpreter.add("40 PRINT print").unwrap();
assert_match!(interpreter.run(), Ok(()));
assert_eq!(String::from_utf8(output).unwrap(), "a\nprint\n");
}
#[test]
fn test_goto_no_line() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 GOTO"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_goto_bad_num() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 GOTO 12num456"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_goto_no_such_line() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 GOTO 20").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::RuntimeError {
line_number: 10,
..
})
);
}
#[test]
fn test_if() {
let input: &[u8] = b"25\n15\n20\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 IF A < 20 GOTO 40").unwrap();
interpreter.add("30 IF A > 20 GOTO 10").unwrap();
interpreter.add("40 READ B").unwrap();
interpreter.add("50 IF B = 20 GOTO 70").unwrap();
interpreter.add("60 PRINT no").unwrap();
interpreter.add("70 PRINT yes").unwrap();
assert_match!(interpreter.run(), Ok(()));
assert_eq!(String::from_utf8(output).unwrap(), "yes\n");
}
#[test]
fn test_if_bad_syntax() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(
interpreter.add("10 IF GOTO"),
Err(InterpreterError::SyntaxError { .. })
);
assert_match!(
interpreter.add("10 IF GOTO 20"),
Err(InterpreterError::SyntaxError { .. })
);
assert_match!(
interpreter.add("10 IF 10 < GOTO 30"),
Err(InterpreterError::SyntaxError { .. })
);
assert_match!(
interpreter.add("10 IF < 20 GOTO 20"),
Err(InterpreterError::SyntaxError { .. })
);
assert_match!(
interpreter.add("10 IF 20 <= 20 GOTO 30"),
Err(InterpreterError::SyntaxError { .. })
);
assert_match!(
interpreter.add("10 IF 20 < 20 GOTO Var"),
Err(InterpreterError::SyntaxError { .. })
);
}
#[test]
fn test_if_no_var1() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF Променлива < 30 GOTO 20").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::UnknownVariable { .. })
);
}
#[test]
fn test_if_no_var2() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF 20 < Променлива GOTO 20").unwrap();
assert_match!(
interpreter.run(),
Err(InterpreterError::UnknownVariable { .. })
);
}
#[test]
fn test_if_not_a_var1() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF променлива < 20 GOTO 20").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::NotANumber { .. }));
}
#[test]
fn test_if_not_a_var2() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF 20 < променлива GOTO 20").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::NotANumber { .. }));
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1v4r9rs/solution) Finished test [unoptimized + debuginfo] target(s) in 1.60s Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01) running 15 tests test solution_test::test_basic_if ... ok test solution_test::test_basic_goto ... ok test solution_test::test_basic_input ... ok test solution_test::test_basic_print ... ok test solution_test::test_erroring_goto ... ok test solution_test::test_basic_read ... ok test solution_test::test_full_program ... ok test solution_test::test_io_error_write ... ok test solution_test::test_io_error_read ... 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_runtime_errors ... FAILED test solution_test::test_syntax_errors_1 ... ok test solution_test::test_syntax_errors_2 ... FAILED failures: ---- 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 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 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 ---- solution_test::test_syntax_errors_2 stdout ---- thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:280:9 thread 'solution_test::test_syntax_errors_2' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:275:5 failures: solution_test::test_runtime_errors solution_test::test_syntax_errors_2 test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--test solution_test`