Решение на Basic BASIC от Давид Петров
Резултати
- 20 точки от тестове
- 0 бонус точки
- 20 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
use std::io::{self, Read, BufReader, Write, BufRead};
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::ops::Bound::*;
type LineNumber = u16; // semantically: the type to index the program lines with
type NumberType = u16; // semantically: the type of the end "values" our program produces
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: LineNumber, message: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Operator {
GT,
EQ,
LT,
}
impl Operator {
fn apply(&self, left: &NumberType, right: &NumberType) -> bool {
match self {
Operator::GT => left > right,
Operator::EQ => left == right,
Operator::LT => left < right,
}
}
fn from_str(word: &str) -> Option<Operator> {
match word {
">" => Some(Operator::GT),
"=" => Some(Operator::EQ),
"<" => Some(Operator::LT),
_ => None,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum Command {
Print(String),
Read(String),
Goto(LineNumber),
If { left: Value, operator: Operator, right: Value, line_number: LineNumber},
}
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
reader: BufReader<R>,
writer: &'a mut W,
// Каквито други полета ви трябват
lines: BTreeMap<LineNumber, Command>,
env: HashMap<String, NumberType>,
}
fn is_variable_name(word: &str) -> bool {
word.chars().next().map_or(false, |c| c.is_uppercase())
}
/// This type is used as a proof that something is a valid "Value" in our language
/// With a "Value" being either a literal or a syntactically valid variable name
#[derive(Debug, Clone, PartialEq, Eq)]
enum Value {
Literal(NumberType),
Variable(String),
}
impl Value {
fn from_str(word: &str) -> Option<Value> {
if is_variable_name(word) {
Some(Value::Variable(word.to_string()))
} else if let Ok(num) = word.parse::<NumberType>() {
Some(Value::Literal(num))
} else {
None
}
}
}
fn parse(line: &String) -> Result<(LineNumber, Command), InterpreterError> {
let syntax_err = || InterpreterError::SyntaxError { code: line.clone() };
let parse_line_number =
|word: &str| word.parse::<LineNumber>().map_err(|_| syntax_err());
let words: Vec<&str> = line.split_whitespace().collect();
let command_line_number = match words.first() {
Some(&arg) => parse_line_number(arg)?,
None => return Err(syntax_err()),
};
let command = match words[1..] {
["READ", var_name]
if is_variable_name(var_name) => {
Command::Read(var_name.to_string())
},
["GOTO", line_number_arg] => {
Command::Goto(parse_line_number(line_number_arg)?)
},
["PRINT", arg] => {
Command::Print(arg.to_string())
},
["IF", left_arg, operator_arg, right_arg, "GOTO", line_number_arg] => {
let operator = Operator::from_str(operator_arg).ok_or(syntax_err())?;
let line_number = parse_line_number(line_number_arg)?;
let left = Value::from_str(left_arg).ok_or(syntax_err())?;
let right = Value::from_str(right_arg).ok_or(syntax_err())?;
Command::If { left, operator, right, line_number }
},
_ => return Err(syntax_err())
};
Ok((command_line_number, command))
}
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 {
reader: BufReader::new(input),
writer: output,
lines: BTreeMap::new(),
env: 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 (line_number, command) = parse(&code.to_string())?;
self.lines.insert(line_number, command);
Ok(())
}
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<NumberType, InterpreterError> {
match Value::from_str(value) {
Some(val) => match self.eval(&val) {
Some(num) => Ok(num),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
},
None => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
fn eval(&self, value: &Value) -> Option<NumberType> {
match value {
Value::Literal(num) => Some(*num),
Value::Variable(var_name) => self.env.get(var_name).cloned(),
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
pub fn run(&mut self) -> Result<(), InterpreterError> {
match self.lines.iter().next() {
Some((&line_number, _)) => self.run_from(line_number),
None => Ok(()),
}
}
fn run_from(&mut self, start_line_number: LineNumber) -> Result<(), InterpreterError> {
let lines = self.lines.clone();
let mut iter = lines.range((Included(start_line_number), Unbounded));
while let Some((&line_number, command)) = iter.next() {
//the result of execute_command is basically a flag if we should
//continue to iterate OR in the case of 'goto' we've started
//another recursive branch and want to terminate current iteration
let should_continue = self.execute_command(line_number, command)?;
if !should_continue {
break;
}
}
Ok(())
}
fn execute_command(&mut self, line_number: LineNumber, command: &Command) -> Result<bool, InterpreterError> {
let to_runtime_err =
|message: &str| InterpreterError::RuntimeError { line_number, message: message.to_string()};
let unknown_variable_runtime_error =
|| to_runtime_err("Unknown variable encountered.");
let no_line_with_such_number_runtime_error =
|| to_runtime_err("A line with the given number doesn't exist in the program code.");
match command {
Command::Print(arg) => {
let value = match self.eval_value(&*arg) {
Err(InterpreterError::UnknownVariable { .. }) => return Err(unknown_variable_runtime_error()),
Ok(num) => num.to_string(),
_ => arg.clone(),
};
self.print(value.as_str()).map(|_| true)
},
Command::Read(var_name) => {
let num =
self.read()?
.parse::<LineNumber>()
.map_err(|_| to_runtime_err("The provided input is not a valid u16 number."))?;
self.env.insert(var_name.clone(), num);
Ok(true)
},
Command::Goto(next_line_number) => {
if !self.lines.contains_key(next_line_number) {
Err(no_line_with_such_number_runtime_error())
} else {
self.run_from(*next_line_number).map(|_| false)
}
},
Command::If { left, operator, right, line_number: next_line_number } => {
let l = self.eval(left).ok_or(unknown_variable_runtime_error())?;
let r = self.eval(right).ok_or(unknown_variable_runtime_error())?;
if operator.apply(&l, &r) {
self.execute_command(line_number, &Command::Goto(*next_line_number)).map(|_| false)
} else {
Ok(true)
}
},
}
}
fn print(&mut self, value: &str) -> Result<(), InterpreterError> {
let writer = &mut self.writer;
writer
.write_all(value.as_bytes())
.and_then(|_| writer.write_all(b"\n"))
.and_then(|_| writer.flush())
.map_err(InterpreterError::IoError)
}
fn read(&mut self) -> Result<String, InterpreterError> {
let mut value = String::new();
self.reader
.read_line(&mut value)
.map_err(InterpreterError::IoError)?;
trim_new_line(&mut value);
Ok(value)
}
}
fn trim_new_line(word: &mut String) -> () {
if word.ends_with('\n') {
word.pop();
if word.ends_with('\r') {
word.pop();
}
}
}
#[cfg(test)]
mod test {
// За тестване че някакъв резултат пасва на някакъв 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)
);
}
};
}
struct NotBuffered {}
impl std::io::Read for NotBuffered {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Ok(0)
}
}
#[cfg(test)]
mod basic {
use crate::*;
use super::*;
#[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_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();
}
}
#[cfg(test)]
mod parse {
use crate::*;
#[test]
fn valid_commands() {
let cmm_expected: Vec<(&str, (LineNumber, Command))> = vec![
("10 PRINT alo_da", (10, Command::Print("alo_da".to_string()))),
("10 READ Var1", (10, Command::Read("Var1".to_string()))),
("10 READ ÜblicheVariable", (10, Command::Read("ÜblicheVariable".to_string()))),
("10 READ Щастие", (10, Command::Read("Щастие".to_string()))),
("10 GOTO 15", (10, Command::Goto(15))),
("10 IF 2 > 1 GOTO 15", (10, Command::If { left: Value::Literal(2), operator: Operator::GT, right: Value::Literal(1), line_number: 15 })),
("10 IF Var2 = K GOTO 15", (10, Command::If { left: Value::Variable("Var2".to_string()), operator: Operator::EQ, right: Value::Variable("K".to_string()), line_number: 15 })),
];
for (line, expected) in cmm_expected {
let res = parse(&line.to_string());
assert!(res.is_ok());
let line_cmm = res.unwrap();
assert_eq!(expected, line_cmm);
}
}
#[test]
fn invalid_commands() {
let invalid_commands = vec![
"10 PRINT A B", //to many args
"20 IF 13 > 18 GOTO", //missing arg for GOTO
"20 IF invalid > 18 GOTO 20", //invalid variable name
"20 IF 20 > 18 GOTO Nqkude", //invalid line number as arg to goto
"20 IF 20 <$> 18 GOTO 50", //invalid operator
"READ X", //missing command line number
"10 READ lowercase", //lowercase variable name -> invalid
"10 READ üblich", // invalid lowercase unicode var name
"10 READ 70", //invalid literal as var name
"30", //missing command
"", //empty line is invalid
];
for line in invalid_commands {
assert_match!(parse(&line.to_string()), Err(InterpreterError::SyntaxError { .. }));
}
}
}
#[cfg(test)]
mod eval {
use crate::*;
#[test]
fn valid_variables() {
let input: &[u8] = b"1\n2\n3\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 Ö").unwrap();
interpreter.add("25 READ Ю").unwrap();
interpreter.add("30 PRINT A").unwrap();
interpreter.add("40 PRINT Ö").unwrap();
interpreter.add("50 PRINT Ю").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1);
assert_eq!(interpreter.eval_value("Ö").unwrap(), 2);
assert_eq!(interpreter.eval_value("Ю").unwrap(), 3);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}
#[test]
fn reassignment_of_variable() {
let input: &[u8] = b"1\n2\n3";let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 READ A").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 2);
}
#[test]
fn test_eval_cases() {
let input: &[u8] = b"1\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.run().unwrap();
assert_match!(interpreter.eval_value("42"), Ok(42));
assert_match!(interpreter.eval_value("A"), Ok(1));
assert_match!(interpreter.eval_value("B"), Err(InterpreterError::UnknownVariable { .. }));
assert_match!(interpreter.eval_value("ö"), Err(InterpreterError::NotANumber { .. }));
assert_match!(interpreter.eval_value("ц"), Err(InterpreterError::NotANumber { .. }));
assert_match!(interpreter.eval_value("invaid"), Err(InterpreterError::NotANumber { .. }));
}
}
#[cfg(test)]
mod command_logic {
use crate::*;
use super::*;
#[test]
fn print() {
let input: &[u8] = b"123\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 PRINT A").unwrap();
interpreter.add("30 PRINT literal_word").unwrap();
interpreter.add("40 PRINT B").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 40, message: _ }));
assert_eq!(String::from_utf8(output).unwrap(), "123\nliteral_word\n");
}
#[test]
fn goto_invalid_line_error_correctly_produced_on_same_line() {
let input = NotBuffered {};
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, message: _ }));
}
}
#[cfg(test)]
mod end_to_end {
use crate::*;
use super::*;
#[test]
fn run_no_code_ok() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.run(), Ok(()));
}
#[test]
fn run_with_error() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT line_10").unwrap();
interpreter.add("20 IF 2 > Unknown GOTO 10").unwrap();
let result = interpreter.run();
assert_match!(result, Err(InterpreterError::RuntimeError { line_number: 20, message: _ }));
}
#[test]
fn if_with_io() {
// Забележете `b""` -- низ от байтове
let input: &[u8] = b"1\n2\n3\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.add("50 IF B < 3 GOTO 20").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n1\n3\n");
}
#[test]
fn simple_game() {
let lines = vec![
"10 READ Guess",
"30 IF Guess > 42 GOTO 100",
"40 IF Guess < 42 GOTO 200",
"50 IF Guess = 42 GOTO 300",
"100 PRINT h",
"110 GOTO 10",
"200 PRINT l",
"210 GOTO 10",
"300 PRINT yes!",
];
let input: &[u8] = b"1\n100\n50\n40\n42\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
for line in lines {
interpreter.add(line).unwrap();
}
assert!(interpreter.run().is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "l\nh\nh\nl\nyes!\n");
}
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-1iek227/solution) Finished test [unoptimized + debuginfo] target(s) in 1.66s 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_print ... ok test solution_test::test_basic_input ... 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_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_runtime_errors ... ok test solution_test::test_syntax_errors_1 ... 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
История (4 версии и 3 коментара)
Давид качи решение на 07.01.2023 05:18 (преди над 2 години)
use std::io::{self, Read, BufReader, Write, BufRead};
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::ops::Bound::*;
type LineNumber = u16; // semantically: the type to index the program lines with
type NumberType = u16; // semantically: the type of the end "values" our program produces
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: LineNumber, message: String },
}
#[derive(Clone)]
enum Operator {
GT,
EQ,
LT,
}
impl Operator {
fn apply(&self, left: &NumberType, right: &NumberType) -> bool {
match self {
Operator::GT => left > right,
Operator::EQ => left == right,
Operator::LT => left < right,
}
}
fn from_str(word: &str) -> Option<Operator> {
match word {
">" => Some(Operator::GT),
"=" => Some(Operator::EQ),
"<" => Some(Operator::LT),
_ => None,
}
}
}
#[derive(Clone)]
enum Command {
Print(String),
Read(String),
Goto(LineNumber),
If { left: Value, operator: Operator, right: Value, line_number: LineNumber},
}
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
reader: BufReader<R>,
writer: &'a mut W,
// Каквито други полета ви трябват
lines: BTreeMap<LineNumber, Command>,
env: HashMap<String, NumberType>,
}
fn is_variable_name(word: &str) -> bool {
word.chars().next().map_or(false, |c| c.is_uppercase())
}
/// This type is used as a proof that something is a valid "Value" in our language
/// With a "Value" being either a literal or a syntactically valid variable name
#[derive(Clone)]
enum Value {
Literal(NumberType),
Variable(String),
}
impl Value {
fn from_str(word: &str) -> Option<Value> {
if is_variable_name(word) {
Some(Value::Variable(word.to_string()))
} else if let Ok(num) = word.parse::<NumberType>() {
Some(Value::Literal(num))
} else {
None
}
}
}
fn parse(line: String) -> Result<(LineNumber, Command), InterpreterError> {
let syntax_err = || InterpreterError::SyntaxError { code: line.clone() };
let parse_line_number =
|word: &str| word.parse::<LineNumber>().map_err(|_| syntax_err());
let words: Vec<&str> = line.split_whitespace().collect();
let command_line_number = match words.first() {
Some(&arg) => parse_line_number(arg)?,
None => return Err(syntax_err()),
};
let command = match words[1..] {
["READ", var_name]
if is_variable_name(var_name) => {
Command::Read(var_name.to_string())
},
["GOTO", line_number_arg] => {
Command::Goto(parse_line_number(line_number_arg)?)
},
["PRINT", arg] => {
Command::Print(arg.to_string())
},
["IF", left_arg, operator_arg, right_arg, "GOTO", line_number_arg] => {
let operator = Operator::from_str(operator_arg).ok_or(syntax_err())?;
let line_number = parse_line_number(line_number_arg)?;
let left = Value::from_str(left_arg).ok_or(syntax_err())?;
let right = Value::from_str(right_arg).ok_or(syntax_err())?;
Command::If { left, operator, right, line_number }
},
_ => return Err(syntax_err())
};
Ok((command_line_number, command))
}
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 {
reader: BufReader::new(input),
writer: output,
lines: BTreeMap::new(),
env: HashMap::new()
}
}
fn print(&mut self, value: &str) -> Result<(), InterpreterError> {
let writer = &mut self.writer;
writer
.write_all(value.as_bytes())
.and_then(|_| writer.write_all(b"\n"))
.and_then(|_| writer.flush())
.map_err(InterpreterError::IoError)
}
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 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 (line_number, command) = parse(code.to_string())?;
self.lines.insert(line_number, command);
Ok(())
}
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<NumberType, InterpreterError> {
match Value::from_str(value) {
Some(val) => match self.eval(&val) {
Some(num) => Ok(num),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
},
None => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
fn eval(&self, value: &Value) -> Option<NumberType> {
match value {
Value::Literal(num) => Some(*num),
Value::Variable(var_name) => self.env.get(var_name).cloned(),
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
pub fn run(&mut self) -> Result<(), InterpreterError> {
match self.lines.iter().next() {
Some((&line_number, _)) => self.run_from(line_number),
None => Ok(()),
}
}
fn run_from(&mut self, start_line_number: LineNumber) -> Result<(), InterpreterError> {
let lines = self.lines.clone();
let mut iter = lines.range((Included(start_line_number), Unbounded));
while let Some((&line_number, command)) = iter.next() {
self.execute_command(line_number, command)?;
}
Ok(())
}
fn execute_command(&mut self, line_number: LineNumber, command: &Command) -> Result<(), InterpreterError> {
let to_runtime_err =
|message: &str| InterpreterError::RuntimeError { line_number, message: message.to_string()};
let io_runtime_error =
|| to_runtime_err("There was an IO error while printing the value.");
Ако в който и да е момент операция по четене от input или писане в output върне грешка, това ще е std::io::Error и очакваме да я пакетирате в InterpreterError::IoError и да я върнете.
Аз добре бях тръгнал в отделената print функция именно по тази част от условието, ама после ми се стори сякаш runtime всички грешки ще са RuntimeError и съм я презаписал... Оправено!
let unknown_variable_runtime_error =
|| to_runtime_err("Unknown variable encountered.");
let no_line_with_such_number_runtime_error =
|| to_runtime_err("A line with the given number doesn't exist in the program code.");
match command {
Command::Print(arg) => {
let value = match self.eval_value(&*arg) {
Err(InterpreterError::UnknownVariable { name: _ }) => return Err(unknown_variable_runtime_error()),
Ok(num) => num.to_string(),
_ => arg.clone(),
};
self.print(value.as_str()).map_err(|_| io_runtime_error())
},
Command::Read(var_name) => {
let mut value = String::new();
self.reader
.read_line(&mut value)
.map_err(|_| io_runtime_error())?;
trim_new_line(&mut value);
let num = value.parse::<LineNumber>()
.map_err(|_| to_runtime_err("The provided input is not a valid u16 number."))?;
self.env.insert(var_name.clone(), num);
Ok(())
},
Command::Goto(next_line_number) => {
if !self.lines.contains_key(next_line_number) {
Err(no_line_with_such_number_runtime_error())
} else {
self.run_from(*next_line_number)
}
},
Command::If { left, operator, right, line_number: next_line_number } => {
- // let error_mapper = |err: InterpreterError| {
- // match err {
- // InterpreterError::UnknownVariable { name: _ } => unknown_variable_runtime_error(),
- // _ => no_line_with_such_number_runtime_error(),
- // }
- // };
-
let l = self.eval(left).ok_or(unknown_variable_runtime_error())?;
let r = self.eval(right).ok_or(unknown_variable_runtime_error())?;
if operator.apply(&l, &r) {
self.execute_command(line_number, &Command::Goto(*next_line_number))
} else {
Ok(())
}
},
}
}
}
fn trim_new_line(word: &mut String) -> () {
if word.ends_with('\n') {
word.pop();
if word.ends_with('\r') {
word.pop();
}
}
}
#[cfg(test)]
mod test {
#[cfg(test)]
mod basic {
use crate::*;
// За тестване че някакъв резултат пасва на някакъв 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\n3\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.add("50 IF B < 3 GOTO 20").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n1\n3\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();
}
}
}
This code doesn't terminate properly on GOTO statements, due to control-flow mashup between iteration and recursion. Next solution is fixed.
Давид качи решение на 07.01.2023 15:01 (преди над 2 години)
use std::io::{self, Read, BufReader, Write, BufRead};
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::ops::Bound::*;
type LineNumber = u16; // semantically: the type to index the program lines with
type NumberType = u16; // semantically: the type of the end "values" our program produces
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: LineNumber, message: String },
}
-#[derive(Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
enum Operator {
GT,
EQ,
LT,
}
impl Operator {
fn apply(&self, left: &NumberType, right: &NumberType) -> bool {
match self {
Operator::GT => left > right,
Operator::EQ => left == right,
Operator::LT => left < right,
}
}
fn from_str(word: &str) -> Option<Operator> {
match word {
">" => Some(Operator::GT),
"=" => Some(Operator::EQ),
"<" => Some(Operator::LT),
_ => None,
}
}
}
-#[derive(Clone)]
+#[derive(Debug, Clone, Eq, PartialEq)]
enum Command {
Print(String),
Read(String),
Goto(LineNumber),
If { left: Value, operator: Operator, right: Value, line_number: LineNumber},
}
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
reader: BufReader<R>,
writer: &'a mut W,
// Каквито други полета ви трябват
lines: BTreeMap<LineNumber, Command>,
env: HashMap<String, NumberType>,
}
fn is_variable_name(word: &str) -> bool {
word.chars().next().map_or(false, |c| c.is_uppercase())
}
-
/// This type is used as a proof that something is a valid "Value" in our language
/// With a "Value" being either a literal or a syntactically valid variable name
-#[derive(Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
enum Value {
Literal(NumberType),
Variable(String),
}
impl Value {
fn from_str(word: &str) -> Option<Value> {
if is_variable_name(word) {
Some(Value::Variable(word.to_string()))
} else if let Ok(num) = word.parse::<NumberType>() {
Some(Value::Literal(num))
} else {
None
}
}
}
-fn parse(line: String) -> Result<(LineNumber, Command), InterpreterError> {
+fn parse(line: &String) -> Result<(LineNumber, Command), InterpreterError> {
let syntax_err = || InterpreterError::SyntaxError { code: line.clone() };
let parse_line_number =
|word: &str| word.parse::<LineNumber>().map_err(|_| syntax_err());
let words: Vec<&str> = line.split_whitespace().collect();
let command_line_number = match words.first() {
Some(&arg) => parse_line_number(arg)?,
None => return Err(syntax_err()),
};
let command = match words[1..] {
["READ", var_name]
if is_variable_name(var_name) => {
Command::Read(var_name.to_string())
},
["GOTO", line_number_arg] => {
Command::Goto(parse_line_number(line_number_arg)?)
},
["PRINT", arg] => {
Command::Print(arg.to_string())
},
["IF", left_arg, operator_arg, right_arg, "GOTO", line_number_arg] => {
let operator = Operator::from_str(operator_arg).ok_or(syntax_err())?;
let line_number = parse_line_number(line_number_arg)?;
let left = Value::from_str(left_arg).ok_or(syntax_err())?;
let right = Value::from_str(right_arg).ok_or(syntax_err())?;
Command::If { left, operator, right, line_number }
},
_ => return Err(syntax_err())
};
Ok((command_line_number, command))
}
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 {
reader: BufReader::new(input),
writer: output,
lines: BTreeMap::new(),
env: HashMap::new()
}
}
- fn print(&mut self, value: &str) -> Result<(), InterpreterError> {
- let writer = &mut self.writer;
-
- writer
- .write_all(value.as_bytes())
- .and_then(|_| writer.write_all(b"\n"))
- .and_then(|_| writer.flush())
- .map_err(InterpreterError::IoError)
- }
-
/// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 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 (line_number, command) = parse(code.to_string())?;
+ let (line_number, command) = parse(&code.to_string())?;
self.lines.insert(line_number, command);
Ok(())
}
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<NumberType, InterpreterError> {
match Value::from_str(value) {
Some(val) => match self.eval(&val) {
Some(num) => Ok(num),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
},
None => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
fn eval(&self, value: &Value) -> Option<NumberType> {
match value {
Value::Literal(num) => Some(*num),
Value::Variable(var_name) => self.env.get(var_name).cloned(),
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
pub fn run(&mut self) -> Result<(), InterpreterError> {
match self.lines.iter().next() {
Some((&line_number, _)) => self.run_from(line_number),
None => Ok(()),
}
}
fn run_from(&mut self, start_line_number: LineNumber) -> Result<(), InterpreterError> {
let lines = self.lines.clone();
let mut iter = lines.range((Included(start_line_number), Unbounded));
while let Some((&line_number, command)) = iter.next() {
- self.execute_command(line_number, command)?;
+
+ //the result of execute_command is basically a flag if we should
+ //continue to iterate OR in the case of 'goto' we've started
+ //another recursive branch and want to terminate current iteration
+ let should_continue = self.execute_command(line_number, command)?;
+ if !should_continue {
+ break;
+ }
}
Ok(())
}
- fn execute_command(&mut self, line_number: LineNumber, command: &Command) -> Result<(), InterpreterError> {
+ fn execute_command(&mut self, line_number: LineNumber, command: &Command) -> Result<bool, InterpreterError> {
let to_runtime_err =
|message: &str| InterpreterError::RuntimeError { line_number, message: message.to_string()};
- let io_runtime_error =
- || to_runtime_err("There was an IO error while printing the value.");
let unknown_variable_runtime_error =
|| to_runtime_err("Unknown variable encountered.");
let no_line_with_such_number_runtime_error =
|| to_runtime_err("A line with the given number doesn't exist in the program code.");
match command {
Command::Print(arg) => {
let value = match self.eval_value(&*arg) {
- Err(InterpreterError::UnknownVariable { name: _ }) => return Err(unknown_variable_runtime_error()),
+ Err(InterpreterError::UnknownVariable { .. }) => return Err(unknown_variable_runtime_error()),
Ok(num) => num.to_string(),
_ => arg.clone(),
};
- self.print(value.as_str()).map_err(|_| io_runtime_error())
+ self.print(value.as_str()).map(|_| true)
},
Command::Read(var_name) => {
- let mut value = String::new();
-
- self.reader
- .read_line(&mut value)
- .map_err(|_| io_runtime_error())?;
-
- trim_new_line(&mut value);
-
- let num = value.parse::<LineNumber>()
+ let num =
+ self.read()?
+ .parse::<LineNumber>()
.map_err(|_| to_runtime_err("The provided input is not a valid u16 number."))?;
self.env.insert(var_name.clone(), num);
- Ok(())
+ Ok(true)
},
Command::Goto(next_line_number) => {
if !self.lines.contains_key(next_line_number) {
Err(no_line_with_such_number_runtime_error())
} else {
- self.run_from(*next_line_number)
+ self.run_from(*next_line_number).map(|_| false)
}
},
Command::If { left, operator, right, line_number: next_line_number } => {
let l = self.eval(left).ok_or(unknown_variable_runtime_error())?;
let r = self.eval(right).ok_or(unknown_variable_runtime_error())?;
if operator.apply(&l, &r) {
- self.execute_command(line_number, &Command::Goto(*next_line_number))
+ self.execute_command(line_number, &Command::Goto(*next_line_number)).map(|_| false)
} else {
- Ok(())
+ Ok(true)
}
},
}
}
+
+ fn print(&mut self, value: &str) -> Result<(), InterpreterError> {
+ let writer = &mut self.writer;
+
+ writer
+ .write_all(value.as_bytes())
+ .and_then(|_| writer.write_all(b"\n"))
+ .and_then(|_| writer.flush())
+ .map_err(InterpreterError::IoError)
+ }
+
+ fn read(&mut self) -> Result<String, InterpreterError> {
+ let mut value = String::new();
+
+ self.reader
+ .read_line(&mut value)
+ .map_err(InterpreterError::IoError)?;
+
+ trim_new_line(&mut value);
+
+ Ok(value)
+ }
}
fn trim_new_line(word: &mut String) -> () {
if word.ends_with('\n') {
word.pop();
if word.ends_with('\r') {
word.pop();
}
}
}
#[cfg(test)]
mod test {
- #[cfg(test)]
- mod basic {
- use crate::*;
- // За тестване че някакъв резултат пасва на някакъв pattern:
- macro_rules! assert_match {
- ($expr:expr, $pat:pat) => {
- let result = $expr;
+ // За тестване че някакъв резултат пасва на някакъв 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)
- );
- }
- };
+ if let $pat = result {
+ // all good
+ } else {
+ assert!(
+ false,
+ "Expression {:?} does not match the pattern {:?}",
+ result,
+ stringify!($pat)
+ );
+ }
+ };
+ }
+
+ struct NotBuffered {}
+
+ impl std::io::Read for NotBuffered {
+ fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
+ Ok(0)
}
+ }
+ #[cfg(test)]
+ mod basic {
+ use crate::*;
+ use super::*;
+
#[test]
fn test_basic_1() {
// Забележете `b""` -- низ от байтове
- let input: &[u8] = b"1\n2\n3\n";
+ 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.add("50 IF B < 3 GOTO 20").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
- assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n1\n3\n");
+ 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 {}
+ #[test]
+ fn test_not_buffered() {
+ let input = NotBuffered {};
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
- impl std::io::Read for NotBuffered {
- fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
- Ok(0)
+ interpreter.add("10 PRINT 10").unwrap();
+ interpreter.run().unwrap();
+ }
+ }
+
+ #[cfg(test)]
+ mod parse {
+ use crate::*;
+ use super::*;
+
+ #[test]
+ fn valid_commands() {
+ let cmm_expected: Vec<(&str, (LineNumber, Command))> = vec![
+ ("10 PRINT alo_da", (10, Command::Print("alo_da".to_string()))),
+ ("10 READ Var1", (10, Command::Read("Var1".to_string()))),
+ ("10 READ ÜblicheVariable", (10, Command::Read("ÜblicheVariable".to_string()))),
+ ("10 READ Щастие", (10, Command::Read("Щастие".to_string()))),
+ ("10 GOTO 15", (10, Command::Goto(15))),
+ ("10 IF 2 > 1 GOTO 15", (10, Command::If { left: Value::Literal(2), operator: Operator::GT, right: Value::Literal(1), line_number: 15 })),
+ ("10 IF Var2 = K GOTO 15", (10, Command::If { left: Value::Variable("Var2".to_string()), operator: Operator::EQ, right: Value::Variable("K".to_string()), line_number: 15 })),
+ ];
+
+ for (line, expected) in cmm_expected {
+ let res = parse(&line.to_string());
+ assert!(res.is_ok());
+ let line_cmm = res.unwrap();
+ assert_eq!(expected, line_cmm);
}
}
#[test]
- fn test_not_buffered() {
+ fn invalid_commands() {
+ let invalid_commands = vec![
+ "10 PRINT A B", //to many args
+
+ "20 IF 13 > 18 GOTO", //missing arg for GOTO
+ "20 IF invalid > 18 GOTO 20", //invalid variable name
+ "20 IF 20 > 18 GOTO Nqkude", //invalid line number as arg to goto
+ "20 IF 20 <$> 18 GOTO 50", //invalid operator
+
+ "READ X", //missing command line number
+
+ "10 READ lowercase", //lowercase variable name -> invalid
+ "10 READ üblich", // invalid lowercase unicode var name
+ "10 READ 70", //invalid literal as var name
+
+ "30", //missing command
+ "", //empty line is invalid
+ ];
+
+ for line in invalid_commands {
+ assert_match!(parse(&line.to_string()), Err(InterpreterError::SyntaxError { .. }));
+ }
+ }
+ }
+
+ #[cfg(test)]
+ mod eval {
+ use crate::*;
+ use super::*;
+
+ #[test]
+ fn valid_variables() {
+ let input: &[u8] = b"1\n2\n3\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 Ö").unwrap();
+ interpreter.add("25 READ Ю").unwrap();
+ interpreter.add("30 PRINT A").unwrap();
+ interpreter.add("40 PRINT Ö").unwrap();
+ interpreter.add("50 PRINT Ю").unwrap();
+
+ interpreter.run().unwrap();
+ assert_eq!(interpreter.eval_value("A").unwrap(), 1);
+ assert_eq!(interpreter.eval_value("Ö").unwrap(), 2);
+ assert_eq!(interpreter.eval_value("Ю").unwrap(), 3);
+ assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
+ }
+
+ #[test]
+ fn reassignment_of_variable() {
+ let input: &[u8] = b"1\n2\n3";let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ interpreter.add("10 READ A").unwrap();
+ interpreter.add("20 READ A").unwrap();
+ interpreter.run().unwrap();
+ assert_eq!(interpreter.eval_value("A").unwrap(), 2);
+ }
+
+ #[test]
+ fn test_eval_cases() {
+ let input: &[u8] = b"1\n";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ interpreter.add("10 READ A").unwrap();
+ interpreter.run().unwrap();
+
+ assert_match!(interpreter.eval_value("42"), Ok(42));
+ assert_match!(interpreter.eval_value("A"), Ok(1));
+ assert_match!(interpreter.eval_value("B"), Err(InterpreterError::UnknownVariable { .. }));
+ assert_match!(interpreter.eval_value("ö"), Err(InterpreterError::NotANumber { .. }));
+ assert_match!(interpreter.eval_value("ц"), Err(InterpreterError::NotANumber { .. }));
+ assert_match!(interpreter.eval_value("invaid"), Err(InterpreterError::NotANumber { .. }));
+ }
+ }
+
+ #[cfg(test)]
+ mod command_logic {
+ use crate::*;
+ use super::*;
+
+ #[test]
+ fn print() {
+ let input: &[u8] = b"123\n";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ interpreter.add("10 READ A").unwrap();
+ interpreter.add("20 PRINT A").unwrap();
+ interpreter.add("30 PRINT literal_word").unwrap();
+ interpreter.add("40 PRINT B").unwrap();
+
+ assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 40, message: _ }));
+
+ assert_eq!(String::from_utf8(output).unwrap(), "123\nliteral_word\n");
+ }
+
+ #[test]
+ fn goto_invalid_line_error_correctly_produced_on_same_line() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
- interpreter.add("10 PRINT 10").unwrap();
+ interpreter.add("10 GOTO 20").unwrap();
+
+ assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 10, message: _ }));
+ }
+ }
+
+ #[cfg(test)]
+ mod end_to_end {
+ use crate::*;
+ use super::*;
+
+ #[test]
+ fn run_no_code_ok() {
+ let input = NotBuffered {};
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+ assert_match!(interpreter.run(), Ok(()));
+ }
+
+ #[test]
+ fn run_with_error() {
+ let input = NotBuffered {};
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ interpreter.add("10 PRINT line_10").unwrap();
+ interpreter.add("20 IF 2 > Unknown GOTO 10").unwrap();
+
+ let result = interpreter.run();
+
+ assert_match!(result, Err(InterpreterError::RuntimeError { line_number: 20, message: _ }));
+ }
+
+ #[test]
+ fn if_with_io() {
+ // Забележете `b""` -- низ от байтове
+ let input: &[u8] = b"1\n2\n3\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.add("50 IF B < 3 GOTO 20").unwrap();
+
interpreter.run().unwrap();
+ assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
+ assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n1\n3\n");
+ }
+
+ #[test]
+ fn simple_game() {
+ let lines = vec![
+ "10 READ Guess",
+ "30 IF Guess > 42 GOTO 100",
+ "40 IF Guess < 42 GOTO 200",
+ "50 IF Guess = 42 GOTO 300",
+ "100 PRINT h",
+ "110 GOTO 10",
+ "200 PRINT l",
+ "210 GOTO 10",
+ "300 PRINT yes!",
+ ];
+
+ let input: &[u8] = b"1\n100\n50\n40\n42\n";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ for line in lines {
+ interpreter.add(line).unwrap();
+ }
+
+ assert!(interpreter.run().is_ok());
+ assert_eq!(String::from_utf8(output).unwrap(), "l\nh\nh\nl\nyes!\n");
}
}
}
Давид качи решение на 07.01.2023 15:02 (преди над 2 години)
use std::io::{self, Read, BufReader, Write, BufRead};
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::ops::Bound::*;
type LineNumber = u16; // semantically: the type to index the program lines with
type NumberType = u16; // semantically: the type of the end "values" our program produces
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: LineNumber, message: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Operator {
GT,
EQ,
LT,
}
impl Operator {
fn apply(&self, left: &NumberType, right: &NumberType) -> bool {
match self {
Operator::GT => left > right,
Operator::EQ => left == right,
Operator::LT => left < right,
}
}
fn from_str(word: &str) -> Option<Operator> {
match word {
">" => Some(Operator::GT),
"=" => Some(Operator::EQ),
"<" => Some(Operator::LT),
_ => None,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum Command {
Print(String),
Read(String),
Goto(LineNumber),
If { left: Value, operator: Operator, right: Value, line_number: LineNumber},
}
pub struct Interpreter<'a, R: Read, W: Write> {
// Тези полета не са публични, така че може да си ги промените, ако искате:
reader: BufReader<R>,
writer: &'a mut W,
// Каквито други полета ви трябват
lines: BTreeMap<LineNumber, Command>,
env: HashMap<String, NumberType>,
}
fn is_variable_name(word: &str) -> bool {
word.chars().next().map_or(false, |c| c.is_uppercase())
}
/// This type is used as a proof that something is a valid "Value" in our language
/// With a "Value" being either a literal or a syntactically valid variable name
#[derive(Debug, Clone, PartialEq, Eq)]
enum Value {
Literal(NumberType),
Variable(String),
}
impl Value {
fn from_str(word: &str) -> Option<Value> {
if is_variable_name(word) {
Some(Value::Variable(word.to_string()))
} else if let Ok(num) = word.parse::<NumberType>() {
Some(Value::Literal(num))
} else {
None
}
}
}
fn parse(line: &String) -> Result<(LineNumber, Command), InterpreterError> {
let syntax_err = || InterpreterError::SyntaxError { code: line.clone() };
let parse_line_number =
|word: &str| word.parse::<LineNumber>().map_err(|_| syntax_err());
let words: Vec<&str> = line.split_whitespace().collect();
let command_line_number = match words.first() {
Some(&arg) => parse_line_number(arg)?,
None => return Err(syntax_err()),
};
let command = match words[1..] {
["READ", var_name]
if is_variable_name(var_name) => {
Command::Read(var_name.to_string())
},
["GOTO", line_number_arg] => {
Command::Goto(parse_line_number(line_number_arg)?)
},
["PRINT", arg] => {
Command::Print(arg.to_string())
},
["IF", left_arg, operator_arg, right_arg, "GOTO", line_number_arg] => {
let operator = Operator::from_str(operator_arg).ok_or(syntax_err())?;
let line_number = parse_line_number(line_number_arg)?;
let left = Value::from_str(left_arg).ok_or(syntax_err())?;
let right = Value::from_str(right_arg).ok_or(syntax_err())?;
Command::If { left, operator, right, line_number }
},
_ => return Err(syntax_err())
};
Ok((command_line_number, command))
}
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 {
reader: BufReader::new(input),
writer: output,
lines: BTreeMap::new(),
env: 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 (line_number, command) = parse(&code.to_string())?;
self.lines.insert(line_number, command);
Ok(())
}
/// Оценява `value` като стойност в контекста на интерпретатора:
///
/// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
/// дефинирана променлива с това име и връщаме нейната стойност.
/// -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
/// - Ако `value` е валидно u16 число, връщаме числото
/// -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
///
pub fn eval_value(&self, value: &str) -> Result<NumberType, InterpreterError> {
match Value::from_str(value) {
Some(val) => match self.eval(&val) {
Some(num) => Ok(num),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
},
None => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
fn eval(&self, value: &Value) -> Option<NumberType> {
match value {
Value::Literal(num) => Some(*num),
Value::Variable(var_name) => self.env.get(var_name).cloned(),
}
}
/// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
/// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
/// по-голямо число, функцията свършва и връща `Ok(())`.
pub fn run(&mut self) -> Result<(), InterpreterError> {
match self.lines.iter().next() {
Some((&line_number, _)) => self.run_from(line_number),
None => Ok(()),
}
}
fn run_from(&mut self, start_line_number: LineNumber) -> Result<(), InterpreterError> {
let lines = self.lines.clone();
let mut iter = lines.range((Included(start_line_number), Unbounded));
while let Some((&line_number, command)) = iter.next() {
//the result of execute_command is basically a flag if we should
//continue to iterate OR in the case of 'goto' we've started
//another recursive branch and want to terminate current iteration
let should_continue = self.execute_command(line_number, command)?;
if !should_continue {
break;
}
}
Ok(())
}
fn execute_command(&mut self, line_number: LineNumber, command: &Command) -> Result<bool, InterpreterError> {
let to_runtime_err =
|message: &str| InterpreterError::RuntimeError { line_number, message: message.to_string()};
let unknown_variable_runtime_error =
|| to_runtime_err("Unknown variable encountered.");
let no_line_with_such_number_runtime_error =
|| to_runtime_err("A line with the given number doesn't exist in the program code.");
match command {
Command::Print(arg) => {
let value = match self.eval_value(&*arg) {
Err(InterpreterError::UnknownVariable { .. }) => return Err(unknown_variable_runtime_error()),
Ok(num) => num.to_string(),
_ => arg.clone(),
};
self.print(value.as_str()).map(|_| true)
},
Command::Read(var_name) => {
let num =
self.read()?
.parse::<LineNumber>()
.map_err(|_| to_runtime_err("The provided input is not a valid u16 number."))?;
self.env.insert(var_name.clone(), num);
Ok(true)
},
Command::Goto(next_line_number) => {
if !self.lines.contains_key(next_line_number) {
Err(no_line_with_such_number_runtime_error())
} else {
self.run_from(*next_line_number).map(|_| false)
}
},
Command::If { left, operator, right, line_number: next_line_number } => {
let l = self.eval(left).ok_or(unknown_variable_runtime_error())?;
let r = self.eval(right).ok_or(unknown_variable_runtime_error())?;
if operator.apply(&l, &r) {
self.execute_command(line_number, &Command::Goto(*next_line_number)).map(|_| false)
} else {
Ok(true)
}
},
}
}
fn print(&mut self, value: &str) -> Result<(), InterpreterError> {
let writer = &mut self.writer;
writer
.write_all(value.as_bytes())
.and_then(|_| writer.write_all(b"\n"))
.and_then(|_| writer.flush())
.map_err(InterpreterError::IoError)
}
fn read(&mut self) -> Result<String, InterpreterError> {
let mut value = String::new();
self.reader
.read_line(&mut value)
.map_err(InterpreterError::IoError)?;
trim_new_line(&mut value);
Ok(value)
}
}
fn trim_new_line(word: &mut String) -> () {
if word.ends_with('\n') {
word.pop();
if word.ends_with('\r') {
word.pop();
}
}
}
#[cfg(test)]
mod test {
// За тестване че някакъв резултат пасва на някакъв 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)
);
}
};
}
struct NotBuffered {}
impl std::io::Read for NotBuffered {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Ok(0)
}
}
#[cfg(test)]
mod basic {
use crate::*;
use super::*;
#[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_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();
}
}
#[cfg(test)]
mod parse {
use crate::*;
- use super::*;
#[test]
fn valid_commands() {
let cmm_expected: Vec<(&str, (LineNumber, Command))> = vec![
("10 PRINT alo_da", (10, Command::Print("alo_da".to_string()))),
("10 READ Var1", (10, Command::Read("Var1".to_string()))),
("10 READ ÜblicheVariable", (10, Command::Read("ÜblicheVariable".to_string()))),
("10 READ Щастие", (10, Command::Read("Щастие".to_string()))),
("10 GOTO 15", (10, Command::Goto(15))),
("10 IF 2 > 1 GOTO 15", (10, Command::If { left: Value::Literal(2), operator: Operator::GT, right: Value::Literal(1), line_number: 15 })),
("10 IF Var2 = K GOTO 15", (10, Command::If { left: Value::Variable("Var2".to_string()), operator: Operator::EQ, right: Value::Variable("K".to_string()), line_number: 15 })),
];
for (line, expected) in cmm_expected {
let res = parse(&line.to_string());
assert!(res.is_ok());
let line_cmm = res.unwrap();
assert_eq!(expected, line_cmm);
}
}
#[test]
fn invalid_commands() {
let invalid_commands = vec![
"10 PRINT A B", //to many args
"20 IF 13 > 18 GOTO", //missing arg for GOTO
"20 IF invalid > 18 GOTO 20", //invalid variable name
"20 IF 20 > 18 GOTO Nqkude", //invalid line number as arg to goto
"20 IF 20 <$> 18 GOTO 50", //invalid operator
"READ X", //missing command line number
"10 READ lowercase", //lowercase variable name -> invalid
"10 READ üblich", // invalid lowercase unicode var name
"10 READ 70", //invalid literal as var name
"30", //missing command
"", //empty line is invalid
];
for line in invalid_commands {
assert_match!(parse(&line.to_string()), Err(InterpreterError::SyntaxError { .. }));
}
}
}
#[cfg(test)]
mod eval {
use crate::*;
- use super::*;
#[test]
fn valid_variables() {
let input: &[u8] = b"1\n2\n3\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 Ö").unwrap();
interpreter.add("25 READ Ю").unwrap();
interpreter.add("30 PRINT A").unwrap();
interpreter.add("40 PRINT Ö").unwrap();
interpreter.add("50 PRINT Ю").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1);
assert_eq!(interpreter.eval_value("Ö").unwrap(), 2);
assert_eq!(interpreter.eval_value("Ю").unwrap(), 3);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n3\n");
}
#[test]
fn reassignment_of_variable() {
let input: &[u8] = b"1\n2\n3";let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 READ A").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 2);
}
#[test]
fn test_eval_cases() {
let input: &[u8] = b"1\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.run().unwrap();
assert_match!(interpreter.eval_value("42"), Ok(42));
assert_match!(interpreter.eval_value("A"), Ok(1));
assert_match!(interpreter.eval_value("B"), Err(InterpreterError::UnknownVariable { .. }));
assert_match!(interpreter.eval_value("ö"), Err(InterpreterError::NotANumber { .. }));
assert_match!(interpreter.eval_value("ц"), Err(InterpreterError::NotANumber { .. }));
assert_match!(interpreter.eval_value("invaid"), Err(InterpreterError::NotANumber { .. }));
}
}
#[cfg(test)]
mod command_logic {
use crate::*;
use super::*;
#[test]
fn print() {
let input: &[u8] = b"123\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ A").unwrap();
interpreter.add("20 PRINT A").unwrap();
interpreter.add("30 PRINT literal_word").unwrap();
interpreter.add("40 PRINT B").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 40, message: _ }));
assert_eq!(String::from_utf8(output).unwrap(), "123\nliteral_word\n");
}
#[test]
fn goto_invalid_line_error_correctly_produced_on_same_line() {
let input = NotBuffered {};
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, message: _ }));
}
}
#[cfg(test)]
mod end_to_end {
use crate::*;
use super::*;
#[test]
fn run_no_code_ok() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.run(), Ok(()));
}
#[test]
fn run_with_error() {
let input = NotBuffered {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT line_10").unwrap();
interpreter.add("20 IF 2 > Unknown GOTO 10").unwrap();
let result = interpreter.run();
assert_match!(result, Err(InterpreterError::RuntimeError { line_number: 20, message: _ }));
}
#[test]
fn if_with_io() {
// Забележете `b""` -- низ от байтове
let input: &[u8] = b"1\n2\n3\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.add("50 IF B < 3 GOTO 20").unwrap();
interpreter.run().unwrap();
assert_eq!(interpreter.eval_value("A").unwrap(), 1_u16);
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n1\n3\n");
}
#[test]
fn simple_game() {
let lines = vec![
"10 READ Guess",
"30 IF Guess > 42 GOTO 100",
"40 IF Guess < 42 GOTO 200",
"50 IF Guess = 42 GOTO 300",
"100 PRINT h",
"110 GOTO 10",
"200 PRINT l",
"210 GOTO 10",
"300 PRINT yes!",
];
let input: &[u8] = b"1\n100\n50\n40\n42\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
for line in lines {
interpreter.add(line).unwrap();
}
assert!(interpreter.run().is_ok());
assert_eq!(String::from_utf8(output).unwrap(), "l\nh\nh\nl\nyes!\n");
}
}
}