Шрифт:
Модифицируем функцию
expression
так, чтобы она не “съедала” лексемы. Куда поместить следующую лексему (t
), если программа никак не использует ее? Можно рассмотреть много сложных схем, но давайте просто перейдем к очевидному ответу (его очевидность станет ясной позднее): поскольку лексема будет использована другой функцией, которая будет считывать ее из потока ввода, давайте вернем лексему обратно в поток ввода, чтобы ее могла считать другая функция! Действительно, мы можем вернуть символ обратно в поток ввода, но это не совсем то, что мы хотим. Мы хотим работать с лексемами, а не возиться с символами. Итак, хотелось бы, чтобы поток ввода работал с лексемам, а мы имели бы возможность записывать в него уже считанные лексемы. Предположим, в нашем распоряжении есть поток лексем — “
Token_stream
” — с именем ts
. Допустим также, что поток Token_stream
имеет функцию-член get
, возвращающую следующую лексему, и функцию-член putback(t)
, возвращающую лексему t
обратно в поток. Мы реализуем класс
Token_stream
в разделе 6.8, как только увидим, как его следует использовать. Имея поток Token_stream
, можем переписать функцию expression
так, чтобы она записывала неиспользованную лексему обратно в поток Token_stream
.
double expression
{
double left = term; // считываем и вычисляем Терм
Token t = ts.get; // получаем следующую лексему
// из потока лексем
while(true) {
switch(t.kind) {
case '+':
left += term; // вычисляем и добавляем Терм
t = ts.get;
break;
case '–':
left –= term; // вычисляем и вычитаем Терм
t = ts.get;
break;
default:
ts.putback(t); // помещаем объект t обратно
// в поток лексем
return left; // финал: символов + и – нет;
// возвращаем ответ
}
}
}
Кроме того, такие же изменения следует внести в функцию
term
.
double term
{
double left = primary;
Token t = ts.get; // получаем следующую лексему
// из потока лексем
while(true) {
switch (t.kind) {
case '*':
left *= primary;
t = ts.get;
break;
case '/':
{
double d = primary;
if (d == 0) error("деление на нуль");
left /= d;
t = ts.get;
break;
}
default:
ts.putback(t); // помещаем объект t обратно в поток лексем
return left;
}
}
}
Для последней функции программы грамматического анализа
primary
достаточно заменить функцию get_token
функцией ts.get
; функция primary
использует каждую лексему, которую она считывает. 6.7. Испытание второй версии
Итак, мы готовы к испытанию второй версии. Введем число
2
и символ перехода на новую строку. Нет ответа. Попробуйте ввести еще один символ перехода на новую строку, чтобы убедиться, что компьютер не завис. По-прежнему нет ответа. Введите число 3
и символ перехода на новую строку. Ответ равен 2
. Попробуйте ввести выражение 2+2
и символ перехода на новую строку. Ответ равен 3. Экран выглядит следующим образом:
2
3
=2
2+2
=3
Хм... Может быть, наша функция
putback
и ее использование в функции expression
и term
не решает проблему. Попробуем другой тест.
2 3 4 2+3 2*3
= 2
= 3
= 4
= 5
Да! Это правильные ответы! Но последний ответ (
6
) пропущен. Проблема следующей лексемы не решена. Однако на этот раз она заключается не в том, что наш программный код “съедает” символы, а в том, что он вообще не получает информации, пока не будет введено следующее выражение. Результат вычисления выражения не выводится на экран немедленно; он откладывается до тех пор, пока программа не увидит первую лексему следующего выражения. К сожалению, программа не видит эту лексему, пока мы не нажмем клавишу <Enter> после следующего выражения. Эта программа на самом деле не настолько плоха, она просто немного медленно реагирует.