Решение на Basic BASIC от Христо Жаблянов

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

Към профила на Христо Жаблянов

Резултати

  • 19 точки от тестове
  • 0 бонус точки
  • 19 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::{
collections::{BTreeMap, HashMap},
io::{self, Write, Read, BufRead, BufReader},
};
#[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: BufReader::<R>,
output: &'a mut W,
commands: BTreeMap<u16, Command>,
env: HashMap<String, u16>,
}
pub enum Operator {
Eq,
Gr,
Le,
}
pub enum Command {
Print(String),
Read(String),
GoTo(u16),
If {
left: String,
right: String,
op: Operator,
line: u16,
},
}
fn get_num<F>(s: Option<&str>, fallback: F) -> Result<u16, InterpreterError>
where
F: Fn() -> InterpreterError,
{
s.ok_or_else(&fallback)?.parse().ok().ok_or_else(fallback)
}
impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
return Interpreter {
input: BufReader::new(input),
output,
commands: BTreeMap::new(),
env: HashMap::new(),
};
}
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let mut iter = code.split_whitespace();
let fallback = || InterpreterError::SyntaxError {
code: String::from(code),
};
let id = get_num(iter.next(), fallback)?;
let cmd = match iter.next().ok_or_else(fallback)? {
"PRINT" => {
self.commands.insert(
id,
Command::Print(String::from(iter.next().ok_or_else(fallback)?)),
);
Ok(())
}
"READ" => {
let name = iter.next().ok_or_else(fallback)?;
if name
.chars()
.peekable()
.peek()
.map_or(true, |c| c.is_lowercase())
{
return Err(fallback());
}
self.commands.insert(id, Command::Read(String::from(name)));
Ok(())
}
"GOTO" => {
let line = get_num(iter.next(), fallback)?;
self.commands.insert(id, Command::GoTo(line));
Ok(())
}
"IF" => {
let left = String::from(iter.next().ok_or_else(fallback)?);
let op = match iter.next().ok_or_else(fallback)? {
"=" => Operator::Eq,
">" => Operator::Gr,
"<" => Operator::Le,
_ => return Err(fallback()),
};
let right = String::from(iter.next().ok_or_else(fallback)?);
iter.next();
let line = get_num(iter.next(), fallback)?;
self.commands.insert(
id,
Command::If {
left,
right,
op,
line,
},
);
Ok(())
}
_ => Err(fallback()),
};
if let Some(_) = iter.next() {
return Err(fallback());
}
cmd
}
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value
.chars()
.peekable()
.peek()
.map_or(false, |c| c.is_uppercase())
{
return self.env.get(value).map(|v| *v).ok_or_else(|| {
InterpreterError::UnknownVariable {
name: String::from(value),
}
});
}
get_num(Some(value), || InterpreterError::NotANumber {
value: String::from(value),
})
}
fn find_pos(&self, goal_line: u16, current_line: u16) -> Result<usize, InterpreterError> {
self.commands
.iter()
.position(|(&l, _)| l == goal_line)
.ok_or_else(|| InterpreterError::RuntimeError {
line_number: current_line,
message: String::from("No such line"),
})
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
if self.commands.is_empty() {
return Ok(());
}
let mut skip = 0;
'outer: loop {
let mut iter = self.commands.iter().skip(skip);
while let Some((&cur_line, c)) = iter.next() {
match c {
Command::Print(name) => match self.eval_value(name) {
Err(InterpreterError::UnknownVariable { .. }) => {
return Err(InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("No such variable"),
});
}
Ok(value) => writeln!(self.output, "{}", value)
.map_err(|e| InterpreterError::IoError(e))?,
Err(_) => writeln!(self.output, "{}", name)
.map_err(|e| InterpreterError::IoError(e))?,
},
Command::Read(name) => {
let mut value = String::new();
self.input
.read_line(&mut value)
.map_err(|e| InterpreterError::IoError(e))?;
let num = get_num(Some(value.trim()), || InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not parse number"),
})?;
self.env.insert(name.to_string(), num);
}
Command::GoTo(to) => {
skip = self.find_pos(*to, cur_line)?;
continue 'outer;
}
Command::If {
left,
right,
op,
line: to,
} => {
let left = self.eval_value(left).map_err(|e| match e {
InterpreterError::UnknownVariable { .. } => {
InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Unknown variable"),
}
}
InterpreterError::NotANumber { .. } => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Not a number"),
},
_ => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not evaluate left operand"),
},
})?;
let right = self.eval_value(right).map_err(|e| match e {
InterpreterError::UnknownVariable { .. } => {
InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Unknown variable"),
}
}
InterpreterError::NotANumber { .. } => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Not a number"),
},
_ => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not evaluate right operand"),
},
})?;
let jump = match op {
Operator::Eq => left == right,
Operator::Gr => left > right,
Operator::Le => left < right,
};
if jump {
skip = self.find_pos(*to, cur_line)?;
continue 'outer;
}
}
}
}
break Ok(());
}
}
}

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

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

running 15 tests
test solution_test::test_basic_goto ... ok
test solution_test::test_basic_if ... ok
test solution_test::test_basic_input ... ok
test solution_test::test_basic_print ... ok
test solution_test::test_basic_read ... ok
test solution_test::test_erroring_goto ... ok
test solution_test::test_full_program ... ok
test solution_test::test_io_error_read ... ok
test solution_test::test_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 ... FAILED
test solution_test::test_syntax_errors_2 ... ok

failures:

---- 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:260:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
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


failures:
    solution_test::test_syntax_errors_1

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

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

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

Христо качи първо решение на 24.12.2022 10:59 (преди над 2 години)

Напомням ей тоя ред от условието :)

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

Христо качи решение на 28.12.2022 15:24 (преди над 2 години)

use std::{
collections::{BTreeMap, HashMap},
- io::{self, BufRead, Write},
+ io::{self, Write, Read, BufRead, BufReader},
};
#[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: BufRead, W: Write> {
- input: R,
+pub struct Interpreter<'a, R: Read, W: Write> {
+ input: BufReader::<R>,
output: &'a mut W,
commands: BTreeMap<u16, Command>,
env: HashMap<String, u16>,
}
pub enum Operator {
Eq,
Gr,
Le,
}
pub enum Command {
Print(String),
Read(String),
GoTo(u16),
If {
left: String,
right: String,
op: Operator,
line: u16,
},
}
fn get_num<F>(s: Option<&str>, fallback: F) -> Result<u16, InterpreterError>
where
F: Fn() -> InterpreterError,
{
s.ok_or_else(&fallback)?.parse().ok().ok_or_else(fallback)
}
-impl<'a, R: BufRead, W: Write> Interpreter<'a, R, W> {
+impl<'a, R: Read, W: Write> Interpreter<'a, R, W> {
pub fn new(input: R, output: &'a mut W) -> Self {
return Interpreter {
- input,
+ input: BufReader::new(input),
output,
commands: BTreeMap::new(),
env: HashMap::new(),
};
}
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
let mut iter = code.split_whitespace();
let fallback = || InterpreterError::SyntaxError {
code: String::from(code),
};
let id = get_num(iter.next(), fallback)?;
let cmd = match iter.next().ok_or_else(fallback)? {
"PRINT" => {
self.commands.insert(
id,
Command::Print(String::from(iter.next().ok_or_else(fallback)?)),
);
Ok(())
}
"READ" => {
let name = iter.next().ok_or_else(fallback)?;
if name
.chars()
.peekable()
.peek()
.map_or(true, |c| c.is_lowercase())
{
return Err(fallback());
}
self.commands.insert(id, Command::Read(String::from(name)));
Ok(())
}
"GOTO" => {
let line = get_num(iter.next(), fallback)?;
self.commands.insert(id, Command::GoTo(line));
Ok(())
}
"IF" => {
let left = String::from(iter.next().ok_or_else(fallback)?);
let op = match iter.next().ok_or_else(fallback)? {
"=" => Operator::Eq,
">" => Operator::Gr,
"<" => Operator::Le,
_ => return Err(fallback()),
};
let right = String::from(iter.next().ok_or_else(fallback)?);
iter.next();
let line = get_num(iter.next(), fallback)?;
self.commands.insert(
id,
Command::If {
left,
right,
op,
line,
},
);
Ok(())
}
_ => Err(fallback()),
};
if let Some(_) = iter.next() {
return Err(fallback());
}
cmd
}
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
if value
.chars()
.peekable()
.peek()
.map_or(false, |c| c.is_uppercase())
{
return self.env.get(value).map(|v| *v).ok_or_else(|| {
InterpreterError::UnknownVariable {
name: String::from(value),
}
});
}
get_num(Some(value), || InterpreterError::NotANumber {
value: String::from(value),
})
}
fn find_pos(&self, goal_line: u16, current_line: u16) -> Result<usize, InterpreterError> {
self.commands
.iter()
.position(|(&l, _)| l == goal_line)
.ok_or_else(|| InterpreterError::RuntimeError {
line_number: current_line,
message: String::from("No such line"),
})
}
pub fn run(&mut self) -> Result<(), InterpreterError> {
if self.commands.is_empty() {
return Ok(());
}
let mut skip = 0;
'outer: loop {
let mut iter = self.commands.iter().skip(skip);
while let Some((&cur_line, c)) = iter.next() {
match c {
Command::Print(name) => match self.eval_value(name) {
Err(InterpreterError::UnknownVariable { .. }) => {
return Err(InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("No such variable"),
});
}
Ok(value) => writeln!(self.output, "{}", value)
.map_err(|e| InterpreterError::IoError(e))?,
Err(_) => writeln!(self.output, "{}", name)
.map_err(|e| InterpreterError::IoError(e))?,
},
Command::Read(name) => {
let mut value = String::new();
self.input
.read_line(&mut value)
.map_err(|e| InterpreterError::IoError(e))?;
let num = get_num(Some(value.trim()), || InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not parse number"),
})?;
self.env.insert(name.to_string(), num);
}
Command::GoTo(to) => {
skip = self.find_pos(*to, cur_line)?;
continue 'outer;
}
Command::If {
left,
right,
op,
line: to,
} => {
let left = self.eval_value(left).map_err(|e| match e {
InterpreterError::UnknownVariable { .. } => {
InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Unknown variable"),
}
}
InterpreterError::NotANumber { .. } => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Not a number"),
},
_ => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not evaluate left operand"),
},
})?;
let right = self.eval_value(right).map_err(|e| match e {
InterpreterError::UnknownVariable { .. } => {
InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Unknown variable"),
}
}
InterpreterError::NotANumber { .. } => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Not a number"),
},
_ => InterpreterError::RuntimeError {
line_number: cur_line,
message: String::from("Could not evaluate right operand"),
},
})?;
let jump = match op {
Operator::Eq => left == right,
Operator::Gr => left > right,
Operator::Le => left < right,
};
if jump {
skip = self.find_pos(*to, cur_line)?;
continue 'outer;
}
}
}
}
break Ok(());
}
}
}