Решение на Basic BASIC от Тодор Кюркчиев

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

Към профила на Тодор Кюркчиев

Резултати

  • 12 точки от тестове
  • 0 бонус точки
  • 12 точки общо
  • 9 успешни тест(а)
  • 6 неуспешни тест(а)

Код

use std::collections::HashMap;
use std::io::{self, Read, Write};
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: R,
output: &'a mut W,
lines: HashMap<u16, (&'a str, Vec<&'a str>)>,
variables: HashMap<String, u16>,
current_line: u16
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter {
input,
output,
lines: HashMap::new(),
variables: HashMap::new(),
current_line: 0
}
}
pub fn add(&mut self, code: &'a str) -> Result<(), InterpreterError> {
let mut tokens = code.split_whitespace();
let line_number: u16 = match tokens.next() {
Some(s) => match s.parse() {
Ok(n) => n,
Err(_) => return Err(InterpreterError::NotANumber { value: s.to_string() }),
},
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let command = match tokens.next() {
Some(s) => s,
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let params: Vec<&'a str> = tokens.collect();
if params.len() < 1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
self.lines.insert(line_number, (command, params));
Ok(())
}
}
impl<R: Read, W: Write> Interpreter<'_, R, W> {
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().next().map_or(false, char::is_uppercase) {
match self.variables.get(value) {
Some(n) => Ok(*n),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else {
match value.parse() {
Ok(n) => Ok(n),
Err(_) => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
}
}
use std::io::Error as IoError;
impl From<IoError> for InterpreterError {
fn from(e: IoError) -> InterpreterError {
InterpreterError::IoError(e)
}
}
impl<R: Read, W: Write> Interpreter<'_ ,R, W> {
fn execute_print(&mut self, value: &str) -> Result<(), InterpreterError> {
let output = if value.chars().next().unwrap().is_uppercase() {
match self.variables.get(value) {
Some(v) => v.to_string(),
None => return Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else if value.chars().next().unwrap().is_digit(10) {
value.to_string()
} else {
value.to_string()
};
write!(self.output, "{}\n", output).map_err(InterpreterError::IoError)
}
fn execute_read(&mut self, variable_name: &str) -> Result<(), InterpreterError> {
let mut buffer = [0; 1];
let mut line = Vec::new();
while self.input.read(&mut buffer)? != 0 {
line.push(buffer[0]);
if buffer[0] == b'\n' {
break;
}
}
let line = String::from_utf8(line).map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
let value = line.trim_end().parse().map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
self.variables.insert(variable_name.to_string(), value);
Ok(())
}
fn execute_goto(&mut self, line_number: u16) -> Result<(), InterpreterError> {
if self.lines.contains_key(&line_number) {
self.current_line = line_number;
Ok(())
} else {
Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid line number".to_string() })
}
}
fn execute_if<'a>(&mut self, lhs: &'a str, op: &'a str, rhs: &'a str, line_number: u16) -> Result<(), InterpreterError> {
let lhs = self.eval_value(lhs)?;
let rhs = self.eval_value(rhs)?;
let should_goto = match op {
">" => lhs > rhs,
"<" => lhs < rhs,
"=" => lhs == rhs,
_ => return Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid operator".to_string() }),
};
if should_goto {
self.execute_goto(line_number)?;
}
Ok(())
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<u16> = self.lines.keys().cloned().collect();
keys.sort();
for key in keys {
self.current_line = key;
let (command, params) = match self.lines.get(&key) {
Some((c, p)) => (c, p),
None => return Err(InterpreterError::RuntimeError { line_number: key, message: "Line not found".to_string() }),
};
match *command {
"PRINT" => self.execute_print(params[0])?,
"READ" => self.execute_read(params[0])?,
"GOTO" => self.execute_goto(params[0].parse::<u16>().unwrap())?,
"IF" => self.execute_if(params[0], params[1], params[2], params[4].parse::<u16>().unwrap())?,
_ => return Err(InterpreterError::SyntaxError { code: command.to_string() }),
}
}
Ok(())
}
}
#[cfg(test)]
mod tests{
use super::*;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
let result = $expr;
if let $pat = result {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", result, stringify!($pat));
}
}
}
#[test]
pub fn test_if_command() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\n");
}
#[test]
pub fn test_if_command_with_variable() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\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();
}
}

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

Compiling solution v0.1.0 (/tmp/d20230111-3772066-1i0vg7b/solution)
    Finished test [unoptimized + debuginfo] target(s) in 1.46s
     Running tests/solution_test.rs (target/debug/deps/solution_test-0edbea2040daef01)

running 15 tests
test solution_test::test_basic_goto ... FAILED
test solution_test::test_basic_if ... FAILED
test solution_test::test_basic_print ... ok
test solution_test::test_basic_input ... ok
test solution_test::test_erroring_goto ... ok
test solution_test::test_basic_read ... ok
test solution_test::test_io_error_read ... ok
test solution_test::test_full_program ... FAILED
test solution_test::test_io_error_write ... ok
test solution_test::test_line_order_and_overwriting ... ok
test solution_test::test_print_cyrillic ... ok
test solution_test::test_runtime_errors ... FAILED
test solution_test::test_print_vars_and_strings ... ok
test solution_test::test_syntax_errors_1 ... FAILED
test solution_test::test_syntax_errors_2 ... FAILED

failures:

---- solution_test::test_basic_goto stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `"1\n2\n3\n"`,
 right: `"1\n3\n"`', tests/solution_test.rs:193:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'solution_test::test_basic_goto' panicked at 'assertion failed: `(left == right)`
  left: `"1\n2\n3\n"`,
 right: `"1\n3\n"`', tests/solution_test.rs:180:5

---- solution_test::test_basic_if stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `"0\n1\n"`,
 right: `"1\n"`', tests/solution_test.rs:224:9
thread 'solution_test::test_basic_if' panicked at 'assertion failed: `(left == right)`
  left: `"0\n1\n"`,
 right: `"1\n"`', tests/solution_test.rs:212:5

---- solution_test::test_full_program stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `"too_high\ntoo_low\nyou_got_it!\n"`,
 right: `"too_low\ntoo_high\nyou_got_it!\n"`', tests/solution_test.rs:248:9
thread 'solution_test::test_full_program' panicked at 'assertion failed: `(left == right)`
  left: `"too_high\ntoo_low\nyou_got_it!\n"`,
 right: `"too_low\ntoo_high\nyou_got_it!\n"`', tests/solution_test.rs:230:5

---- solution_test::test_runtime_errors stdout ----
thread '<unnamed>' panicked at 'Expression Err(UnknownVariable { name: "A" }) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 11, .. })"', tests/solution_test.rs:307:9
thread 'solution_test::test_runtime_errors' panicked at 'Expression Err(UnknownVariable { name: "A" }) does not match the pattern "Err(InterpreterError::RuntimeError { line_number: 11, .. })"', tests/solution_test.rs:301:5

---- solution_test::test_syntax_errors_1 stdout ----
thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:259:9
thread 'solution_test::test_syntax_errors_1' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:254:5

---- solution_test::test_syntax_errors_2 stdout ----
thread '<unnamed>' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:280:9
thread 'solution_test::test_syntax_errors_2' panicked at 'Expression Ok(()) does not match the pattern "Err(InterpreterError::SyntaxError { .. })"', tests/solution_test.rs:275:5


failures:
    solution_test::test_basic_goto
    solution_test::test_basic_if
    solution_test::test_full_program
    solution_test::test_runtime_errors
    solution_test::test_syntax_errors_1
    solution_test::test_syntax_errors_2

test result: FAILED. 9 passed; 6 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--test solution_test`

История (4 версии и 2 коментара)

Тодор качи първо решение на 10.01.2023 11:48 (преди над 2 години)

От условието:

Допълнително, забележете, че типа R има само Read ограничение. Очакваме да четете цели редове, така че това може да е доста досадно (надяваме се, че е очевидно, но ако просто смените trait constraint-а, домашното няма да се компилира). Но може би из лекциите сме говорили за тип от стандартната библиотека, който може да буферира Read типове и да ви даде да четете по редове?

Тодор качи решение на 10.01.2023 13:03 (преди над 2 години)

use std::collections::HashMap;
use std::io::{self, Read, Write};
use crate::io::BufRead;
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: R,
output: &'a mut W,
lines: HashMap<u16, (&'a str, Vec<&'a str>)>,
variables: HashMap<String, u16>,
current_line: u16
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter {
input,
output,
lines: HashMap::new(),
variables: HashMap::new(),
current_line: 0
}
}
pub fn add(&mut self, code: &'a str) -> Result<(), InterpreterError> {
let mut tokens = code.split_whitespace();
let line_number: u16 = match tokens.next() {
Some(s) => match s.parse() {
Ok(n) => n,
Err(_) => return Err(InterpreterError::NotANumber { value: s.to_string() }),
},
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let command = match tokens.next() {
Some(s) => s,
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let params: Vec<&'a str> = tokens.collect();
if params.len() < 1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
self.lines.insert(line_number, (command, params));
Ok(())
}
}
impl<R: Read, W: Write> Interpreter<'_, R, W> {
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().next().map_or(false, char::is_uppercase) {
match self.variables.get(value) {
Some(n) => Ok(*n),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else {
match value.parse() {
Ok(n) => Ok(n),
Err(_) => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
}
}
impl<R: Read + BufRead, W: Write> Interpreter<'_ ,R, W> {
fn execute_print(&mut self, value: &str) -> Result<(), InterpreterError> {
let output = if value.chars().next().unwrap().is_uppercase() {
match self.variables.get(value) {
Some(v) => v.to_string(),
None => return Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else if value.chars().next().unwrap().is_digit(10) {
value.to_string()
} else {
value.to_string()
};
write!(self.output, "{}\n", output).map_err(InterpreterError::IoError)
}
fn execute_read(&mut self, variable_name: &str) -> Result<(), InterpreterError> {
let mut line = String::new();
self.input.read_line(&mut line).map_err(InterpreterError::IoError)?;
let value = line.trim_end().parse().map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
self.variables.insert(variable_name.to_string(), value);
Ok(())
}
fn execute_goto(&mut self, line_number: u16) -> Result<(), InterpreterError> {
if self.lines.contains_key(&line_number) {
self.current_line = line_number;
Ok(())
} else {
Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid line number".to_string() })
}
}
fn execute_if<'a>(&mut self, lhs: &'a str, op: &'a str, rhs: &'a str, line_number: u16) -> Result<(), InterpreterError> {
let lhs = self.eval_value(lhs)?;
let rhs = self.eval_value(rhs)?;
let should_goto = match op {
">" => lhs > rhs,
"<" => lhs < rhs,
"=" => lhs == rhs,
_ => return Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid operator".to_string() }),
};
if should_goto {
self.execute_goto(line_number)?;
}
Ok(())
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<u16> = self.lines.keys().cloned().collect();
keys.sort();
for key in keys {
self.current_line = key;
let (command, params) = match self.lines.get(&key) {
Some((c, p)) => (c, p),
None => return Err(InterpreterError::RuntimeError { line_number: key, message: "Line not found".to_string() }),
};
match *command {
"PRINT" => self.execute_print(params[0])?,
"READ" => self.execute_read(params[0])?,
"GOTO" => self.execute_goto(params[0].parse::<u16>().unwrap())?,
"IF" => self.execute_if(params[0], params[1], params[2], params[4].parse::<u16>().unwrap())?,
_ => return Err(InterpreterError::SyntaxError { code: command.to_string() }),
}
}
Ok(())
}
}
+
+#[cfg(test)]
+mod tests{
+ use super::*;
+macro_rules! assert_match {
+ ($expr:expr, $pat:pat) => {
+ let result = $expr;
+
+ if let $pat = result {
+ // all good
+ } else {
+ assert!(false, "Expression {:?} does not match the pattern {:?}", result, stringify!($pat));
+ }
+ }
+}
+
+#[test]
+pub fn test_if_command() {
+ let input: &[u8] = b"";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
+ assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
+ assert_match!(interpreter.run(), Ok(()));
+ assert_eq!(output, b"hello\n");
+}
+
+#[test]
+pub fn test_if_command_with_variable() {
+ let input: &[u8] = b"";
+ let mut output = Vec::<u8>::new();
+ let mut interpreter = Interpreter::new(input, &mut output);
+
+ assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
+ assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
+ assert_match!(interpreter.run(), Ok(()));
+ assert_eq!(output, b"hello\n");
+}
+
+}

Тодор качи решение на 10.01.2023 13:25 (преди над 2 години)

use std::collections::HashMap;
use std::io::{self, Read, Write};
-use crate::io::BufRead;
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: R,
output: &'a mut W,
lines: HashMap<u16, (&'a str, Vec<&'a str>)>,
variables: HashMap<String, u16>,
current_line: u16
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter {
input,
output,
lines: HashMap::new(),
variables: HashMap::new(),
current_line: 0
}
}
pub fn add(&mut self, code: &'a str) -> Result<(), InterpreterError> {
let mut tokens = code.split_whitespace();
let line_number: u16 = match tokens.next() {
Some(s) => match s.parse() {
Ok(n) => n,
Err(_) => return Err(InterpreterError::NotANumber { value: s.to_string() }),
},
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let command = match tokens.next() {
Some(s) => s,
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let params: Vec<&'a str> = tokens.collect();
if params.len() < 1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
self.lines.insert(line_number, (command, params));
Ok(())
}
}
impl<R: Read, W: Write> Interpreter<'_, R, W> {
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().next().map_or(false, char::is_uppercase) {
match self.variables.get(value) {
Some(n) => Ok(*n),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else {
match value.parse() {
Ok(n) => Ok(n),
Err(_) => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
}
}
-impl<R: Read + BufRead, W: Write> Interpreter<'_ ,R, W> {
+use std::io::Error as IoError;
+impl From<IoError> for InterpreterError {
+ fn from(e: IoError) -> InterpreterError {
+ InterpreterError::IoError(e)
+ }
+}
+
+impl<R: Read, W: Write> Interpreter<'_ ,R, W> {
fn execute_print(&mut self, value: &str) -> Result<(), InterpreterError> {
let output = if value.chars().next().unwrap().is_uppercase() {
match self.variables.get(value) {
Some(v) => v.to_string(),
None => return Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else if value.chars().next().unwrap().is_digit(10) {
value.to_string()
} else {
value.to_string()
};
write!(self.output, "{}\n", output).map_err(InterpreterError::IoError)
}
fn execute_read(&mut self, variable_name: &str) -> Result<(), InterpreterError> {
- let mut line = String::new();
- self.input.read_line(&mut line).map_err(InterpreterError::IoError)?;
+ let mut buffer = [0; 1];
+ let mut line = Vec::new();
+ while self.input.read(&mut buffer)? != 0 {
+ line.push(buffer[0]);
+ if buffer[0] == b'\n' {
+ break;
+ }
+ }
+ let line = String::from_utf8(line).map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
let value = line.trim_end().parse().map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
self.variables.insert(variable_name.to_string(), value);
Ok(())
}
-
+
fn execute_goto(&mut self, line_number: u16) -> Result<(), InterpreterError> {
if self.lines.contains_key(&line_number) {
self.current_line = line_number;
Ok(())
} else {
Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid line number".to_string() })
}
}
fn execute_if<'a>(&mut self, lhs: &'a str, op: &'a str, rhs: &'a str, line_number: u16) -> Result<(), InterpreterError> {
let lhs = self.eval_value(lhs)?;
let rhs = self.eval_value(rhs)?;
let should_goto = match op {
">" => lhs > rhs,
"<" => lhs < rhs,
"=" => lhs == rhs,
_ => return Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid operator".to_string() }),
};
if should_goto {
self.execute_goto(line_number)?;
}
Ok(())
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<u16> = self.lines.keys().cloned().collect();
keys.sort();
for key in keys {
self.current_line = key;
let (command, params) = match self.lines.get(&key) {
Some((c, p)) => (c, p),
None => return Err(InterpreterError::RuntimeError { line_number: key, message: "Line not found".to_string() }),
};
match *command {
"PRINT" => self.execute_print(params[0])?,
"READ" => self.execute_read(params[0])?,
"GOTO" => self.execute_goto(params[0].parse::<u16>().unwrap())?,
"IF" => self.execute_if(params[0], params[1], params[2], params[4].parse::<u16>().unwrap())?,
_ => return Err(InterpreterError::SyntaxError { code: command.to_string() }),
}
}
Ok(())
}
}
#[cfg(test)]
mod tests{
use super::*;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
let result = $expr;
if let $pat = result {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", result, stringify!($pat));
}
}
}
#[test]
pub fn test_if_command() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\n");
}
#[test]
pub fn test_if_command_with_variable() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\n");
}
}

Тодор качи решение на 10.01.2023 14:12 (преди над 2 години)

use std::collections::HashMap;
use std::io::{self, Read, Write};
#[derive(Debug)]
pub enum InterpreterError {
IoError(io::Error),
UnknownVariable { name: String },
NotANumber { value: String },
SyntaxError { code: String },
RuntimeError { line_number: u16, message: String },
}
pub struct Interpreter<'a, R: Read, W: Write> {
input: R,
output: &'a mut W,
lines: HashMap<u16, (&'a str, Vec<&'a str>)>,
variables: HashMap<String, u16>,
current_line: u16
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
Interpreter {
input,
output,
lines: HashMap::new(),
variables: HashMap::new(),
current_line: 0
}
}
pub fn add(&mut self, code: &'a str) -> Result<(), InterpreterError> {
let mut tokens = code.split_whitespace();
let line_number: u16 = match tokens.next() {
Some(s) => match s.parse() {
Ok(n) => n,
Err(_) => return Err(InterpreterError::NotANumber { value: s.to_string() }),
},
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let command = match tokens.next() {
Some(s) => s,
None => return Err(InterpreterError::SyntaxError { code: code.to_string() }),
};
let params: Vec<&'a str> = tokens.collect();
if params.len() < 1 {
return Err(InterpreterError::SyntaxError { code: code.to_string() });
}
self.lines.insert(line_number, (command, params));
Ok(())
}
}
impl<R: Read, W: Write> Interpreter<'_, R, W> {
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value.chars().next().map_or(false, char::is_uppercase) {
match self.variables.get(value) {
Some(n) => Ok(*n),
None => Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else {
match value.parse() {
Ok(n) => Ok(n),
Err(_) => Err(InterpreterError::NotANumber { value: value.to_string() }),
}
}
}
}
use std::io::Error as IoError;
impl From<IoError> for InterpreterError {
fn from(e: IoError) -> InterpreterError {
InterpreterError::IoError(e)
}
}
impl<R: Read, W: Write> Interpreter<'_ ,R, W> {
fn execute_print(&mut self, value: &str) -> Result<(), InterpreterError> {
let output = if value.chars().next().unwrap().is_uppercase() {
match self.variables.get(value) {
Some(v) => v.to_string(),
None => return Err(InterpreterError::UnknownVariable { name: value.to_string() }),
}
} else if value.chars().next().unwrap().is_digit(10) {
value.to_string()
} else {
value.to_string()
};
write!(self.output, "{}\n", output).map_err(InterpreterError::IoError)
}
fn execute_read(&mut self, variable_name: &str) -> Result<(), InterpreterError> {
let mut buffer = [0; 1];
let mut line = Vec::new();
while self.input.read(&mut buffer)? != 0 {
line.push(buffer[0]);
if buffer[0] == b'\n' {
break;
}
}
let line = String::from_utf8(line).map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
let value = line.trim_end().parse().map_err(|_| InterpreterError::RuntimeError { line_number: 0, message: "Invalid number".to_string() })?;
self.variables.insert(variable_name.to_string(), value);
Ok(())
}
fn execute_goto(&mut self, line_number: u16) -> Result<(), InterpreterError> {
if self.lines.contains_key(&line_number) {
self.current_line = line_number;
Ok(())
} else {
Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid line number".to_string() })
}
}
fn execute_if<'a>(&mut self, lhs: &'a str, op: &'a str, rhs: &'a str, line_number: u16) -> Result<(), InterpreterError> {
let lhs = self.eval_value(lhs)?;
let rhs = self.eval_value(rhs)?;
let should_goto = match op {
">" => lhs > rhs,
"<" => lhs < rhs,
"=" => lhs == rhs,
_ => return Err(InterpreterError::RuntimeError { line_number: self.current_line, message: "Invalid operator".to_string() }),
};
if should_goto {
self.execute_goto(line_number)?;
}
Ok(())
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut keys: Vec<u16> = self.lines.keys().cloned().collect();
keys.sort();
for key in keys {
self.current_line = key;
let (command, params) = match self.lines.get(&key) {
Some((c, p)) => (c, p),
None => return Err(InterpreterError::RuntimeError { line_number: key, message: "Line not found".to_string() }),
};
match *command {
"PRINT" => self.execute_print(params[0])?,
"READ" => self.execute_read(params[0])?,
"GOTO" => self.execute_goto(params[0].parse::<u16>().unwrap())?,
"IF" => self.execute_if(params[0], params[1], params[2], params[4].parse::<u16>().unwrap())?,
_ => return Err(InterpreterError::SyntaxError { code: command.to_string() }),
}
}
Ok(())
}
}
#[cfg(test)]
mod tests{
use super::*;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
let result = $expr;
if let $pat = result {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", result, stringify!($pat));
}
}
}
#[test]
pub fn test_if_command() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\n");
}
#[test]
pub fn test_if_command_with_variable() {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 20"), Ok(()));
assert_match!(interpreter.add("20 PRINT hello"), Ok(()));
assert_match!(interpreter.run(), Ok(()));
assert_eq!(output, b"hello\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();
+}
+
}