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()
работает как оператор >>
, за исключением того, что не может по умолчанию пропускать пробелы.
7.8.3. Предопределенные имена