Решение на Basic BASIC от Никола Николов
Резултати
- 20 точки от тестове
- 0 бонус точки
- 20 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
use std::{
collections::HashMap,
io::{self, BufRead, BufReader, Read, Write},
};
#[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,
rows: HashMap<u16, String>,
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 {
Self {
input: BufReader::new(input),
output,
rows: HashMap::new(),
variables: HashMap::new(),
}
}
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 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> {
match code.split_once(char::is_whitespace) {
Some((first_word, rest)) => {
let parsed_first_word = first_word.parse::<u16>();
if let Ok(line_number) = parsed_first_word {
if self.is_command_valid(rest) {
self.rows.insert(line_number, rest.to_string());
Ok(())
} else {
Err(InterpreterError::SyntaxError {
code: code.to_string(),
})
}
} else {
Err(InterpreterError::SyntaxError {
code: code.to_string(),
})
}
}
None => Err(InterpreterError::SyntaxError {
code: code.to_string(),
}),
}
}
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().next().unwrap().is_uppercase() {
match self.variables.get(value) {
Some(var_value) => Ok(*var_value),
None => Err(InterpreterError::UnknownVariable {
name: value.to_string(),
}),
}
} else if value.parse::<u16>().is_ok() {
Ok(value.parse::<u16>().unwrap())
} else {
Err(InterpreterError::NotANumber {
value: value.to_string(),
})
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
/// Вижте по-долу за отделните команди и какви грешки могат да върнат.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut row_numbers: Vec<u16> = self.rows.keys().cloned().collect();
row_numbers.sort();
let mut current_row_number = row_numbers[0];
while current_row_number <= *row_numbers.last().unwrap() {
let current_row = self.rows.get(¤t_row_number).unwrap();
match current_row.split_once(char::is_whitespace) {
Some(("PRINT", args)) => {
if args.chars().next().unwrap().is_lowercase() {
writeln!(self.output, "{}", args)
.map_err(|e| InterpreterError::IoError(e))?
} else {
let value =
self.eval_value(args)
.map_err(|_e| InterpreterError::RuntimeError {
line_number: current_row_number,
message: "PRINT failed".to_string(),
})?;
writeln!(self.output, "{}", value)
.map_err(|e| InterpreterError::IoError(e))?
}
match self.get_next_row_number(&row_numbers, ¤t_row_number) {
Some(next_row_number) => current_row_number = next_row_number,
None => return Ok(()),
}
}
Some(("IF", args)) => {
let command_args: Vec<&str> = args.split_whitespace().collect();
let val1 = self.eval_value(&command_args[0]).map_err(|_e| {
InterpreterError::RuntimeError {
line_number: current_row_number,
message: "IF failed".to_string(),
}
})?;
let operator = command_args[1];
let val2 = self.eval_value(&command_args[2]).map_err(|_e| {
InterpreterError::RuntimeError {
line_number: current_row_number,
message: "IF failed".to_string(),
}
})?;
let is_condition_true;
match operator {
">" => {
is_condition_true = val1 > val2;
}
"<" => {
is_condition_true = val1 < val2;
}
"=" => {
is_condition_true = val1 == val2;
}
_ => {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: "IF failed".to_string(),
})
}
}
if is_condition_true {
let target_row = command_args[4].parse::<u16>().unwrap();
if !row_numbers.contains(&target_row) {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: "IF failed".to_string(),
});
}
current_row_number = target_row
} else {
match self.get_next_row_number(&row_numbers, ¤t_row_number) {
Some(next_row_number) => current_row_number = next_row_number,
None => return Ok(()),
}
}
}
Some(("GOTO", args)) => {
let target_row = args.parse::<u16>().unwrap();
if !row_numbers.contains(&target_row) {
return Err(InterpreterError::RuntimeError {
line_number: current_row_number,
message: "GOTO failed".to_string(),
});
}
current_row_number = target_row
}
Some(("READ", args)) => {
let mut value = String::new();
self.input
.read_line(&mut value)
.map_err(|e| InterpreterError::IoError(e))?;
let parsed_value = value.trim_end().parse::<u16>().map_err(|_e| {
InterpreterError::RuntimeError {
line_number: current_row_number,
message: "READ failed".to_string(),
}
})?;
self.variables.insert(args.to_string(), parsed_value);
match self.get_next_row_number(&row_numbers, ¤t_row_number) {
Some(next_row_number) => current_row_number = next_row_number,
None => return Ok(()),
}
}
_ => panic!(),
}
}
Ok(())
}
fn is_command_valid(&self, command: &str) -> bool {
match command.split_once(char::is_whitespace) {
Some((command_name, rest)) => match command_name {
"PRINT" => {
let args: Vec<&str> = rest.split_whitespace().collect();
args.len() == 1
}
"IF" => {
let args: Vec<&str> = rest.split_whitespace().collect();
args.len() == 5 && args[3] == "GOTO" && args[4].parse::<u16>().is_ok()
}
"GOTO" => {
let args: Vec<&str> = rest.split_whitespace().collect();
args.len() == 1 && args[0].parse::<u16>().is_ok()
}
"READ" => {
let args: Vec<&str> = rest.split_whitespace().collect();
args.len() == 1 && args[0].chars().next().unwrap().is_uppercase()
}
_ => false,
},
None => false,
}
}
fn get_next_row_number(&self, sorted_row_numbers: &Vec<u16>, current_row: &u16) -> Option<u16> {
let pos = sorted_row_numbers.iter().position(|&n| n == *current_row)?;
if pos == sorted_row_numbers.len() - 1 {
return None;
}
Some(sorted_row_numbers[pos + 1])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_returns_syntax_error_on_empty_string() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_missing_line_number() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("READ A");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "READ A")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_invalid_command() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("1 ADD");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 ADD")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_invalid_print_args() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("2 PRINT A B");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "2 PRINT A B")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("2 PRINT");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "2 PRINT")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_invalid_if_args() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("1 IF A > B GOTO");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 IF A > B GOTO")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("1 IF A > B G 15");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 IF A > B G 15")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("1 IF A > B GOTO C");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 IF A > B GOTO C")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_invalid_goto_args() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("1 GOTO");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 GOTO")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("1 GOTO -1");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 GOTO -1")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("1 GOTO B");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 GOTO B")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("1 GOTO 3 4");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "1 GOTO 3 4")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_returns_syntax_error_on_invalid_read_args() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
let result = interpreter.add("2 READ");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "2 READ")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("2 READ A B");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "2 READ A B")
}
_ => panic!("Expected syntax error"),
}
let result = interpreter.add("2 READ c");
assert!(result.is_err());
match result {
Err(InterpreterError::SyntaxError { code }) => {
assert_eq!(code, "2 READ c")
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_add_adds_successfully_a_print() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("2 PRINT A").is_ok());
assert!(interpreter.add("2 PRINT 5").is_ok());
}
#[test]
fn test_add_adds_successfully_an_if() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("2 IF A > B GOTO 10").is_ok());
assert!(interpreter.add("2 IF 5 > 3 GOTO 2").is_ok());
}
#[test]
fn test_add_adds_successfully_a_goto() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("2 GOTO 10").is_ok());
}
#[test]
fn test_add_adds_successfully_a_read() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("2 READ B").is_ok());
}
#[test]
fn test_run_successfully_executes_a_print() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("2 PRINT a").unwrap();
// TODO: Add test for a variable
let result = interpreter.run();
assert!(result.is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "a\n");
}
#[test]
fn test_run_returns_a_runtime_error_on_failed_print() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("2 PRINT Var").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "PRINT failed");
assert_eq!(line_number, 2);
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_run_successfully_executes_an_if() {
let input: &[u8] = b"10\n5\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("30 PRINT hello").unwrap();
interpreter.add("20 READ B").unwrap();
interpreter.add("10 READ A").unwrap();
interpreter.add("40 IF A > B GOTO 60").unwrap();
interpreter.add("50 PRINT hello_again").unwrap();
interpreter.add("60 PRINT end").unwrap();
let result = interpreter.run();
assert!(result.is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "hello\nend\n");
}
#[test]
fn test_run_returns_a_runtime_error_on_invalid_first_if_val() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF A > B GOTO 60").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "IF failed");
assert_eq!(line_number, 10);
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_run_returns_a_runtime_error_on_invalid_second_if_val() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF 10 > b GOTO 60").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "IF failed");
assert_eq!(line_number, 10);
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_run_returns_a_runtime_error_on_invalid_if_operator() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF 10 * 5 GOTO 10").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "IF failed");
assert_eq!(line_number, 10);
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_run_successfully_executes_a_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 30").unwrap();
interpreter.add("30 PRINT b").unwrap();
// TODO: Add test for a variable
let result = interpreter.run();
assert!(result.is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "a\nb\n");
}
#[test]
fn test_run_returns_a_runtime_error_on_failed_goto() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 GOTO 20").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "GOTO failed");
assert_eq!(line_number, 10);
}
_ => panic!("Expected syntax error"),
}
}
#[test]
fn test_run_successfully_executes_a_read() {
let input: &[u8] = b"5\n10\n15\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 READ Щ").unwrap();
interpreter.add("40 PRINT A").unwrap();
interpreter.add("50 PRINT B").unwrap();
interpreter.add("60 PRINT Щ").unwrap();
let result = interpreter.run();
assert!(result.is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "5\n10\n15\n");
}
#[test]
fn test_run_returns_a_runtime_error_on_failed_read() {
let input: &[u8] = b"asd\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("20 READ A").unwrap();
let result = interpreter.run();
assert!(result.is_err());
match result {
Err(InterpreterError::RuntimeError {
message,
line_number,
}) => {
assert_eq!(message, "READ failed");
assert_eq!(line_number, 20);
}
_ => panic!("Expected syntax error"),
}
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1l0y1a5/solution) Finished test [unoptimized + debuginfo] target(s) in 1.51s 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 ... ok 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_full_program ... ok test solution_test::test_io_error_read ... ok 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 ... ok test solution_test::test_syntax_errors_2 ... ok test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s