для последующего вычисления. Один выбор является разумным, а второй может удивить пользователя. Обе альтернативы одинаково просто реализуются. Мы предпочли второй вариант, поскольку его проще протестировать. Он выглядит проще. Чтение лексем выполняется функцией
get
, поэтому можно написать функцию
clean_up_mess
, имеющую примерно такой вид:
void clean_up_mess // наивно
{
while (true) { // пропускаем,
// пока не обнаружим инструкцию "печать"
Token t = ts.get;
if (t.kind == print) return;
}
}
К сожалению, эта функция не всегда работает хорошо. Почему? Рассмотрим следующий вариант:
1@z; 1+3;
Символ
@
приводит нас к разделу
catch
в цикле
while
. Тогда для выявления следующей точки с запятой вызываем функцию
clean_up_mess
. Функция
clean_up_mess
вызывает функцию
get
и считывает символ
z
. Это порождает следующую ошибку (поскольку символ
z
не является лексемой), и мы снова оказываемся в блоке
catch
внутри функции
main
и выходим из программы. Ой! У нас теперь нет шансов вычислить лексему
1+3
. Вернитесь к меловой доске!
Можно было бы уточнить содержание блоков
try
и
catch
, но это внесет в программу еще большую путаницу. Ошибки в принципе трудно обрабатывать, а ошибки, возникающие при обработке других ошибок, обрабатывать еще труднее. Поэтому стоит попытаться найти способ удалять из потока
Token_stream
символы, которые могут породить исключение. Единственный путь для ввода данных в калькулятор пролегает через функцию
get
, и он может, как мы только что выяснили, порождать исключения. Таким образом, необходима новая операция. Очевидно, что ее целесообразно поместить в класс
Token_stream
.
class Token_stream {
public:
Token_stream; // создает поток Token_stream,
считывающий
// данные из потока cin
Token get; // считывает лексему
void putback(Token t); // возвращает лексему
void ignore(char c); // отбрасывает символы,
// предшествующие символу с включительно
private:
bool full; // есть лексема в буфере?
Token buffer; // здесь хранится лексема, которая возвращается
// назад с помощью функции putback
};
Функция
ignore
должна быть членом класса
Token_stream
, так как она должна иметь доступ к его буферу. Мы выбрали в качестве искомого символа аргумент функции
ignore
. Помимо всего прочего, объект класса
Token_stream
не обязан знать, что калькулятор считает хорошим символом для исправления ошибок. Мы решили, что этот аргумент должен быть символом, потому что не хотим рисковать, работая с составными лексемами (мы уже видели, что при этом происходит). Итак, мы получаем следующую функцию:
void Token_stream::ignore(char c)
// символ c обозначает разновидность лексем
{
// сначала проверяем буфер:
if (full && c==buffer.kind) {
full = false;
return;
}
full = false;
// теперь проверяем входные данные:
char ch = 0;
while (cin>>ch)
if (ch==c) return;
}
В этом коде сначала происходит проверка буфера. Если в буфере есть символ
c
, прекращаем работу, отбрасывая этот символ
c
; в противном случае необходимо считывать символы из потока
cin
, пока не встретится символ
c
. Теперь функцию
clean_up_mess
можно написать следующим образом:
void clean_up_mess
{
ts.ignore(print);
}
Обработка ошибок всегда является сложной. Она требует постоянного экспериментирования и тестирования, поскольку крайне трудно представить заранее, какая ошибка может возникнуть в ходе выполнения программы. Защита программы от неправильного использования всегда представляет собой очень сложную задачу. Дилетанты об этом никогда не беспокоятся. Качественная обработка ошибок — один из признаков профессионализма.