Решение на Basic BASIC от Александър Македонски
Към профила на Александър Македонски
Резултати
- 17 точки от тестове
- 0 бонус точки
- 17 точки общо
- 13 успешни тест(а)
- 2 неуспешни тест(а)
Код
use std::io::{self, Write, Read, BufReader, BufRead};
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(Clone, Debug)]
// Енъм за всяка команда в интерпретатора
// Позволил съм си да направя IF да е с рекурсивна дефиниция, за да може да се направи имплементация на:
// Пример1: IF (true_statement) PRINT Thing
// Пример2: IF (true_statement) READ Thing
// Може дори да се направят вложени If-ове. Това ще станце доста лесно,
// но в случая ще проверявам дали командата е задължително GOTO, защото това се иска от мен
pub enum Action {
GoTo{line_number: u16},
Print{value: String},
Read{variable: String},
If{value1: String, value2: String, operation: String, action: Box<Action>},
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: &'a mut W,
lines: HashMap<u16, Action>,
max_line: u16,
variables: HashMap<String, u16>,
}
impl Action {
//Действие по подразбиране, което ще се хвърля на HashMap-а, за default стойоност на ред.
fn default() -> Action {
Action::GoTo{line_number: 0}
}
// превръщане на вектор от стрингове в действие.
// (подава се векторът, който се получава при сплитване на ред)
fn from(code_parts: &Vec<&str>) -> Result<Action, ()> {
match code_parts[1]
{
"PRINT" =>
{
if code_parts.len() != 3
{ return Err(()); }
Ok(Action::Print{value: code_parts[2].to_string()})
}
"READ" =>
{
if code_parts.len() != 3 || !code_parts[2].chars().nth(0).unwrap().is_uppercase()
{ return Err(()); }
Ok(Action::Read{variable: code_parts[2].to_string()})
}
"GOTO" =>
{
if code_parts.len() != 3 || code_parts[2].parse::<u16>().is_err()
{ return Err(()); }
Ok(Action::GoTo{line_number: code_parts[2].parse::<u16>().unwrap()})
}
"IF" =>
{
if code_parts.len() != 7 ||
(code_parts[3]!= "=" && code_parts[3] != ">" && code_parts[3] != "<") ||
(code_parts[2].parse::<u16>().is_err() && !code_parts[2].chars().nth(0).unwrap().is_uppercase()) ||
(code_parts[4].parse::<u16>().is_err() && !code_parts[4].chars().nth(0).unwrap().is_uppercase()) ||
code_parts[5] != "GOTO" ||
code_parts[6].parse::<u16>().is_err()
{
return Err(());
}
Ok(Action::If{
value1: code_parts[2].to_string(),
value2: code_parts[4].to_string(),
operation: code_parts[3].to_string(),
action: Box::new(Action::GoTo{line_number: code_parts[6].parse::<u16>().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 {
Interpreter{
input: BufReader::<R>::new(input),
output,
lines: HashMap::new(),
max_line: 0,
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> {
let code_parts : Vec<&str> = code.split_whitespace().collect();
if code_parts.len() < 2 || code_parts[0].parse::<u16>().is_err()
{ return Err(InterpreterError::SyntaxError{code: code.to_string()}); }
let line_number: u16 = code_parts[0].parse().unwrap();
let result = Action::from(&code_parts);
if result.is_err()
{ return Err(InterpreterError::SyntaxError{code: code.to_string()}); }
if line_number > self.max_line
{ self.max_line = line_number; }
*self.lines.entry(line_number).or_insert(Action::default()) = result.unwrap();
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().nth(0).unwrap().is_uppercase()
{
if self.variables.contains_key(value)
{return Ok(*self.variables.get(value).unwrap());}
else
{return Err(InterpreterError::UnknownVariable{name: value.to_string()});}
}
if value.parse::<u16>().is_ok()
{
return Ok(value.parse::<u16>().unwrap());
}
Err(InterpreterError::NotANumber{value: value.to_string()})
}
//Функцията оценява кода за изпълнение на съответния ред
fn eval_action(&mut self, line_number: &mut u16) -> Result<(), InterpreterError> {
let action = self.lines.get(line_number).unwrap();
match action{
Action::GoTo{line_number: go_to_line_number} =>
{
if !self.lines.contains_key(&go_to_line_number)
{ return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("GOTO wrong line")}); }
*line_number = go_to_line_number-1;
}
Action::Read{variable} =>
{
let mut line = String::new();
self.input.read_line(&mut line).unwrap();
line = line.trim_end().to_string();
if line.parse::<u16>().is_err()
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("Read gone wrong, cannot read non-numbers")}); }
let value = line.parse::<u16>().unwrap();
*self.variables.entry(variable.to_string()).or_insert(value) = value;
}
Action::Print{ value } =>
{
if value.chars().nth(0).unwrap().is_uppercase()
{
if !self.variables.contains_key(value)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("print gone wrong, cannot print non-existing")}); }
let var = self.variables.get(value).unwrap().to_string();
write!(&mut self.output, "{}", var).unwrap();
}
else //if(value.parse::<u16>().is_err());?
{
let var = value.clone();
write!(&mut self.output, "{}", var).unwrap();
}
self.output.write(b"\n").unwrap();
}
Action::If{ value1, value2, operation, action } =>
{
let val1: u16;
let val2: u16;
if value1.parse::<u16>().is_ok()
{
val1 = value1.parse::<u16>().unwrap();
}
else
{
if !self.variables.contains_key(value1)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("if gone wrong, cannot read non-existing ".to_owned() + value1)}); }
val1 = *self.variables.get(value1).unwrap();
}
if value2.parse::<u16>().is_ok()
{
val2 = value2.parse::<u16>().unwrap();
}
else
{
if !self.variables.contains_key(value2)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("if gone wrong, cannot read non-existing ".to_owned() + value2)}); }
val2 = *self.variables.get(value2).unwrap();
}
let mut result_of_if: bool = false;
if operation == ">"
{ result_of_if = val1 > val2;}
if operation == "="
{ result_of_if = val1 == val2;}
if operation == "<"
{ result_of_if = val1 < val2;}
if result_of_if
{
//Тук реално мога да направя рекурсивно извикване на функцията eval_action. Но трябва да валидирам,
//че е goto. Иначе може да се смени доста лесно кода и да може да се влагат оператии в If.
match **action {
Action::GoTo{line_number: go_to_line_number} =>
{
if !self.lines.contains_key(&go_to_line_number)
{ return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("GOTO wrong line")}); }
*line_number = go_to_line_number-1;
}
_ => return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("Should be a goto action")})
}
}
}
}
Ok(())
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut current_line_number: u16 = 0;
while current_line_number <= self.max_line
{
if self.lines.contains_key(¤t_line_number)
{
let result = self.eval_action(&mut current_line_number);
if result.is_err()
{ return result; }
}
current_line_number+=1;
}
self.output.flush().unwrap();
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_adding_read(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 READ A").is_err());
assert!(interpreter.add("20 READ 2").is_err());
assert!(interpreter.add("20 READ a").is_err());
assert!(interpreter.add("20 ReAD A").is_err());
assert!(interpreter.add("20 READ ").is_err());
assert!(interpreter.add("20 READ A B").is_err());
assert!(interpreter.add("READ A").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("10 READ A").is_ok()); //repeated adding;
assert!(interpreter.add("10 READ A ").is_ok());
assert!(interpreter.add("10 READ Addd ").is_ok());
}
#[test]
fn test_adding_print(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 PRINT A").is_err());
assert!(interpreter.add("20 PrINT A").is_err());
assert!(interpreter.add("20 PRINT").is_err());
assert!(interpreter.add("20 PRINT ").is_err());
assert!(interpreter.add("20 PRINT A B").is_err());
assert!(interpreter.add("PRINT A").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 PRINT A").is_ok());
assert!(interpreter.add("10 PRINT A").is_ok()); //repeated adding;
assert!(interpreter.add("10 PRINT A ").is_ok());
}
#[test]
fn test_adding_goto(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 GOTO 20").is_err());
assert!(interpreter.add("20 GOTO K").is_err());
assert!(interpreter.add("20 GOTO k").is_err());
assert!(interpreter.add("20 GOTO 20k").is_err());
assert!(interpreter.add("20 GOTO ").is_err());
assert!(interpreter.add("20 GOTO 1 1").is_err());
assert!(interpreter.add("GOTO 1").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 GOTO 10").is_ok());
assert!(interpreter.add("10 GOTO 2").is_ok()); //repeated adding;
assert!(interpreter.add("10 GOTO 2 ").is_ok());
assert!(interpreter.add("10 GOTO 2020 ").is_ok());
}
#[test]
fn test_adding_if(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 IF 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 = 10 GOTO 2n").is_err());
assert!(interpreter.add("10 IF 20 < 1n GOTO 20").is_err());
assert!(interpreter.add("10 IF n0 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 < 10 GOTO 20 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOtO 20").is_err());
assert!(interpreter.add("10 IF > 10 GOTO 20").is_err());
assert!(interpreter.add("10 If 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOTO").is_err());
assert!(interpreter.add("IF 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF value < 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 = 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 < 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF Value < 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 < Value GOTO 20").is_ok());
assert!(interpreter.add("10 IF Value < Value GOTO 20").is_ok());
}
#[test]
fn test_running_empty(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.run().is_ok());
assert!(interpreter.eval_value("A").is_err());
assert_eq!(String::from_utf8(output).unwrap(), "");
}
#[test]
fn test_evaluation_actual(){
let input: &[u8] = b"1\n2\n3\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.eval_value("A").is_err());
assert!(interpreter.eval_value("B").is_err());
assert!(interpreter.eval_value("B").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("20 READ B").is_ok());
assert!(interpreter.add("30 READ C").is_ok());
assert!(interpreter.add("70 PRINT A").is_ok());
assert!(interpreter.add("80 PRINT B").is_ok());
assert!(interpreter.add("90 PRINT C").is_ok());
println!("{:?}", interpreter.run());
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(interpreter.eval_value("B").unwrap(), 2_u16);
assert_eq!(interpreter.eval_value("C").unwrap(), 3_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}
#[test]
fn test_running_actual(){
let input: &[u8] = b"1\n2\n3\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.eval_value("A").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("20 READ B").is_ok());
assert!(interpreter.add("30 READ C").is_ok());
assert!(interpreter.add("40 IF A > B GOTO 70").is_ok());
assert!(interpreter.add("50 IF A = B GOTO 80").is_ok());
assert!(interpreter.add("60 IF C > B GOTO 80").is_ok());
assert!(interpreter.add("70 PRINT A").is_ok());
assert!(interpreter.add("80 PRINT B").is_ok());
assert!(interpreter.add("90 PRINT C").is_ok());
println!("{:?}", interpreter.run());
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(interpreter.eval_value("B").unwrap(), 2_u16);
assert_eq!(interpreter.eval_value("C").unwrap(), 3_u16);
assert_eq!(String::from_utf8(output).unwrap(), "2\n3\n");
}
#[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 { .. }));
}
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_running_actual_in_bulgarian(){
let input: &[u8] = b"1\n2\n3\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.eval_value("Щ").is_err());
assert!(interpreter.add("10 READ Ъ").is_ok());
assert!(interpreter.add("20 READ Й").is_ok());
assert!(interpreter.add("30 READ Щ").is_ok());
assert!(interpreter.add("40 IF Ъ > Й GOTO 70").is_ok());
assert!(interpreter.add("50 IF Ъ = Й GOTO 80").is_ok());
assert!(interpreter.add("70 PRINT Ъ").is_ok());
assert!(interpreter.add("80 PRINT Й").is_ok());
assert!(interpreter.add("90 PRINT Щ").is_ok());
println!("{:?}", interpreter.run());
assert_eq!(interpreter.eval_value("Ъ").unwrap(), 1_u16);
assert_eq!(interpreter.eval_value("Й").unwrap(), 2_u16);
assert_eq!(interpreter.eval_value("Щ").unwrap(), 3_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-c4aut0/solution) warning: unused macro definition: `assert_match` --> src/lib.rs:323:14 | 323 | macro_rules! assert_match { | ^^^^^^^^^^^^ | = note: `#[warn(unused_macros)]` on by default warning: `solution` (lib) generated 1 warning Finished test [unoptimized + debuginfo] target(s) in 1.49s 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 ... FAILED test solution_test::test_full_program ... ok test solution_test::test_io_error_write ... FAILED 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 failures: ---- solution_test::test_io_error_read stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "read error!" }', /tmp/d20230111-3772066-c4aut0/solution/src/lib.rs:204:49 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'solution_test::test_io_error_read' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "read error!" }', tests/solution_test.rs:344:5 ---- solution_test::test_io_error_write stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "Write Error!" }', /tmp/d20230111-3772066-c4aut0/solution/src/lib.rs:225:57 thread 'solution_test::test_io_error_write' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "Write Error!" }', tests/solution_test.rs:358:5 failures: solution_test::test_io_error_read solution_test::test_io_error_write 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`
История (2 версии и 0 коментара)
Александър качи решение на 09.01.2023 14:52 (преди над 2 години)
use std::io::{self, Write, Read, BufReader, BufRead};
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(Clone, Debug)]
// Енъм за всяка команда в интерпретатора
// Позволил съм си да направя IF да е с рекурсивна дефиниция, за да може да се направи имплементация на:
// Пример1: IF (true_statement) PRINT Thing
// Пример2: IF (true_statement) READ Thing
// Може дори да се направят вложени If-ове. Това ще станце доста лесно,
// но в случая ще проверявам дали командата е задължително GOTO, защото това се иска от мен
pub enum Action {
GoTo{line_number: u16},
Print{value: String},
Read{variable: String},
If{value1: String, value2: String, operation: String, action: Box<Action>},
}
// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<'a, R: Read, W: Write> {
input: BufReader<R>,
output: &'a mut W,
lines: HashMap<u16, Action>,
max_line: u16,
variables: HashMap<String, u16>,
}
impl Action {
//Действие по подразбиране, което ще се хвърля на HashMap-а, за default стойоност на ред.
fn default() -> Action {
Action::GoTo{line_number: 0}
}
// превръщане на вектор от стрингове в действие.
// (подава се векторът, който се получава при сплитване на ред)
fn from(code_parts: &Vec<&str>) -> Result<Action, ()> {
match code_parts[1]
{
"PRINT" =>
{
if code_parts.len() != 3
{ return Err(()); }
Ok(Action::Print{value: code_parts[2].to_string()})
}
"READ" =>
{
if code_parts.len() != 3 || !code_parts[2].chars().nth(0).unwrap().is_uppercase()
{ return Err(()); }
Ok(Action::Read{variable: code_parts[2].to_string()})
}
"GOTO" =>
{
if code_parts.len() != 3 || code_parts[2].parse::<u16>().is_err()
{ return Err(()); }
Ok(Action::GoTo{line_number: code_parts[2].parse::<u16>().unwrap()})
}
"IF" =>
{
if code_parts.len() != 7 ||
(code_parts[3]!= "=" && code_parts[3] != ">" && code_parts[3] != "<") ||
(code_parts[2].parse::<u16>().is_err() && !code_parts[2].chars().nth(0).unwrap().is_uppercase()) ||
(code_parts[4].parse::<u16>().is_err() && !code_parts[4].chars().nth(0).unwrap().is_uppercase()) ||
code_parts[5] != "GOTO" ||
code_parts[6].parse::<u16>().is_err()
{
return Err(());
}
Ok(Action::If{
value1: code_parts[2].to_string(),
value2: code_parts[4].to_string(),
operation: code_parts[3].to_string(),
action: Box::new(Action::GoTo{line_number: code_parts[6].parse::<u16>().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 {
Interpreter{
input: BufReader::<R>::new(input),
output,
lines: HashMap::new(),
max_line: 0,
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> {
let code_parts : Vec<&str> = code.split_whitespace().collect();
if code_parts.len() < 2 || code_parts[0].parse::<u16>().is_err()
{ return Err(InterpreterError::SyntaxError{code: code.to_string()}); }
let line_number: u16 = code_parts[0].parse().unwrap();
let result = Action::from(&code_parts);
if result.is_err()
{ return Err(InterpreterError::SyntaxError{code: code.to_string()}); }
if line_number > self.max_line
{ self.max_line = line_number; }
*self.lines.entry(line_number).or_insert(Action::default()) = result.unwrap();
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().nth(0).unwrap().is_uppercase()
{
if self.variables.contains_key(value)
{return Ok(*self.variables.get(value).unwrap());}
else
{return Err(InterpreterError::UnknownVariable{name: value.to_string()});}
}
if value.parse::<u16>().is_ok()
{
return Ok(value.parse::<u16>().unwrap());
}
Err(InterpreterError::NotANumber{value: value.to_string()})
}
//Функцията оценява кода за изпълнение на съответния ред
fn eval_action(&mut self, line_number: &mut u16) -> Result<(), InterpreterError> {
let action = self.lines.get(line_number).unwrap();
match action{
Action::GoTo{line_number: go_to_line_number} =>
{
if !self.lines.contains_key(&go_to_line_number)
{ return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("GOTO wrong line")}); }
*line_number = go_to_line_number-1;
}
Action::Read{variable} =>
{
let mut line = String::new();
self.input.read_line(&mut line).unwrap();
line = line.trim_end().to_string();
if line.parse::<u16>().is_err()
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("Read gone wrong, cannot read non-numbers")}); }
let value = line.parse::<u16>().unwrap();
*self.variables.entry(variable.to_string()).or_insert(value) = value;
}
Action::Print{ value } =>
{
if value.chars().nth(0).unwrap().is_uppercase()
{
if !self.variables.contains_key(value)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("print gone wrong, cannot print non-existing")}); }
let var = self.variables.get(value).unwrap().to_string();
write!(&mut self.output, "{}", var).unwrap();
}
else //if(value.parse::<u16>().is_err());?
{
let var = value.clone();
write!(&mut self.output, "{}", var).unwrap();
}
self.output.write(b"\n").unwrap();
}
Action::If{ value1, value2, operation, action } =>
{
let val1: u16;
let val2: u16;
if value1.parse::<u16>().is_ok()
{
val1 = value1.parse::<u16>().unwrap();
}
else
{
if !self.variables.contains_key(value1)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("if gone wrong, cannot read non-existing ".to_owned() + value1)}); }
val1 = *self.variables.get(value1).unwrap();
}
if value2.parse::<u16>().is_ok()
{
val2 = value2.parse::<u16>().unwrap();
}
else
{
if !self.variables.contains_key(value2)
{return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("if gone wrong, cannot read non-existing ".to_owned() + value2)}); }
val2 = *self.variables.get(value2).unwrap();
}
let mut result_of_if: bool = false;
if operation == ">"
{ result_of_if = val1 > val2;}
if operation == "="
{ result_of_if = val1 == val2;}
if operation == "<"
{ result_of_if = val1 < val2;}
if result_of_if
{
//Тук реално мога да направя рекурсивно извикване на функцията eval_action. Но трябва да валидирам,
//че е goto. Иначе може да се смени доста лесно кода и да може да се влагат оператии в If.
match **action {
Action::GoTo{line_number: go_to_line_number} =>
{
if !self.lines.contains_key(&go_to_line_number)
{ return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("GOTO wrong line")}); }
*line_number = go_to_line_number-1;
}
_ => return Err(InterpreterError::RuntimeError{
line_number: *line_number,
message: String::from("Should be a goto action")})
}
}
}
}
Ok(())
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
///
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut current_line_number: u16 = 0;
while current_line_number <= self.max_line
{
if self.lines.contains_key(¤t_line_number)
{
let result = self.eval_action(&mut current_line_number);
if result.is_err()
{ return result; }
}
current_line_number+=1;
}
self.output.flush().unwrap();
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_adding_read(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 READ A").is_err());
assert!(interpreter.add("20 READ 2").is_err());
assert!(interpreter.add("20 READ a").is_err());
assert!(interpreter.add("20 ReAD A").is_err());
assert!(interpreter.add("20 READ ").is_err());
assert!(interpreter.add("20 READ A B").is_err());
assert!(interpreter.add("READ A").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("10 READ A").is_ok()); //repeated adding;
assert!(interpreter.add("10 READ A ").is_ok());
assert!(interpreter.add("10 READ Addd ").is_ok());
}
#[test]
fn test_adding_print(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 PRINT A").is_err());
assert!(interpreter.add("20 PrINT A").is_err());
assert!(interpreter.add("20 PRINT").is_err());
assert!(interpreter.add("20 PRINT ").is_err());
assert!(interpreter.add("20 PRINT A B").is_err());
assert!(interpreter.add("PRINT A").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 PRINT A").is_ok());
assert!(interpreter.add("10 PRINT A").is_ok()); //repeated adding;
assert!(interpreter.add("10 PRINT A ").is_ok());
}
#[test]
fn test_adding_goto(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 GOTO 20").is_err());
assert!(interpreter.add("20 GOTO K").is_err());
assert!(interpreter.add("20 GOTO k").is_err());
assert!(interpreter.add("20 GOTO 20k").is_err());
assert!(interpreter.add("20 GOTO ").is_err());
assert!(interpreter.add("20 GOTO 1 1").is_err());
assert!(interpreter.add("GOTO 1").is_err());
assert!(interpreter.add("20").is_err());
assert!(interpreter.add("10 GOTO 10").is_ok());
assert!(interpreter.add("10 GOTO 2").is_ok()); //repeated adding;
assert!(interpreter.add("10 GOTO 2 ").is_ok());
assert!(interpreter.add("10 GOTO 2020 ").is_ok());
}
#[test]
fn test_adding_if(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.add("n0 IF 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 = 10 GOTO 2n").is_err());
assert!(interpreter.add("10 IF 20 < 1n GOTO 20").is_err());
assert!(interpreter.add("10 IF n0 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 < 10 GOTO 20 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOtO 20").is_err());
assert!(interpreter.add("10 IF > 10 GOTO 20").is_err());
assert!(interpreter.add("10 If 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOTO").is_err());
assert!(interpreter.add("IF 20 > 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF value < 10 GOTO 20").is_err());
assert!(interpreter.add("10 IF 20 > 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 = 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 < 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF Value < 10 GOTO 20").is_ok());
assert!(interpreter.add("10 IF 20 < Value GOTO 20").is_ok());
assert!(interpreter.add("10 IF Value < Value GOTO 20").is_ok());
}
#[test]
fn test_running_empty(){
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.run().is_ok());
assert!(interpreter.eval_value("A").is_err());
assert_eq!(String::from_utf8(output).unwrap(), "");
}
#[test]
fn test_evaluation_actual(){
let input: &[u8] = b"1\n2\n3\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.eval_value("A").is_err());
assert!(interpreter.eval_value("B").is_err());
assert!(interpreter.eval_value("B").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("20 READ B").is_ok());
assert!(interpreter.add("30 READ C").is_ok());
assert!(interpreter.add("70 PRINT A").is_ok());
assert!(interpreter.add("80 PRINT B").is_ok());
assert!(interpreter.add("90 PRINT C").is_ok());
println!("{:?}", interpreter.run());
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(interpreter.eval_value("B").unwrap(), 2_u16);
assert_eq!(interpreter.eval_value("C").unwrap(), 3_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}
#[test]
fn test_running_actual(){
let input: &[u8] = b"1\n2\n3\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert!(interpreter.eval_value("A").is_err());
assert!(interpreter.add("10 READ A").is_ok());
assert!(interpreter.add("20 READ B").is_ok());
assert!(interpreter.add("30 READ C").is_ok());
assert!(interpreter.add("40 IF A > B GOTO 70").is_ok());
assert!(interpreter.add("50 IF A = B GOTO 80").is_ok());
assert!(interpreter.add("60 IF C > B GOTO 80").is_ok());
assert!(interpreter.add("70 PRINT A").is_ok());
assert!(interpreter.add("80 PRINT B").is_ok());
assert!(interpreter.add("90 PRINT C").is_ok());
println!("{:?}", interpreter.run());
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(interpreter.eval_value("B").unwrap(), 2_u16);
assert_eq!(interpreter.eval_value("C").unwrap(), 3_u16);
assert_eq!(String::from_utf8(output).unwrap(), "2\n3\n");
}
#[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 { .. }));
}
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_running_actual_in_bulgarian(){
+ let input: &[u8] = b"1\n2\n3\n";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ assert!(interpreter.eval_value("Щ").is_err());
+
+ assert!(interpreter.add("10 READ Ъ").is_ok());
+ assert!(interpreter.add("20 READ Й").is_ok());
+ assert!(interpreter.add("30 READ Щ").is_ok());
+
+ assert!(interpreter.add("40 IF Ъ > Й GOTO 70").is_ok());
+ assert!(interpreter.add("50 IF Ъ = Й GOTO 80").is_ok());
+
+ assert!(interpreter.add("70 PRINT Ъ").is_ok());
+ assert!(interpreter.add("80 PRINT Й").is_ok());
+ assert!(interpreter.add("90 PRINT Щ").is_ok());
+
+ println!("{:?}", interpreter.run());
+
+ assert_eq!(interpreter.eval_value("Ъ").unwrap(), 1_u16);
+ assert_eq!(interpreter.eval_value("Й").unwrap(), 2_u16);
+ assert_eq!(interpreter.eval_value("Щ").unwrap(), 3_u16);
+
+ assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}