Литмир - Электронная Библиотека
A
A

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;

103
{"b":"847443","o":1}