Решение на Basic BASIC от Даниел Георгиев

Обратно към всички решения

Към профила на Даниел Георгиев

Резултати

  • 20 точки от тестове
  • 0 бонус точки
  • 20 точки общо
  • 15 успешни тест(а)
  • 0 неуспешни тест(а)

Код

use std::io::{self, Write, Read, BufRead, BufReader};
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)]
enum ParsedCommandArgs {
Print { value: String },
Read { variable: String },
Goto { line_number: u16 },
If { left_value: String, operation: String, right_value: String, goto_line_number : u16 }
}
#[derive(Debug)]
struct Command {
line: u16,
parsed_command: ParsedCommandArgs
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
input: BufReader<R>,
output: &'a mut W,
tape_mappings : HashMap<u16, usize>,
tape: Vec<Command>,
defined_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,
tape_mappings : HashMap::new(),
tape : Vec::new(),
defined_variables : HashMap::new()
}
}
fn parse_print(command: &str, split_command: &[&str]) -> Result<ParsedCommandArgs, InterpreterError> {
if split_command.len() != 3 {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
Ok(ParsedCommandArgs::Print{ value : split_command[2].to_string() })
}
fn parse_goto(command: &str, split_command: &[&str]) -> Result<ParsedCommandArgs, InterpreterError> {
if split_command.len() != 3 {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
let line_num : u16 = match split_command[2].parse::<u16>() {
Ok(num) => num,
Err(_) => { return Err(InterpreterError::SyntaxError{ code : command.to_string() }); }
};
Ok(ParsedCommandArgs::Goto{ line_number : line_num })
}
fn parse_read(command: &str, split_command: &[&str]) -> Result<ParsedCommandArgs, InterpreterError> {
if split_command.len() != 3 {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
if !split_command[2].chars()
.next()
.unwrap_or('a')
.is_uppercase() {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
Ok(ParsedCommandArgs::Read{ variable : split_command[2].to_string() })
}
fn parse_if(command: &str, split_command: &[&str]) -> Result<ParsedCommandArgs, InterpreterError> {
if split_command.len() != 7 {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
if split_command[3] != "<" &&
split_command[3] != ">" &&
split_command[3] != "=" {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
if split_command[5] != "GOTO" {
return Err(InterpreterError::SyntaxError{code : command.to_string()});
}
let line_num : u16 = match split_command[6].parse::<u16>() {
Ok(num) => num,
Err(_) => { return Err(InterpreterError::SyntaxError{ code : command.to_string() }); }
};
Ok(ParsedCommandArgs::If{ left_value : split_command[2].to_string(),
operation : split_command[3].to_string(),
right_value : split_command[4].to_string(),
goto_line_number : line_num })
}
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 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 command_str = code.to_string();
let split_command : Vec<_> = code.split_whitespace()
.filter(|s| !s.is_empty())
.collect();
if split_command.len() < 3 {
return Err(InterpreterError::SyntaxError{ code : command_str });
}
let command_linecode : u16 = match split_command[0].parse::<u16>() {
Ok(num) => num,
Err(_) => { return Err(InterpreterError::SyntaxError{ code : command_str }); }
};
let parsed_command = if split_command[1] == "PRINT" {
Self::parse_print(&command_str, &split_command)?
} else if split_command[1] == "GOTO" {
Self::parse_goto(&command_str, &split_command)?
} else if split_command[1] == "READ" {
Self::parse_read(&command_str, &split_command)?
} else if split_command[1] == "IF" {
Self::parse_if(&command_str, &split_command)?
} else {
return Err(InterpreterError::SyntaxError{ code : command_str });
};
if self.tape_mappings.contains_key(&command_linecode) {
self.tape[self.tape_mappings[&command_linecode]] =
Command{ line : command_linecode,
parsed_command };
} else {
self.tape_mappings.insert(command_linecode, self.tape.len());
self.tape.push(Command{ line : command_linecode,
parsed_command });
}
Ok(())
}
/// Оценява `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_or('a').is_uppercase() {
if self.defined_variables.contains_key(value) {
return Ok(self.defined_variables[value]);
} else {
return Err(InterpreterError::UnknownVariable{name : value.to_string()});
}
}
match value.parse::<u16>() {
Ok(result) => Ok(result),
Err(_) => {return Err(InterpreterError::NotANumber{value : value.to_string()}); }
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
self.tape.sort_by(|command_a, command_b| command_a.line.cmp(&command_b.line));
self.tape_mappings.clear();
let mut idx = 0;
for command in self.tape.iter() {
self.tape_mappings.insert(command.line, idx);
idx += 1;
}
let mut cur = 0;
while cur < self.tape.len() {
let parsed_command = &self.tape[cur].parsed_command;
match parsed_command {
ParsedCommandArgs::Print{ ref value } => {
if value.chars().next().unwrap_or('A').is_lowercase() {
if let Err(io_err) = self.output.write_all(value.as_bytes()) {
return Err(InterpreterError::IoError(io_err));
}
if let Err(io_err) = self.output.write_all("\n".as_bytes()) {
return Err(InterpreterError::IoError(io_err));
}
} else {
match self.eval_value(value) {
Err(_) => {return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")});}
Ok(num) => {
if let Err(io_err) = self.output.write_all(num.to_string().as_bytes()) {
return Err(InterpreterError::IoError(io_err));
}
if let Err(io_err) = self.output.write_all("\n".as_bytes()) {
return Err(InterpreterError::IoError(io_err));
}
}
}
}
cur += 1;
},
ParsedCommandArgs::Read{ ref variable } => {
let mut line = String::new();
if let Err(io_err) = self.input.read_line(&mut line) {
return Err(InterpreterError::IoError(io_err));
}
let val_str = line.trim_end();
let val : u16 = match val_str.parse::<u16>() {
Ok(num) => num,
Err(_) => { return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")}); }
};
self.defined_variables.insert(variable.to_string(), val);
cur += 1;
},
ParsedCommandArgs::Goto { ref line_number } => {
if !self.tape_mappings.contains_key(line_number) {
return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")});
}
cur = self.tape_mappings[line_number];
},
ParsedCommandArgs::If { ref left_value, ref operation, ref right_value, ref goto_line_number} => {
let left_val_evaluated = match self.eval_value(left_value) {
Err(_) => {return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")});}
Ok(num) => num
};
let right_val_evaluated = match self.eval_value(right_value) {
Err(_) => {return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")});}
Ok(num) => num
};
let cmp_result : bool = if operation == "<" {
left_val_evaluated < right_val_evaluated
} else if operation == "=" {
left_val_evaluated == right_val_evaluated
} else {
left_val_evaluated > right_val_evaluated
};
if cmp_result {
if !self.tape_mappings.contains_key(goto_line_number) {
return Err(InterpreterError::RuntimeError{line_number : self.tape[cur].line, message : String::from("Runtime error!")});
}
cur = self.tape_mappings[goto_line_number];
} else {
cur += 1
}
}
}
}
Ok(())
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20230111-3772066-kwhzvg/solution)
    Finished test [unoptimized + debuginfo] target(s) in 1.54s
     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_print ... ok
test solution_test::test_basic_input ... ok
test solution_test::test_erroring_goto ... ok
test solution_test::test_basic_read ... ok
test solution_test::test_io_error_read ... ok
test solution_test::test_full_program ... ok
test solution_test::test_line_order_and_overwriting ... ok
test solution_test::test_io_error_write ... 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

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

Даниел качи първо решение на 10.01.2023 03:49 (преди над 2 години)