Читаем Программирование полностью

const string declkey = "let"; // ключевое слово let

Token Token_stream::get()

{

  if (full) { full=false; return buffer; }

    char ch;

    cin >> ch;

    switch (ch) {

    // как и прежде

    default:

    if (isalpha(ch)) {

      cin.putback(ch);

      string s;

      cin>>s;

      if (s == declkey) return Token(let); // ключевое слово let

      return Token(name,s);

    }

    error("Неправильная лексема");

  }

}

В первую очередь обратите внимание на вызов функции isalpha(ch). Этот вызов отвечает на вопрос “Является ли символ ch буквой?”; функция isalpha() принадлежит стандартной библиотеке и описана в заголовочном файле std_lib_facilities.h. Остальные функции классификации символов описаны в разделе 11.6. Логика распознавания имен совпадает с логикой распознавания чисел: находим первый символ соответствующего типа (в данном случае букву), а затем возвращаем его назад в поток с помощью функции putback() и считываем все имя целиком с помощью оператора >>.

К сожалению, этот код не компилируется; класс Token не может хранить строку, поэтому компилятор отказывается распознавать вызов Token(name,s). К счастью, эту проблему легко исправить, предусмотрев такую возможность в определении класса Token.

class Token {

public:

  char kind;

  double value;

  string name;

  Token(char ch):kind(ch), value(0) { }

  Token(char ch, double val) :kind(ch), value(val) { }

  Token(char ch, string n) :kind(ch), name(n) { }

};

Для представления лексемы let мы выбрали букву 'L', а само ключевое слово храним в виде строки. Очевидно, что это ключевое слово легко заменить ключевыми словами double, var, #, просто изменив содержимое строки declkey, с которой сравнивается строка s.

Попытаемся снова протестировать программу. Если напечатать следующие выражения, то легко убедиться, что программа работает:

let x = 3.4;

let y = 2;

x + y * 2;

Однако следующие выражения показывают, что программа еще не работает так, как надо:

let x = 3.4;

let y = 2;

x+y*2;

Чем различаются эти примеры? Посмотрим, что происходит. Проблема в том, что мы небрежно определили лексему Имя. Мы даже “забыли” включить правило вывода Имя в грамматику (раздел 7.8.1). Какие символы могут бы частью имени? Буквы? Конечно. Цифры? Разумеется, если с них не начинается имя. Символ подчеркивания? Нет? Символ +? Неужели?

Посмотрим на код еще раз. После первой буквы считываем строку в объект класса string с помощью оператора >>. Он считывает все символы, пока не встретит пробел. Так, например, строка x+y*2; является отдельным именем — даже завершающая точка с запятой считывается как часть имени. Это неправильно и неприемлемо.

Что же сделать вместо этого? Во-первых, мы должны точно определить, что представляет собой имя, а затем изменить функцию get(). Ниже приведено вполне разумное определение имени: последовательность букв и цифр, начинающаяся с буквы. Например, все перечисленные ниже строки являются именами.

a

ab

a1

Z12

asdsddsfdfdasfdsa434RTHTD12345dfdsa8fsd888fadsf

А следующие строки именами не являются:

1a

as_s

#

as*

a car

За исключением отброшенного символа подчеркивания это совпадает с правилом языка С++. Мы можем реализовать его в разделе default в функции get().

default:

  if (isalpha(ch)) {

    string s;

    s += ch;

    while (cin.get(ch) && (isalpha(ch) || isdigit(ch)))

      s+=ch;

    cin.putback(ch);

    if (s == declkey) return Token(let); // ключевое слово let

    return Token(name,s);

  }

  error("Неправильная лексема");

Вместо непосредственного считывания в объект string s считываем символ и записываем его в переменную s, если он является буквой или цифрой. Инструкция s+=ch добавляет (приписывает) символ ch в конец строки s. Любопытная инструкция

while (cin.get(ch) && (isalpha(ch) || isdigit(ch)) s+=ch;

считывает символ в переменную ch (используя функцию-член get() потока cin) и проверяет, является ли он символом или цифрой. Если да, то она добавляет символ ch в строку s и считывает символ снова. Функция-член get() работает как оператор >>, за исключением того, что не может по умолчанию пропускать пробелы.

<p id="AutBody_Root130"><strong>7.8.3. Предопределенные имена</strong></p>
Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже