Решение на Basic BASIC от Димитър Димитров
Към профила на Димитър Димитров
Резултати
- 20 точки от тестове
- 0 бонус точки
- 20 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap},
io::{self, BufRead, BufReader, Read, Write},
str::FromStr,
};
#[derive(Debug)]
struct StringLiteral(String);
impl FromStr for StringLiteral {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > 0 {
Ok(Self(s.to_owned()))
} else {
Err(())
}
}
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
struct Number(u16);
impl FromStr for Number {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u16>().map(|n| Self(n)).map_err(|_| ())
}
}
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
struct VariableName(String);
impl FromStr for VariableName {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with(|c: char| c.is_uppercase()) {
Ok(Self(s.to_owned()))
} else {
Err(())
}
}
}
#[derive(Debug)]
enum Value {
VariableName(VariableName),
Number(Number),
}
impl FromStr for Value {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
(s.parse::<Number>().map(Self::Number))
.or_else(|_| s.parse::<VariableName>().map(Self::VariableName))
}
}
// Понеже игнорираме синтактични грешки за аргументите на "IF".
// Реално е същото като "PrintValue", освен че стринговете запазваме като грешки.
#[derive(Debug)]
enum RuntimeValidatedValue {
Value(Value),
Invalid(StringLiteral),
}
impl FromStr for RuntimeValidatedValue {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<Value>()
.map(Self::Value)
.or_else(|_| s.parse::<StringLiteral>().map(Self::Invalid))
}
}
#[derive(Debug)]
enum PrintValue {
Value(Value),
StringLiteral(StringLiteral),
}
impl FromStr for PrintValue {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
(s.parse::<Value>().map(Self::Value))
.or_else(|_| s.parse::<StringLiteral>().map(Self::StringLiteral))
}
}
#[derive(Debug)]
struct Operator(Ordering);
impl FromStr for Operator {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"<" => Ok(Self(Ordering::Less)),
">" => Ok(Self(Ordering::Greater)),
"=" => Ok(Self(Ordering::Equal)),
_ => Err(()),
}
}
}
#[derive(Debug)]
enum Command {
Print(PrintValue),
Read(VariableName),
GoTo(Number),
If {
lhs: RuntimeValidatedValue,
op: Operator,
rhs: RuntimeValidatedValue,
jump_line_number: Number,
},
}
impl FromStr for Command {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_whitespace().collect::<Vec<_>>().split_first() {
Some((&"PRINT", rest)) if rest.len() == 1 => {
let print_value = rest[0].parse::<PrintValue>()?;
Ok(Self::Print(print_value))
}
Some((&"READ", rest)) if rest.len() == 1 => {
let variable_name = rest[0].parse::<VariableName>()?;
Ok(Self::Read(variable_name))
}
Some((&"GOTO", rest)) if rest.len() == 1 => {
let line_number = rest[0].parse::<Number>()?;
Ok(Self::GoTo(line_number))
}
Some((&"IF", rest)) if rest.len() == 5 && rest[3] == "GOTO" => {
let lhs = rest[0].parse::<RuntimeValidatedValue>()?;
let op = rest[1].parse::<Operator>()?;
let rhs = rest[2].parse::<RuntimeValidatedValue>()?;
let jump_line_number = rest[4].parse::<Number>()?;
Ok(Self::If {
lhs,
op,
rhs,
jump_line_number,
})
}
_ => Err(()),
}
}
}
#[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<'out, R: Read, W: Write> {
input: R,
output: &'out mut W,
commands: BTreeMap<Number, Command>,
variable_values: HashMap<VariableName, Number>,
}
impl<'out, R: Read, W: Write> Interpreter<'out, R, W> {
pub fn new(input: R, output: &'out mut W) -> Self {
let commands = BTreeMap::new();
let variable_values = HashMap::new();
Self {
input,
output,
commands,
variable_values,
}
}
}
impl<'out, R: Read, W: Write> Interpreter<'out, R, W> {
fn parse_line(line: &str) -> Result<(Number, Command), ()> {
let (token, rest) = line.split_once(|c: char| c.is_whitespace()).ok_or(())?;
let line_number = token.parse::<Number>()?;
let command = rest.parse::<Command>()?;
Ok((line_number, command))
}
pub fn add(&mut self, code: &str) -> Result<(), InterpreterError> {
Self::parse_line(code)
.map(|(line_number, command)| {
self.commands.insert(line_number, command);
})
.map_err(|_| InterpreterError::SyntaxError {
code: code.to_owned(),
})
}
}
impl<'out, R: Read, W: Write + 'out> Interpreter<'out, R, W> {
pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
match value.parse::<RuntimeValidatedValue>() {
Ok(miv) => Self::eval_runtime_validated_value(&self.variable_values, &miv),
Err(()) => Err(InterpreterError::NotANumber {
value: value.to_owned(),
}),
}
}
fn eval_valid_value(
variable_values: &HashMap<VariableName, Number>,
value: &Value,
) -> Result<u16, InterpreterError> {
match value {
Value::VariableName(variable_name) => variable_values
.get(&variable_name)
.map(|number| number.0)
.ok_or_else(|| InterpreterError::UnknownVariable {
name: variable_name.clone().0,
}),
Value::Number(number) => Ok(number.0),
}
}
fn eval_runtime_validated_value(
variable_values: &HashMap<VariableName, Number>,
runtime_validated_value: &RuntimeValidatedValue,
) -> Result<u16, InterpreterError> {
match runtime_validated_value {
RuntimeValidatedValue::Value(value) => Self::eval_valid_value(variable_values, value),
RuntimeValidatedValue::Invalid(string_literal) => Err(InterpreterError::NotANumber {
value: string_literal.0.to_owned(),
}),
}
}
fn eval_print_value(
variable_values: &HashMap<VariableName, Number>,
print_value: &PrintValue,
) -> Result<String, InterpreterError> {
match print_value {
PrintValue::Value(value) => {
Self::eval_valid_value(variable_values, &value).map(|number| number.to_string())
}
PrintValue::StringLiteral(StringLiteral(string)) => Ok(string.to_owned()),
}
}
}
impl InterpreterError {
fn jump_error(line_number: &Number, jump_line_number: &Number) -> InterpreterError {
InterpreterError::RuntimeError {
line_number: line_number.0,
message: format!(
"Cannot jump to line `{}` as it is not defined.",
jump_line_number.0
),
}
}
fn to_runtime_error(self, line_number: &Number) -> InterpreterError {
let message = match &self {
InterpreterError::RuntimeError { .. } => return self,
InterpreterError::UnknownVariable { name } => format!("No variable named `{}`.", name),
InterpreterError::NotANumber { value } => {
format!("Cannot interpret `{}` as a number", value)
}
InterpreterError::IoError(_) => panic!("Converting an IO error to runtime error."),
InterpreterError::SyntaxError { .. } => {
panic!("Converting a Syntax error to runtime error.")
}
};
InterpreterError::RuntimeError {
line_number: line_number.0,
message,
}
}
}
impl<'out, R: Read, W: Write> Interpreter<'out, R, W> {
pub fn run(&mut self) -> Result<(), InterpreterError> {
let mut command_iter = self.commands.range(Number(0)..);
let mut input_lines = BufReader::new(&mut self.input).lines();
while let Some((line_number, command)) = command_iter.next() {
match command {
Command::Print(print_value) => {
let to_print = Self::eval_print_value(&self.variable_values, print_value)
.map_err(|e| e.to_runtime_error(line_number))?;
writeln!(self.output, "{}", to_print).map_err(InterpreterError::IoError)?;
}
Command::Read(variable_name) => {
let token = input_lines
.next()
.unwrap_or(Ok(String::from("")))
.map_err(InterpreterError::IoError)?;
let value = token.parse::<Number>().map_err(|_| {
InterpreterError::NotANumber { value: token }.to_runtime_error(line_number)
})?;
self.variable_values.insert(variable_name.clone(), value);
}
Command::GoTo(jump_line_number) => {
if !self.commands.contains_key(jump_line_number) {
return Err(InterpreterError::jump_error(line_number, jump_line_number));
} else {
command_iter = self.commands.range(jump_line_number..)
}
}
Command::If {
lhs,
op,
rhs,
jump_line_number,
} => {
let lhs = Self::eval_runtime_validated_value(&self.variable_values, lhs)
.map_err(|e| e.to_runtime_error(line_number))?;
let rhs = Self::eval_runtime_validated_value(&self.variable_values, rhs)
.map_err(|e| e.to_runtime_error(line_number))?;
if lhs.cmp(&rhs) == op.0 {
if !self.commands.contains_key(jump_line_number) {
return Err(InterpreterError::jump_error(
line_number,
jump_line_number,
));
} else {
command_iter = self.commands.range(jump_line_number..)
}
}
}
}
}
Ok(())
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20230111-3772066-11t7p5v/solution) Finished test [unoptimized + debuginfo] target(s) in 1.69s 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_erroring_goto ... ok test solution_test::test_basic_read ... ok test solution_test::test_io_error_read ... ok test solution_test::test_full_program ... ok test solution_test::test_io_error_write ... ok test solution_test::test_line_order_and_overwriting ... ok test solution_test::test_print_cyrillic ... ok test solution_test::test_print_vars_and_strings ... ok test solution_test::test_syntax_errors_1 ... ok test solution_test::test_runtime_errors ... ok test solution_test::test_syntax_errors_2 ... ok test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s