Во всех языках программирования, многих калькуляторах и электронных таблицах предусмотрены переменные, позволяющие сохранять значения для дальнейшего использования. Для того чтобы синтаксический анализатор из предыдущего примера обладал такой возможностью, в него необходимо внести некоторые дополнения. Во-первых, это, конечно, сами переменные. Как уже было сказано выше, анализатор будет распознавать только переменные с именами от А до 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)