В данном случае необходимо внести исправления в грамматику, чтобы предусмотреть унарный минус. На первый взгляд легче всего внести исправления в пункт Первичное выражение. Сейчас он выглядит так:
Первичное выражение:
Число
"("Выражение")"
Нам требуется, чтобы этот пункт выглядел примерно таким образом:
Первичное выражение:
Число
"("Выражение")"
"–" Первичное выражение
"+" Первичное выражение
Мы добавили унарный плюс, поскольку он есть в языке С++. Если есть унарный минус, то легче реализовать унарный плюс, чем объяснить его бесполезность. Код, реализующий Первичное выражение, принимает следующий вид:
double primary()
{
Token t = ts.get();
switch (t.kind) {
case '(': // обработка пункта '(' выражение ')'
{
double d = expression();
t = ts.get();
if (t.kind != ')') error("')' expected");
return d;
}
case '8': // символ '8' используется для представления числа
return t.value; // возвращаем число
case '–':
return – primary();
case '+':
return primary();
default:
error("ожидается первичное выражение");
}
}
Этот код настолько прост, что работает с первого раза.
7.5. Остаток от деления: %
Обдумывая проект калькулятора, мы хотели, чтобы он вычислял остаток от деления — оператор %
. Однако этот оператор не определен для чисел с плавающей точкой, поэтому мы отказались от этой идеи. Настало время вернуться к ней снова.
Это должно быть простым делом.
1. Добавляем символ % как Token.
2. Преобразовываем число типа double
в тип int
, чтобы впоследствии применить к нему оператор %
.
Вот как изменится код функции term()
:
case '%':
{ double d = primary();
int i1 = int(left);
int i2 = int(d);
return i1%i2;
}
Для преобразования чисел типа double
в числа типа int
проще всего использовать явное выражение int(d)
, т.е. отбросить дробную часть числа. Несмотря на то что это избыточно (см. раздел 3.9.2), мы предпочитаем явно указать, что знаем о произошедшем преобразовании, т.е. избегаем непреднамеренного или неявного преобразования чисел типа double
в числа типа int
. Теперь получим правильные результаты для целочисленных операндов. Рассмотрим пример.
> 2%3;
= 0
> 3%2;
= 1
> 5%3;
= 2
Как обработать операнды, которые не являются целыми числами? Каким должен быть результат следующего выражения:
> 6.7%3.3;
Это выражение не имеет корректного результата, поэтому запрещаем применение оператора %
к аргументам с десятичной точкой. Проверяем, имеет ли аргумент дробную часть, и в случае положительного ответа выводим сообщение об ошибке.
Вот как выглядит результат функции term()
:
double term()
{
double left = primary();
Token t = ts.get(); // получаем следующую лексему
// из потока Token_stream
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;
}
case '%':
{ double d = primary();
int i1 = int(left);
if (i1 != left)
error ("Левый операнд % не целое число");
int i2 = int(d);
if (i2 != d) error ("Правый операнд % не целое число");
if (i2 == 0) error("%: деление на нуль");
left = i1%i2;
t = ts.get();
break;
}
default:
ts.putback(t); // возвращаем t обратно в поток
// Token_stream
return left;
}
}
}
Здесь мы лишь проверяем, изменилось ли число при преобразовании типа double
в тип int
. Если нет, то можно применять оператор %
. Проблема проверки целочисленных операндов перед использованием оператора %
— это вариант проблемы сужения (см. разделы 3.9.2 и 5.6.4), поэтому ее можно решить с помощью оператора narrow_cast
.
case '%':
{ int i1 = narrow_cast
int i2 = narrow_cast
if (i2 == 0) error("%: деление на нуль");
left = i1%i2;
t = ts.get();