Выбрать главу

Отличие от нашей грамматики заключается именно в том, что выражение 1–2–3 должно трактоваться как Выражение 1–2, за которым следует символ и Терм 3, а на самом деле функция интерпретирует выражение 1–2–3 как Терм 1, за которым следует символ и Выражение 2–3. Иначе говоря, мы хотели, чтобы выражение 1–2–3 было эквивалентно (1–2)–3 , а не 1–(2–3).

Да, отладка утомительна, скучна и требует много времени, но в данном случае мы действительно работаем с правилами, известными со школьной скамьи, и не должны испытывать больших затруднений. Проблема заключается лишь в том, чтобы научить этим правилам компьютер, а он учится намного медленнее нас.

Обратите внимание на то, что мы могли бы определить выражение 1–2–3 как 1–(2–3), а не (1–2)–3 и вообще избежать этой дискуссии. Довольно часто самые трудные программистские проблемы возникают тогда, когда мы работаем с привычными для людей правилами, которые изобрели задолго до компьютеров.

6.5.2.3. Выражения: третья попытка (удачная)

Итак, что теперь? Еще раз взгляните на грамматику (правильная грамматика приведена в разделе 6.5.2): любое Выражение начинается с Терма, за которым может следовать символ + или . Следовательно, мы должны найти Терм, проверить, следует ли за ним символ + или , и делать это, пока символы “плюс” и “минус” не закончатся. Рассмотрим пример.

double expression()

{

  double left = term();     // считываем и вычисляем Терм

  Token t = get_token();    // получаем следующую лексему

  while (t.kind=='+' || t.kind=='–') { // ищем + или –

    if (t.kind == '+')

      left += term();       // вычисляем Терм и добавляем его

    else

      left –= term();       // вычисляем Терм и вычитаем его

    t = get_token();

  }

  return left;              // финал: символов + и – нет; возвращаем ответ

}

Этот вариант немного сложнее: мы ввели цикл для поиска символов + и . Кроме того, дважды повторили проверку символов + и , а также дважды вызвали функцию get_token(). Поскольку это запутывает логику кода, просто продублируем проверку символов + и .

double expression()

{

  double left = term();  // считываем и вычисляем Терм

  Token t = get_token(); // получаем следующую лексему

  while(true) {

    switch(t.kind) {

    case '+':

      left += term();    // вычисляем Терм и добавляем его

      t = get_token();

      break;

    case '–':

      left –= term();    // вычисляем Терм и вычитаем его

      t = get_token();

      break;

    default:

      return left;       // финал: символов + и – нет;

                         // возвращаем ответ

    }

  }

}

Обратите внимание на то, что — за исключением цикла — этот вариант напоминает первый (см. раздел 6.5.2.1). Мы просто удалили вызов функции expression() в функции expression() и заменили его циклом. Другими словами, перевели Выражение в грамматическом правиле в цикл поиска Терма, за которым следует символ + или .

6.5.3. Термы

Грамматическое правило для Терма очень похоже на правило для Выражения.

Терм:

  Первичное выражение

  Терм '*' Первичное выражение

  Терм '/' Первичное выражение

  Терм '%' Первичное выражение

Следовательно, программный код также должен быть похож на код для Выражения. Вот как выглядит его первый вариант:

double term()

{

  double left = primary();

  Token t = get_token();

  while(true) {

    switch (t.kind) {

    case '*':

      left *= primary();

      t = get_token();

      break;

    case '/':

      left /= primary();

      t = get_token();

      break;

    case '%':

      left %= primary();

      t = get_token();

полную версию книги