double declaration()
// предполагается, что мы можем выделить ключевое слово "let"
// обработка: name = выражение
// объявляется переменная с именем "name" с начальным значением,
// заданным "выражением"
{
Token t = ts.get();
if (t.kind != name) error ("в объявлении ожидается переменная
name");
string var_name = t.name;
Token t2 = ts.get();
if (t2.kind != '=') error("в объявлении пропущен символ =",
var_name);
double d = expression();
define_name(var_name,d);
return d;
}
Обратите внимание на то, что мы возвращаем значение, хранящееся в новой переменной. Это полезно, когда инициализирующее выражение является нетривиальным. Рассмотрим пример.
let v = d/(t2–t1);
Это объявление определяет переменную
v
и выводит ее значение. Кроме того, печать переменной упрощает код функции
calculate()
, поскольку при каждом вызове функция
statement()
возвращает значение. Как правило, общие правила позволяют сохранить простоту кода, а специальные варианты приводят к усложнениям.
Описанный механизм отслеживания переменных часто называют таблицей символов (symbol tables). Его можно радикально упростить с помощью стандартной библиотеки
map
(см. раздел 21.6.1).
7.8.2. Использование имен
Все это очень хорошо, но, к сожалению, не работает. Это не должно было стать для нас сюрпризом. Первый вариант никогда — почти никогда — не работает. В данном случае мы даже не закончили программу — она даже не скомпилируется. У нас нет лексемы
'='
, но это легко исправить, добавив дополнительный раздел
case
в функцию
Token_stream::get()
(см. раздел 7.6.3). А как представить ключевые слова
let
и
name
в виде лексем? Очевидно, для того чтобы распознавать эти лексемы, необходимо модифицировать функцию
get()
. Как? Вот один из способов.
const char name = 'a'; // лексема name
const char let = 'L'; // лексема let
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;