Теперь необходимо написать функцию declaration()
. Что следует сделать? Нужно убедиться, что после ключевого слова let
следует Имя, а за ним — символ = и Выражение. Именно это утверждает грамматика. Что делать с членом name
? Мы должны добавить в вектор var_table
типа vector
объект класса Variable
c заданными строкой name и значением выражения. После этого мы сможем извлекать значения с помощью функции get_value()
и изменять их с помощью функции set_value()
. Однако сначала надо решить, что случится, если мы определим переменную дважды. Рассмотрим пример.
let v1 = 7;
let v1 = 8;
Мы решили, что повторное определение является ошибкой. Обычно это просто синтаксическая ошибка. Вероятно, мы имели в виду не то, что написали, а следующие инструкции:
let v1 = 7;
let v2 = 8;
Определение объекта класса Variable
с именем var
и значением val
состоит из двух логических частей.
1. Проверяем, существует ли в векторе var_table
объект класса Variable
с именем var
.
2. Добавляем пару (var
, val
) в вектор var_table
.
Мы не должны использовать неинициализированные переменные, поэтому определили функции is_declared()
и define_name()
, представляющие эти две операции.
bool is_declared(string var)
// есть ли переменная var в векторе var_table?
{
for (int i = 0; i
if (var_table[i].name == var) return true;
return false;
}
double define_name(string var, double val)
// добавляем пару (var,val) в вектор var_table
{
if (is_declared(var)) error(var,"declared twice");
var_table.push_back(Variable(var,val));
return val;
}
Добавить новый объект класса Variable
в вектор типа vector
легко; эту операцию выполняет функция-член вектора 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()
возвращает значение. Как правило, общие правила позволяют сохранить простоту кода, а специальные варианты приводят к усложнениям.
Описанный механизм отслеживания переменных часто называют 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