Работа с переменными в анализаторе

Содержание

Во всех языках программирования, многих калькуляторах и электронных таблицах предусмотрены переменные, позволяющие сохранять значения для дальнейшего использования. Для того чтобы синтаксический анализатор из предыдущего примера обладал такой возможностью, в него необходимо внести некоторые дополнения. Во-первых, это, конечно, сами переменные. Как уже было сказано выше, анализатор будет распознавать только переменные с именами от А до Z. (Впрочем, при желании вы можете избавиться от этого ограничения.) Каждая переменная хранится в одной ячейке массива из 26 элементов типа double. Поэтому в исходный текст анализатора необходимо добавить следующий фрагмент:

double vars[26] = { /* 26 пользовательских переменных, A-Z */
  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
  0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
  0.0, 0.0, 0.0, 0.0, 0.0, 0.0
};

Как вы заметили, для удобства пользователя все переменные инициализируются нулями.

Кроме этого, понадобится процедура для получения значения заданной переменной. Поскольку имена переменных являются буквами от А до Z, их можно использовать для индексации массива vars, вычитая код ASCII буквы А из имени переменной. Ниже показана функция find_var(), возвращающая значение переменной:

/* Получение значения переменной. */
double find_var(char *s)
{
  if(!isalpha(*s)){
    serror(1);
    return 0;
  }
  return vars[toupper(*token)-'A'];
}

Данная функция написана так, что она принимает имена любой длины, но только первый символ является значимым. Данное ограничение можно изменить в соответствии с вашими потребностями.

Также необходимо модифицировать функцию atom(), чтобы она обрабатывала как числа, так и переменные. Ниже показана ее новая версия:

/* Получение значение числа или переменной. */
void atom(double *answer)
{
  switch(tok_type) {
    case VARIABLE:
      *answer = find_var(token);
      get_token();
      return;
    case NUMBER:
      *answer = atof(token);
      get_token();
      return;
    default:
      serror(0);
  }
}

С технической точки зрения, это все, что требуется анализатору для корректной обработки переменных. Однако пока нет способа присвоить этим переменным значения. Часто это делается за пределами анализатора, но в анализаторе можно рассматривать знак равенства как знак операции присваивания и сделать обработку этого знака частью анализатора. Этого можно достичь несколькими способами. Один из них — добавить в анализатор функцию eval_exp1(), показанную ниже:

/* Обработка присваивания. */
void eval_exp1(double *result)
{
  int slot, ttok_type;
  char temp_token[80];

  if(tok_type == VARIABLE) {
    /* сохраниеть старую лексему */
    strcpy(temp_token, token);
    ttok_type = tok_type;

    /* вычислить индекс переменной */
    slot = toupper(*token) - 'A';

    get_token();
    if(*token != '=') {
      putback(); /* вернуть текущую переменную */
      /* восстановить старуб лексему - это не присваивание */
      strcpy(token, temp_token);
      tok_type = ttok_type;
    }
    else {
      get_token(); /* получить следующую часть выражения */
      eval_exp2(result);
      vars[slot] = *result;
      return;
    }
  }

  eval_exp2(result);
}

Как вы видите, этой функции приходится заглядывать вперед, чтобы определить, выполняется ли на самом деле присваивание. Это связано с тем, что имя переменной всегда находится перед оператором присваивания, но само по себе наличие имени переменной не гарантирует, что за ней последует присваивание. Другими словами, анализатор воспримет выражение А = 100 как присваивание, причем он может определить, что А / 10 им не является. Для этого функция eval_exp1() считывает из входного потока следующую лексему. Если эта лексема не является знаком равенства, она с помощью функции putback() возвращается во входной поток для последующего использования:

/* Возврат лексемы во входной поток. */
void putback(void)
{
  char *t;

  t = token;
  for(; *t; t++) prog--;
}

Ниже приведен полный текст улучшенного анализатора:

/* Данный модуль содержит рекурсивный нисходящий
   синтаксический анализатор, распознающий переменные.
*/

#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>

#define DELIMITER 1
#define VARIABLE  2
#define NUMBER    3

extern char *prog; /* указатель на анализируемое выражение */
char token[80];
char tok_type;

double vars[26] = { /* 26 пользовательских переменных,  A-Z */
 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
};

void eval_exp(double *answer), eval_exp2(double *answer);
void eval_exp1(double *result);
void eval_exp3(double *answer), eval_exp4(double *answer);
void eval_exp5(double *answer), eval_exp6(double *answer);
void atom(double *answer);
void get_token(void), putback(void);
void serror(int error);
double find_var(char *s);
int isdelim(char c);

/* Точка входа анализатора. */
void eval_exp(double *answer)
{
  get_token();
  if(!*token) {
    serror(2);
    return;
  }
  eval_exp1(answer);
  if(*token) serror(0); /* последня лексема должна быть нулем */
}

/* Обработка присваивания. */
void eval_exp1(double *answer)
{
  int slot;
  char ttok_type;
  char temp_token[80];

  if(tok_type == VARIABLE) {
    /* сохранить старую лексему */
    strcpy(temp_token, token);
    ttok_type = tok_type;
    /* вычислить индекс переменной */
    slot = toupper(*token) - 'A';

    get_token();
    if(*token != '=') {
      putback(); /* вернуть текущую лексему */
      /* восстановить старую лексему - это не присваивание */
      strcpy(token, temp_token);
      tok_type = ttok_type;
    }
    else {
      get_token(); /* получить следующую часть выражения */
      eval_exp2(answer);
      vars[slot] = *answer;
      return;
    }
  }
  eval_exp2(answer);
}

/* Сложение или вычитание двух слагаемых. */
void eval_exp2(double *answer)
{
  register char op;
  double temp;

  eval_exp3(answer);
  while((op = *token) == '+' || op == '-') {
    get_token();
    eval_exp3(&temp);
    switch(op) {
      case '-':
        *answer = *answer - temp;
        break;
      case '+':
        *answer = *answer + temp;
        break;
    }
  }
}

/* Умножение или деление двух множителей. */
void eval_exp3(double *answer)
{
  register char op;
  double temp;

  eval_exp4(answer);
  while((op = *token) == '*' || op == '/' || op == '%') {
    get_token();
    eval_exp4(&temp);
    switch(op) {
      case '*':
        *answer = *answer * temp;
        break;
      case '/':
        if(temp == 0.0) {
          serror(3); /* деление на ноль */
          *answer = 0.0;
        } else *answer = *answer / temp;
        break;
      case '%':
        *answer = (int) *answer % (int) temp;
        break;
    }
  }
}

/* Возведение в степень */
void eval_exp4(double *answer)
{
  double temp, ex;
  register int t;

  eval_exp5(answer);
  if(*token == '^') {
    get_token();
    eval_exp4(&temp);
    ex = *answer;
    if(temp==0.0) {
      *answer = 1.0;
      return;
    }
    for(t=temp-1; t>0; --t) *answer = (*answer) * (double)ex;
  }
}

/* Вычисление унарного + и -. */
void eval_exp5(double *answer)
{
  register char  op;

  op = 0;
  if((tok_type == DELIMITER) && *token=='+' || *token == '-') {
    op = *token;
    get_token();
  }
  eval_exp6(answer);
  if(op == '-') *answer = -(*answer);
}

/* Обработка выражения в скобках. */
void eval_exp6(double *answer)
{
  if((*token == '(')) {
    get_token();
    eval_exp2(answer);
    if(*token != ')')
      serror(1);
    get_token();
  }
  else atom(answer);
}

/* Получение значения числа или переменной. */
void atom(double *answer)
{
  switch(tok_type) {
    case VARIABLE:
      *answer = find_var(token);
      get_token();
      return;
    case NUMBER:
      *answer = atof(token);
      get_token();
      return;
    default:
      serror(0);
  }
}

/* Возврат лексемы во входной поток. */
void putback(void)
{
  char *t;

  t = token;
  for(; *t; t++) prog--;
}

/* Отображение сообщения о синтаксической ошибке. */
void serror(int error)
{
  static char *e[]= {
      "Синтаксическая ошибка",
      "Несбалансированные скобки",
      "Нет выражения",
      "Деление на нуль"
  };
  printf("%s\n", e[error]);
}

/* Получение очередной лексемы. */
void get_token(void)
{
  register char *temp;

  tok_type = 0;
  temp = token;
  *temp = '\0';

  if(!*prog) return; /* конец выражения */

  while(isspace(*prog)) ++prog; /* пропустить пробелы,
                  символы табуляции и пустой строки */

  if(strchr("+-*/%^=()", *prog)){
    tok_type = DELIMITER;
    /* перейти к следующему символу */
    *temp++ = *prog++;
  }
  else if(isalpha(*prog)) {
    while(!isdelim(*prog)) *temp++ = *prog++;
    tok_type = VARIABLE;
  }
  else if(isdigit(*prog)) {
    while(!isdelim(*prog)) *temp++ = *prog++;
    tok_type = NUMBER;
  }

  *temp = '\0';
}

/* Возвращение значения ИСТИНА, если с является разделителем. */
int isdelim(char c)
{
  if(strchr(" +-/*%^=()", c) || c==9 || c=='\r' || c==0)
    return 1;
  return 0;
}

/* Получение значения переменной. */
double find_var(char *s)
{
  if(!isalpha(*s)){
    serror(1);
    return 0.0;
  }
  return vars[toupper(*token)-'A'];
}

Для демонстрации работы данного анализатора можно использовать ту функцию main(), которая использовалась для демонстрации работы простого анализатора. Усовершенствованный анализатор позволяет вводить выражения, подобные следующим:

A = 10 / 4
A - B
C = A * (F - 21)