Ввод из потока cin через поток Token_stream с именем ts.
*/
Здесь мы использовали блок комментариев, который начинается символами
/*
и заканчивается символами
*/
. В реальной программе история пересмотра может содержать сведения о том, какие именно изменения были внесены и какие улучшения были сделаны. Обратите внимание на то, что эти комментарии помещены за пределами кода. Фактически это несколько упрощенная грамматика: сравните правило для Инструкции с тем, что на самом деле происходит в программе (например, взгляните на код в следующем разделе). Этот комментарий ничего не говорит от цикле в функции
calculate
, позволяющем выполнять несколько вычислений в рамках одного сеанса работы программы. Мы вернемся к этой проблеме в разделе 7.8.1.
7.7. Исправление ошибок
Почему мы прекращаем работу программы, когда находим ошибку? В свое время это казалось простым и очевидным решением, но почему? Почему бы не вывести сообщение об ошибке и продолжить работу? Помимо всего прочего, мы часто делаем опечатки, и такие ошибки не означают, что мы решили не выполнять вычисления. Итак, попробуем исправить ошибки. Это по существу значит, что мы должны перехватить исключение и продолжить работу после исправления ошибки.
До сих пор все ошибки представлялись в виде исключений и обрабатывались функцией
main
. Если мы хотим исправить ошибку, то функция
calculate
должна перехватывать исключения и попытаться устранить неисправность прежде, чем приступить к вычислению следующего выражения.
void calculate
{
while (cin)
try {
cout << prompt;
Token t = ts.get;
while (t.kind == print) t=ts.get; // сначала
// игнорируем все
//
инструкции
"печать"
if (t.kind == quit) return;
ts.putback(t);
cout << result << expression << endl;
}
catch (exception& e) {
cerr << e.what << endl; //
выводим сообщение об ошибке
clean_up_mess;
}
}
Мы просто поместили цикл
while
в блоке
try
, который выводит сообщения об ошибке и устраняет неисправности. После этого работу можно продолжать по-прежнему. Что означает выражение “устранить неисправность”? В принципе готовность к выполнению вычислений после исправления ошибки означает, что все данные находятся в полном порядке и вполне предсказуемы. В калькуляторе единственные данные за пределами отдельных функций находятся в потоке
Token_stream
. Следовательно, мы должны убедиться, что в потоке нет лексем, связанных с прекращенными вычислениями и способных помешать будущим вычислениям.
Рассмотрим пример.
1++2*3; 4+5;
Эти выражения вызывают ошибку, и лексемы
2*3; 4+5
останутся в буферах потоков
Token_stream
и
cin
после того, как второй символ
+
породит исключение.
У нас есть две возможности.
1. Удалить все лексемы из потока
Token_stream
.
2. Удалить из потока все лексемы
Token_stream
, связанные с текущими вычислениями.
В первом случае отбрасываем все лексемы (включая
4+5;
), а во втором — отбрасываем только лексему
2*3
, оставляя лексему
4+5
для последующего вычисления. Один выбор является разумным, а второй может удивить пользователя. Обе альтернативы одинаково просто реализуются. Мы предпочли второй вариант, поскольку его проще протестировать. Он выглядит проще. Чтение лексем выполняется функцией
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
и выходим из программы. Ой! У нас теперь нет шансов вычислить лексему