легко; эту операцию выполняет функция-член вектора
push_back
.
var_table.push_back(Variable(var,val));
Вызов конструктора
Variable(var,val)
создает соответствующий объект класса
Variable
, а затем функция
push_back
добавляет этот объект в конец вектора
var_table
. В этих условиях и с учетом лексем
let
и
name
функция
declaration
становится вполне очевидной.
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)
. К счастью, эту проблему легко исправить, предусмотрев такую возможность в определении класса
, а само ключевое слово храним в виде строки. Очевидно, что это ключевое слово легко заменить ключевыми словами
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
. Ниже приведено вполне разумное определение имени: последовательность букв и цифр, начинающаяся с буквы. Например, все перечисленные ниже строки являются именами.