Basic BASIC

Предадени решения

Краен срок:
10.01.2023 17:00
Точки:
20

Срокът за предаване на решения е отминал

use solution::*;
use std::io::{self, Read, Write};
// За тестване че някакъв резултат пасва на някакъв 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));
}
}
}
// За да избегнем infinite loops :/
macro_rules! timeout {
($time:expr, $body:block) => {
use std::panic::catch_unwind;
let (sender, receiver) = std::sync::mpsc::channel();
std::thread::spawn(move || {
if let Err(e) = catch_unwind(|| $body) {
sender.send(Err(e)).unwrap();
return;
}
match sender.send(Ok(())) {
Ok(()) => {}, // everything good
Err(_) => {}, // we have been released, don't panic
}
});
if let Err(any) = receiver.recv_timeout(std::time::Duration::from_millis($time)).unwrap() {
panic!("{}", any.downcast_ref::<String>().unwrap());
}
}
}
// За тестване на IO грешки:
struct ErroringReader {}
impl Read for ErroringReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "read error!"))
}
}
struct ErroringWriter {}
impl Write for ErroringWriter {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "Write Error!"))
}
fn flush(&mut self) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Other, "Flush Error!"))
}
}
#[test]
fn test_basic_print() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT 42").unwrap();
interpreter.add("20 PRINT 24").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "42\n24\n");
});
}
#[test]
fn test_line_order_and_overwriting() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("20 PRINT 2").unwrap();
interpreter.add("10 PRINT 1").unwrap();
interpreter.add("30 PRINT 3").unwrap();
interpreter.add("30 PRINT 4").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\n4\n");
});
}
#[test]
fn test_basic_input() {
timeout!(1000, {
let input: &[u8] = b"13\n";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ N").unwrap();
interpreter.add("20 PRINT N").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "13\n");
});
}
#[test]
fn test_basic_read() {
timeout!(1000, {
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 READ A").unwrap();
interpreter.add("40 PRINT A").unwrap();
interpreter.add("50 PRINT B").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "3\n2\n");
});
}
#[test]
fn test_print_vars_and_strings() {
timeout!(1000, {
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 Abc").unwrap();
interpreter.add("30 PRINT A").unwrap();
interpreter.add("40 PRINT Abc").unwrap();
interpreter.add("50 PRINT abc").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "1\n2\nabc\n");
});
}
#[test]
fn test_print_cyrillic() {
timeout!(1000, {
let input: &[u8] = b"37\n999\n";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Щ").unwrap();
interpreter.add("20 READ Юнак").unwrap();
interpreter.add("30 PRINT Щ").unwrap();
interpreter.add("40 PRINT Юнак").unwrap();
interpreter.add("50 PRINT евала").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "37\n999\nевала\n");
});
}
#[test]
fn test_basic_goto() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT 1").unwrap();
interpreter.add("20 GOTO 40").unwrap();
interpreter.add("30 PRINT 2").unwrap();
interpreter.add("40 PRINT 3").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "1\n3\n");
});
}
#[test]
fn test_erroring_goto() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT 1").unwrap();
interpreter.add("20 GOTO 999").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 20, .. }));
});
}
#[test]
fn test_basic_if() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 IF 2 > 1 GOTO 30").unwrap();
interpreter.add("20 PRINT 0").unwrap();
interpreter.add("30 PRINT 1").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "1\n");
});
}
#[test]
fn test_full_program() {
timeout!(1000, {
let input: &[u8] = b"13\n97\n42\n";
let mut output = Vec::<u8>::new();
{
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ Guess").unwrap();
interpreter.add("20 IF Guess > 42 GOTO 100").unwrap();
interpreter.add("30 IF Guess < 42 GOTO 200").unwrap();
interpreter.add("40 IF Guess = 42 GOTO 300").unwrap();
interpreter.add("100 PRINT too_high").unwrap();
interpreter.add("110 GOTO 10").unwrap();
interpreter.add("200 PRINT too_low").unwrap();
interpreter.add("210 GOTO 10").unwrap();
interpreter.add("300 PRINT you_got_it!").unwrap();
interpreter.run().unwrap();
}
assert_eq!(String::from_utf8(output).unwrap(), "too_low\ntoo_high\nyou_got_it!\n");
});
}
#[test]
fn test_syntax_errors_1() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 READ guess"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 READ 13"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 READ ..."), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 GOTO 123456789"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 GOTO -3"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 GOTO some_line"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 IF 2 > 1 GOTO 987654321"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 IF 1 < 2 GOTO -17"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 IF 3 = 3 GOTO other_line"), Err(InterpreterError::SyntaxError { .. }));
});
}
#[test]
fn test_syntax_errors_2() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
assert_match!(interpreter.add("10 PRINT A B"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 PRINT"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("PRINT 13"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 READ A B"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 READ"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("READ A"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 GOTO 1 2"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 GOTO"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("GOTO 100"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 IF 2 > 1 GOTO"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 IF 1 < 2"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("10 3 = 3 GOTO 10"), Err(InterpreterError::SyntaxError { .. }));
assert_match!(interpreter.add("IF 3 = 3 GOTO 10"), Err(InterpreterError::SyntaxError { .. }));
});
}
#[test]
fn test_runtime_errors() {
timeout!(1000, {
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT 13").unwrap();
interpreter.add("11 PRINT A").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 11, .. }));
let input: &[u8] = b"1\nfoobar\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("30 READ A").unwrap();
interpreter.add("40 READ B").unwrap();
interpreter.add("50 READ C").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 40, .. }));
let input: &[u8] = b"";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 GOTO 123").unwrap();
interpreter.add("123 GOTO 1000").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 123, .. }));
let input: &[u8] = b"1\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("11 READ A").unwrap();
interpreter.add("22 READ B").unwrap();
interpreter.add("33 IF A = C GOTO 1000").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 33, .. }));
let input: &[u8] = b"2\n2\n";
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("11 READ A").unwrap();
interpreter.add("22 READ B").unwrap();
interpreter.add("33 IF A = B GOTO 1000").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::RuntimeError { line_number: 33, .. }));
});
}
#[test]
fn test_io_error_read() {
timeout!(1000, {
let input = ErroringReader {};
let mut output = Vec::<u8>::new();
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 PRINT this_is_fine").unwrap();
interpreter.add("20 READ Oh_No").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::IoError(_)));
});
}
#[test]
fn test_io_error_write() {
timeout!(1000, {
let input: &[u8] = b"13\n";
let mut output = ErroringWriter {};
let mut interpreter = Interpreter::new(input, &mut output);
interpreter.add("10 READ ThisIsFine").unwrap();
interpreter.add("20 PRINT ThisIsFine").unwrap();
assert_match!(interpreter.run(), Err(InterpreterError::IoError(_)));
});
}

От носталгия за по-стари, по-прости езици, ще имплементираме наш си интерпретатор за нещо като езика BASIC. Ще искаме една ей такава игричка тип "познай числото" да сработи:

10 PRINT guess_a_number
20 READ Guess
30 IF Guess > 42 GOTO 100
40 IF Guess < 42 GOTO 200
50 IF Guess = 42 GOTO 300
100 PRINT too_high
110 GOTO 10
200 PRINT too_low
210 GOTO 10
300 PRINT you_got_it!

Ах, този GOTO, an elegant weapon for a more civilized age! Все едно съм на стария Правец-8C... Kinda. Някои неща може да изглеждат малко странни, но ще караме стъпка по стъпка и ще ги доизясним.

Базови структури

Ще започнем със структурата за интерпретатора и възможните му грешки (които ще доизясняваме после):

use std::io::{self, Write, Read};

#[derive(Debug)]
pub enum InterpreterError {
    IoError(io::Error),
    UnknownVariable { name: String },
    NotANumber { value: String },
    SyntaxError { code: String },
    RuntimeError { line_number: u16, message: String },
}

// ВАЖНО: тук ще трябва да се добави lifetime анотация!
pub struct Interpreter<R: Read, W: Write> {
    // Тези полета не са публични, така че може да си ги промените, ако искате:
    input: R,
    output: &mut W,
    // Каквито други полета ви трябват
}

impl<R: Read, W: Write> Interpreter<R, W> {
    /// Конструира нов интерпретатор, който чете вход от `input` чрез `READ` командата и пише в
    /// `output` чрез `PRINT`.
    ///
    pub fn new(input: R, output: &mut W) -> Self {
        todo!()
    }
}

Има едно важно нещо, което трябва да се отбележи тук -- както е даден, горния код запазва &mut W, в структурата Interpreter и съответно тя трябва да има lifetime анотация. Това не би трябвало да е особено трудно, но все пак трябва да го направите, иначе няма да ви се компилира дори и базовия тест. You have been warned.

Навсякъде надолу, където пишем impl<R: Read, W: Write> Interpreter<R, W> {, приемете, че трябва да промените декларацията с lifetimes, както сте я променили тук.

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

Добавяне и парсене на редове

// Не забравяйте lifetime анотацията
impl<R: Read, W: Write> Interpreter<R, W> {
    /// Тази функция добавя ред код към интерпретатора. Този ред се очаква да започва с 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> {
        todo!()
    }
}

Има 4 команди, които ще искаме да имплементирате. Караме една по една:

  • PRINT <стойност>: При изпълнение ще напечата дадената стойност и нов ред (символ \n). Не е нужно да обработвате стойността по време на добавяне -- може да я запазите като низ за интерпретация после. (Ако искате да добавите допълнителна валидация, няма да попречи -- ще подаваме на PRINT само синтактично-валидни стойности в тестовете)

  • READ <име на променлива>: При изпълнение, ще прочете точно един ред, премахвайки завършващия \n символ. Името на променливата трябва да е низ, който започва с главна буква (char::is_uppercase), иначе тази команда е невалидна и очакваме да върнете InterpreterError::SyntaxError. (Да, низа може да е на кирилица, ще тестваме с примерно READ Щ)

  • GOTO <номер на ред>: При изпълнение, променя следващия ред за изпълнение на програмата да бъде дадения ред. Реда трябва да е валидно u16 число, иначе очакваме да върнете InterpreterError::SyntaxError.

  • IF <стойност> <оператор> <стойност> GOTO <номер на ред>: Най-сложната операция, която ще поддържаме. По време на добавяне на реда, може да си запазите <стойност> <оператор> <стойност> частта както ви е удобно, няма нужда да валидирате какви точно неща има вътре. Номера на ред трябва да е валидно u16 число, иначе очакваме да върнете InterpreterError::SyntaxError.

За всички тези команди, може да очаквате, че всеки един от параметрите ще бъде разделен с точно един интервал. Свободни сте да ги разбиете със .split_whitespace или по един интервал, ваш избор. Винаги ще използваме големи букви за командите, точно както ги даваме в инструкциите и в примерите.

Всички аргументи са задължителни, също и номер на всеки ред -- ако липсва аргумент, това е InterpreterError::SyntaxError, също ако има твърде много. Примерно, тези редове са синтактично грешни:

10 PRINT A B
20 IF 13 > 18 GOTO
READ X
30

Ред 10 има твърде много компоненти, а на ред 20 му липсват. Ред 30 е напълно празен, което също е невалидно. Между 20 и 30 има ред без номер, което е невалидно. Празен низ също не е валиден ред.

В нашите тестове ще пробваме само и единствено тези 4 команди, така че няма нужда да валидирате неизвестни такива, но можете, ако искате. Можете и да добавите ваши, в случай че усетите изблик на ентусиазъм, който може да бъде потушен само с имплементация на АКО 3 > 2 ХОП 40. Само внимавайте да не счупите тези, които искаме :) (hint: тестове помагат много)

Когато добавяте ред, може да го запазите като низ, като вектор, като някаква ваша структура -- както го измислите. Редовете са важни, защото както виждате, има команда за скок на ред. Не е необходимо да се вкарват поред -- ако напишете програма, в която първо слагате ред 20, после ред 10, тя ще се изпълни от 10 към 20. Можете да добавите редове с един и същ номер, които ще се презапишат. Тоест:

3 PRINT 1
2 PRINT 2
1 PRINT 3
1 PRINT 4

Втория ред 1 ще презапише първия ред 1, и по време на изпълнение, програмата ще се пренареди. Тоест, горната програма е еквивалентна на:

1 PRINT 4
2 PRINT 2
3 PRINT 1

Както забелязвате, не е нужно да са през 10 :). Причината повечето примери да са през 10 е защото едно време така се правеше, за да има дупки между редовете да сложим код, който сме забравили 😅.

Изпълнение на програмата

След като сме добавили всички редове на програмата, време е да я изпълним. Тук ще трябва да се занимаваме с променливи и вход и изход. Първо, нека да имплементираме една helper функция, която да оцени някой низ като стойност, базирано на специфични правила:

// Не забравяйте lifetime анотацията
impl<R: Read, W: Write> Interpreter<R, W> {
    /// Оценява `value` като стойност в контекста на интерпретатора:
    ///
    /// - Ако `value` е низ, който започва с главна буква (съгласно `char::is_uppercase`), търсим
    ///   дефинирана променлива с това име и връщаме нейната стойност.
    ///   -> Ако няма такава, връщаме `InterpreterError::UnknownVariable` с това име.
    /// - Ако `value` е валидно u16 число, връщаме числото
    ///   -> Иначе, връщаме `InterpreterError::NotANumber` с тази стойност
    ///
    pub fn eval_value(&self, value: &str) -> Result<u16, InterpreterError> {
        todo!()
    }
}

Аргумента на PRINT ще работи по горния начин, с една добавка. Стойностите в условието на IF-клаузите също ще се оценяват по този начин -- число или променлива, която вади число.

Записването на стойност на променлива става с командата READ, която чете стойността от input. Ако запишем втори път същата променлива, просто се презаписва. Няма да си кривим душата -- перфектен use case е за HashMap или BTreeMap, смело си харесайте една от тези структури за да си пазите променливите. Ключовете ще са низове, а стойностите ще са u16. Освен началната главна буква, ще тестваме само с други букви (малки и големи, включително кирилица), така че дали и каква валидация ще правите е ваша работа.

Ето как изглежда run функцията:

// Не забравяйте lifetime анотацията
impl<R: Read, W: Write> Interpreter<R, W> {
    /// Функцията започва да изпълнява редовете на програмата в нарастващ ред. Ако стигне до GOTO,
    /// скача на съответния ред и продължава от него в нарастващ ред. Когато вече няма ред с
    /// по-голямо число, функцията свършва и връща `Ok(())`.
    ///
    /// Вижте по-долу за отделните команди и какви грешки могат да върнат.
    ///
    pub fn run(&mut self) -> Result<(), InterpreterError> {
        todo!()
    }
}

Ако в който и да е момент операция по четене от input или писане в output върне грешка, това ще е std::io::Error и очакваме да я пакетирате в InterpreterError::IoError и да я върнете. Вероятно ще е удобно да използвате eval_value, но забележете, че грешките оттам трябва да се преведат до RuntimeError.

Всички InterpreterError::RuntimeError очакваме в line_number да съдържат конкретния номер на ред, където се е счупила програмата, и каквото съобщение си искате в message.

Една по една, какво прави изпълнението на 4те команди:

  • PRINT <стойност>: записва дадената стойност и нов ред (символ \n) в output-а. Ако стойността започва с голяма буква (спрямо char::is_uppercase), третирайте я като променлива и върнете InterpreterError::RuntimeError, ако няма такава дефинирана. Ако стойността е число, просто го напечатайте, но тук имаме трети случай -- ако е низ, който започва с малка буква, запишете низа в output. Така можем да пишем числа, идващи от входа, но също и нормални низови съобщения (без да ви караме да имплементирате парсене на кавички, hope you appreciate it).

  • READ <име на променлива>: Прочита от input точно един ред, премахвайки завършващия \n. Опитва се да прочете u16 от него, но ако не сработи, очакваме InterpreterError::RuntimeError. Както отбелязахме по-горе, това записва променлива с даденото име, като я презаписва, ако вече съществува.

  • GOTO <номер на ред>: Променя следващия ред за изпълнение на програмата да бъде дадения номер на ред. Ако такъв номер на ред не съществува, очакваме да върнете InterpreterError::RuntimeError с номера на реда на самата команда, а не реда, към който се скача.

  • IF <стойност> <оператор> <стойност> GOTO <номер на ред>: Частта, която включва GOTO <номер на ред> работи по абсолютно същия начин като по-горе откъм грешки и функционалност. GOTO-то се изпълнява (и валидира за грешки) само ако условието на IF-клаузата е истина.

    Условието се състои от три части, две стойности и оператор между тях. Операторите, които ще подаваме в тестове, ще са само тези три: <, >, и = за равенство.

    Двете стойности се оценяват както е обяснено в eval_value -- променливи с главна буква или u16 числа. Както указахме по-горе, превеждате всякакви грешки до RuntimeError. Тоест, валидни IF-клаузи могат да са, примерно:

    • IF 2 > 1 GOTO 13 -- скача на ред 13
    • IF 2 < 1 GOTO 31 -- не скача на подадения ред, продължава си на следващия
    • IF 3 = 3 GOTO 33 -- скача на ред 33

В тестовете ще викаме run само по веднъж на всеки интерпретатор (макар че за целите на теста, ще викаме eval_value след run), така че е ваш избор какво да се случи ако се извика втори път -- може да се занулят променливите и да се почне отначало, може да се resume-не от последния ред, който се е счупил, ние няма да проверяваме този case.

(Hint: Предвид, че консистентно връщате RuntimeError, може би си заслужава да си направите някакво помощно средство за да ги инстанцирате лесно в контекста на run.)

Ако няма програма за изпълнение, функцията run просто не прави нищо и връща успешен резултат.

Допълнителни бележки

Ако ви изглежда като твърде много работа, мисля че ще се изненадате -- просто карайте команда по команда, пишете си по някой и друг тест от примерите, които ви дадохме, и ще се оправите. Спокойно можете да си направите частично решение като имплементирате само PRINT и в други случаи оставите todo!(), после имплементирате READ и т.н..

Искате да си тествате играта от по-горе с истински вход и изход?

let stdin = std::io::stdin();
let mut stdout = std::io::stdout();

let mut interpreter = Interpreter::new(stdin, &mut stdout);
interpreter.add("10 PRINT guess_a_number").unwrap();
// interpreter.add(...
interpreter.run().unwrap();

Иначе, ако ви е любопитно да видите как изглежда пълен вариант на езика, може да цъкнете на тези линкове:

Нашия интерпретатор не е баш като тези, така че не приемайте официалната документация на Applesoft Basic като канонична за домашното -- следвайте си инструкциите по-горе.

Задължително прочетете (или си припомнете): Указания за предаване на домашни

Погрижете се решението ви да се компилира с базовия тест:

use solution::*;
// За тестване че някакъв резултат пасва на някакъв 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\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();
}