.: [предыдущая | оглавление | следующая] :.

15.2 Реализация интерпретатора арифметических выражений

15.2.1 Организация хранения идентификаторов, их значений и констант

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

#define SIZETABLVAR 30 //размер таблицы символов
struct //описание таблицы символов
{
      int length; //длина имени
      char Name[10]; //имя переменной
      double init; //значение переменной
} TablVar[SIZETABLVAR];
int TopVar; //номер свободной ячейки таблицы

Этот массив структур статический. Поэтому вся память при запуске программы обнуляется. Кроме того, имеется две функции FindNameVar и AddNameVar. Первая ищет идентификатор таблице и если находит, то возвращает номер элемента таблицы хранящий этот идентификатор, соответственно и его значение. Вторая - производит добавление нового идентификатора в таблицу и возвращает его табличный номер. Если идентификатор хранится уже в таблице, то возвращает номер идентификатора.

int FindNameVar(char *var) //найти имя в таблице символов
{
      int i;
      int len;
      len=strlen(var);
      for(i=0; i < SIZETABLVAR; i++) //просмотреть всю таблицу
      {
            if(TablVar[i].length==0) return -1; //не найдено
            if(TablVar[i].length==len)
            if(strcmp(TablVar[i].Name,var)==0) return i;
            //найдено
      }
      return -1; //не найдено
}
 
int AddNameVar(char *var) // добавить новое имя в таблицу символов
{
      int num;
      int len;
      char *ptr;
      if((num=FindNameVar(var))==-1) //такое имя не найдено
      {
            if(TopVar < SIZETABLVAR) //вставка имени
            {
                  num=TopVar; //взять номер свободного элемента массива
                  len=strlen(var); //вычислить размер идентификатора
                  TablVar[TopVar].length=len;
                  strncpy(TablVar[TopVar].Name,var,9);
                  TopVar++;
            }
            else Error("Переполнена таблица символов");
      }
      return num; //вернуть индекс имени в таблице
}

Хранение констант в таблице символов организовано следующим образом. Выделенная строка символов, в которой записана константа, хранится как имя идентификатора, а ее значение записывается как значение идентификатора. Например, TablVar[i].Name=”3.355” это будет имя. А TablVar[i].init=3.335 - хранит значение.

15.2.2 Использование стека для выполнения арифметических операций

Стек является удобным инструментом для выполнения арифметических операций. Например, для выполнения операции "сложить" необходимо знать адрес первого слагаемого, адрес второго слагаемого и адрес, куда записать результат. При организации стековых вычислений значения операндов берутся из стека и результат операции также заносится в стек. Например, С=A+B, тогда стековые операции будут выглядеть так:
Положить(A), Положить(B), Сложить, Взять(С)
Или для формулы (A+B)*(X-Y)+20
Положить(A), Положить(B), Сложить
Положить(X), Положить(Y), Вычесть
Умножить
Положить (20), Сложить

Результат вычисления этого выражения будет храниться на вершине стека. Ниже описан простой вариант организации стека и стековых операций "положить" и "взять".
Описание статической памяти

#define SIZESTACK 30 //размер стека
double Stack[SIZESTACK]; //стек (массив фиксированного размера)
int TopStack; //указатель вершины стека

Переменная TopStack всегда указывает на первый свободный элемент стека, а последний занесенный элемент хранится по адресу TopStack-1 . Стек пустой если переменная TopStack равна нулю. Стек переполнен если TopStack равна SIZESTACK. Ниже приведен текст функций Push и Pull - первая кладет значение в стек, вторая выталкивает значение из стека. 

void Push(double f) // положить значение в стек
{
      if(TopStack < SIZESTACK)
            Stack[TopStack++]=f;
      else Error("Стек переполнен");
}
 
double Pull() //взять значения из стека
{
      if(TopStack>0)
      {
            TopStack--;
            return Stack[TopStack];
      }
      return 0;
}

15.2.3 Вспомогательные функции

К вспомогательным функциям относятся функции Error, SkipBlanks, GetName, getfloat, match. Необходимо отметить, что почти все указанные функции работают с глобальным указателем на текущий символ входной строки. Он объявлен и проинициализированным следующим образом: 

char *ch;//объявление глобального указателя
ch=in_line[0]; //инициализация
 

Функция Error обеспечивает вывод сообщения на экран терминала. Ниже приведен текст этой функции:
void Error(char *soo) //вывод сообщения об ошибке
{
      printf("%s\n",soo);
}

Функция SkipBlanks обеспечивает пропуск служебных символов во входной строке. Пропуск обеспечивает тем, что статический указатель ch на текущий символ увеличивается на еденицу. Это процесс продолжается пока текущий символ будет служебным (табуляция, конец файла, возврат каретки, перевод строки, пробел). Ниже приведет текст функции

void SkipBlanks() // пропуск служебных символов
{
      while(true) //цикл пока есть служебное символ
      {
            switch (*ch)
            {
                  case 8: //табуляция
                  case 26: //конец файла
                  case 10: //возврат каретки
                  case 13: //перевод строки
                  case ’ ’: //пробел
                        ch++; break;
                  default: //если это не служебный символ или конец строки
                        return;
            }
      }
}

Функция GetName обеспечивает выделение идентификатора из входной строки. Первоначально функция вызывает SkipBlanks для пропуска служебных символов. Затем с помощью функции isalpha, описанной в заголовочном файле ctype.h проверяется, является ли текущий символ буквой (буковой являются буквы латиницы). Далее в цикле выбираются буквы или цифры (для проверки используется библиотечная функция isalnum, описанная в ctype.h)

int GetName(char *name, int max) // взять имя во входной строке
{
      int i;
      SkipBlanks(); //пропустить пробелы
      if(isalpha(*ch)) *name++=*ch++; //первый символ должен быть буквой
      else
      {
            *name=0;
            return 0;
      }
      for(i=1; i < max; i++) //взять остальные символы (буква или цифра)
      {
            if(isalnum(*ch)) *name++=*ch++;
            else
            {
                  *name=0;
                  return 1;
            }
      }
      *name=0;
      Error("Большое имя");
      return 0;
}

Функция getfloat выделяет из входной строки константу, записывает в строку num и преобразует ее в вещественное число с помощью библиотечной функции atof, которая описана в заголовочном файле stdlib.h

double getfloat(char *num, int max) // взять число во входной строке и преобразовать к double
{
      int i = 0, j;
      SkipBlanks(); //пропустить пробелы
      if(*ch==’-’|| *ch==’+’) //взять знак
            num[i++]=*ch++;
      SkipBlanks(); //пропустить пробелы
      for(; i < max; i++) //взять целую часть числа
      {
            if(isdigit(*ch)) num[i]=*ch++;
            else break;
      }
      j=i;
      if(*ch==’.’) //взять дробную часть числа
      {
            num[i]=*ch++;
            for(j=i+1; j < max; j++)
            {
                  if(isdigit(*ch)) num[j]=*ch++;
                  else break;
            }
      }
      num[j]=0;
      return atof(num); //это стандартная функция Си описана в <stdlib.h>
}

Функция match производит сопоставление строки str c символами входной строки, начиная с текущего (по указателю ch). Если строка str полностью содержится в во входной строке, то указатель ch сдвигается на заданной количество символов. В противном случае не сдвигается. 

int match(char *str) //эта функция разбиралась в разделе "Обработка строк"
{
      char *chv;
      SkipBlanks(); //пропустить служебные символы
      chv=ch; //запомнить указатель
      while(*str) //цикл проверки совпадения
      {
            if((*str++)!=(*chv++)) return 0; //вернуть ноль, если не совпала
      }
      ch=chv; //переместить указатель
      return 1; //вернуть значение совпадения
}

15.2.4 Реализация метода рекурсивного спуска

Рекурсивный разбор арифметического выражения начинается с работы функции ExpressAdd, которая осуществляет разбор и вычисление аддитивных составляющих арифметического выражения.
TranSin - функция разбора и вычисления синуса;
ExpressFunc - разбор первичного (см. алгоритм РазборD() )
ExpressMult - разбор и вычисление выражений с умножением и делением
ExpressAdd - разбор и вычисление выражений со сложением и вычитанием
AssignOp - вычисление и разбор выражения и присвоение результата переменной
Текст функции разбора и вычисления синуса 


void TranSin(void) // вычисление синуса
{
      double f;
      if(match("(")) //есть ли скобка ?
      {
            ExpressAdd(); //вычислить выражение аргумента синуса
            f=Pull(); //взять значение из стека
            Push(sin(f)); //положить в стек значения синуса
            match(")"); //ждать закрывающуюся скобку
      }
      else Error("Ошибка в функции sin");
}
 
void ExpressFunc(void) // вычисление первичного выражения
{
      int num;
      char name[30];
      double f;
      int max=29;
      if(match("(")) //вычисление выражения в скобках
      {
            ExpressAdd(); //разобрать и вычислить выражение в скобках
            if(match(")")==0) Error("Пропущена )");
      }
      else //вычисление функций
      {
            if(match("sin")) TranSin();
            else //это переменная?
            {
                  if(GetName(name,max)==1) //взятие значения переменной
                  {
                        num=AddNameVar(name); //найти переменную в таблице
                        Push(TablVar[num].init); //положить табличное значение в стек
                  }
                  else //взять значение константы
                  {
                        f=getfloat(name,max);
                        Push(f); //положить значение константы в стек
                  }
            }
      }
}
 
void ExpressMult(void) //вычисление выражения для умножения и деления
{
      double f1, f2;
      ExpressFunc(); //вычислить первичное
      while(true)
      {
            if(match("*")) //если знак умножения
            {
                  ExpressFunc(); //вычислить второе первичное
                  f1=Pull(); f2=Pull(); //взять из стека множители
                  Push(f1*f2); //положить в стек произведение
            }
            else
            if(match("/"))//если знак деления
            {
                  ExpressFunc(); //вычислить второе первичное
                  f1=Pull(); //взять делитель
                  f2=Pull(); //взять делимое
                  if(f1==0.) Error("Деление на 0");
                  else Push(f2/f1); //положить частное в стек
            }
            else break;
      }
}
 
void ExpressAdd(void) // вычисление выражения для сложения и вычитания
{
      double f1, f2;
      ExpressMult(); //вычислить значение первого операнда
      while(true)
      {
            if(match("+"))//если знак плюс
            {
                  ExpressMult(); //вычислить значение для второго операнда
                  f1=Pull(); f2=Pull(); //взять значения слагаемых из стека
                  Push(f1+f2); //положить сумму в стек
            }
            else
                  if(match("-")) //если это минус
                  {
                        ExpressMult(); //взять значение второго операнда
                        f1=Pull(); f2=Pull(); //взять значения из стека
                        Push(f2-f1); //положить разность в стек
                  }
                  else break;
      }
}
 
void AssignOp(void) // выполнение операции присваивания
{
      char name[10];
      int max=9;
      int num;
      if(GetName(name,max)==1) //взять имя переменной
      {
            num=AddNameVar(name); //записать имя в таблицу
      }
      else
      {
            ExpressAdd(); //иначе вычислить выражение
            printf("%f\n",Pull());
            return;
      }
      if(match("=")) //имеется операция присваивания
      {
            ExpressAdd(); //вычислить выражение
            TablVar[num].init=Pull(); //записать значение в таблицу
      }
      printf("%f\n",TablVar[num].init);
}
 
void PrintTablVar() // вывод таблицы символов со значениями
{
      int i;
      for(i=0; i < TopVar; i++)
      {
            printf("%2d %10s %f\n",i,TablVar[i].Name,TablVar[i].in
      }
}
 
void main()
{
      TopVar=0; //инициализация стека и таблицы переменных
      TopStack=0;
      while(1)
      {
            printf("calc>"); //вывод подсказки
            gets(in_line); //ввод строки символов
            ch=&in_line[0]; //инициализация указателя разбора
            if(match("end")) //если это "end", то завершение работы
                  break;
            else
                  if(match("tabl")) //если это "tabl", то вывести таблицу символов
                        PrintTablVar();
                  else //иначе вычислить значение выражения
            AssignOp();
      }
}
 
Пример работы интерпретатора:
calc>x=100 //вычислить выражение
100.00 //результат
calc>y=200
200.00
calc>z=y/x
2.00
calc>tabl .//вывести таблицу символов
0 x 100
1 y 200
2 z 2
calc>end //завершить работу программы

.: [предыдущая | оглавление | следующая] :.