Простое приложение – калькулятор
Рассмотрим простое приложение – арифметический калькулятор, вычисляющий произвольные арифметические выражения с любым уровнем вложенности скобок. В этом приложении действия, выполняемые при синтаксическом разборе, выполняются с компонентами правил, соответствующий компонент во вставке на языке Си (она производится в фигурных скобках) представлен своим значением, находящимся в стеке. Тип элемента стека в этом варианте – удвоенной точности вещественное число. Значение компонента обозначается метасимволом $N, где цифра N обозначает номер компонента в правиле. Метасимволом $$ обозначается верхушка стека.
Листинг 6. Калькулятор со скобками
/* Пример калькулятора */
%{ /* вставка определения на языке Си */
#define YYSTYPE double
/* тип данных в стеке yacc */
#define QUIT 101010
%}
%token NUMBER /* лексема число */
%left '+' '-' /* левоассоциативные операции */
%left '*' '/' /* левые опер.,больше приоритет */
%left UNARYMINUS./* унарный минус */
%% /* СЕКЦИЯ ПРАВИЛ */ list: /* пусто */
{ prompt(); }
| list '\n' /* список выражений */
{prompt(); }
| list expr '\n'
{if ($2 == QUIT) { return(0);
} else {
fprintf(stdout, "RESULT> %.8g\n", $2); prompt();
}
}
| list error '\n'
{ yyerrok; prompt();
}
;
expr: /* правило для нетерминала выражение */
NUMBER { $$ = $1; }
| '-' expr %prec UNARYMINUS { $$ = -$2; }
| expr '+' expr { $$ = $1 + $3; } | expr '-' expr { $$ = $1 – $3; }
31
| expr '*' expr { $$ = $1 * $3; } | expr '/' expr { $$ = $1 / $3; }
| '(' expr ')' { $$ = $2; }
;
/* конец секции */
%%
#include <stdio.h> #include <ctype.h>
char *progname; /* для сообщения об ошибках */ int lineno = 1;
main(argc, argv) char *argv[];
{
if (argc > 1)
fprintf(stderr, "лишние аргументы\n"); progname = argv[0];
fprintf(stdout,
"\n********************************************");
fprintf(stdout,
"\n* Простой калькулятор *"); fprintf(stdout,
"**********************************************"); putchar ('\n');
yyparse();
fprintf(stdout, "* До свиданья! *\n");
}
yylex() /* лексический анализатор */
{
int c;
while ((c=getchar()) == ' ' || c == '\t');
/* пропустить пробельные символы */ if (c == EOF) return 0;
if (c == '.' || isdigit(c)) { /* число */ ungetc(c, stdin);
scanf("%lf", &yylval); return NUMBER;
}
if (c == '\n') lineno++;
if (c == 'Q' || c == 'q') /* плохой код! */ if ((c=getchar()) == 'U' || c == 'u')
if ((c=getchar()) == 'I' || c == 'i') if ((c=getchar()) == 'T' || c == 't')
{
yylval = QUIT; return NUMBER;
} else return '?';
32
return c;
}
yyerror(s) /* вызывается при ошибках yacc */ char *s;
{
warning (s, (char *) 0);
}
warning(s,t) /* предупреждение */
char *s, *t;
{
fprintf(stderr, "%s: %s", progname, s); if (t) fprintf(stderr, " %s", t);
fprintf(stderr, " near line %d\n", lineno);
}
prompt() /* приглашение к вводу выражения */
{
fprintf(stdout, "READY> ");
}
Лабораторная работа 7
Добавим функции в калькулятор
Расширить простой калькулятор, добавив в него тригонометрические функции (или exp, log, abs).
Требования
Два часа на выполнение, языки реализации – уасс и С. Модифицировать грамматику, вставки на языке С и дополнить лек-
сический анализатор. В секции описаний добавить лексемы SIN, COS, TAN, ATAN и т.д.
Алгоритмизация задачи
Можно использовать функции стандартной библиотеки С для вычисления функций. В лексическом распознавателе придется анализировать символьные строки, выявляя последовательности символов «sin», «cos», etc…
Калькулятор с функциями и константами
В этот калькулятор добавлены переменные (именованные регистры), функции и константы. В стеке теперь может находиться либо имя (Sym), либо значение (val), для этого элемент стека описывается объединением (союзом) %union.
33
Листинг 7. Калькулятор с переменными и функциями
%{
#include <stdio.h> #include <ctype.h> #include <stdlib.h>
#include <string.h>
#include <math.h> int yylex();
double Sqrt (double x); // своя функция sqrt typedef struct Symbol // структура для описания
{ // переменных, констант и char name; short type; // функций type задает union { /* тип объекта */
double val; double (*ptr)(); }u;
struct Symbol *next;
} Symbol;
Symbol *symlist =0; static struct { char *name;
double cval; } Const[]= /* список констант */
{
"PI", 3.1415926536, |
|
"E", 2.718281828459, |
|
"RAD",57.2958, |
|
0,0 |
|
}; |
|
static struct { |
|
char *name; |
|
double (*func)(); } |
|
Funcs[]= |
/* список функций */ |
{ |
|
"sin",sin, |
|
"cos",cos, |
|
"tan",tan, |
|
"sqrt",Sqrt, |
|
"abs",fabs, |
|
0,0 |
|
}; |
|
%} |
|
%union { |
/* тип элемента стека */ |
double val; |
|
Symbol *sym; |
|
}; |
|
%token <val> NUM |
/* лексемы */ |
%token <sym> VAR FUNC UNDF |
|
34
%type <val> expr assign |
|
%type <val> prog |
|
%right '=' |
/* операции */ |
%left '+' '-' %left '*' '/'
%right '^'
%left UNMIN
%%
prog:
'\n' { printf("!\n"); }
| expr '\n'{ $$=$1; printf("=>%lf\n",$$); }
| prog expr '\n' { $$=$2; printf("->%lf\n",$$); }
;
assign: VAR '=' expr
{ $$ = $1->u.val=$3; $1->type=VAR; }
;
expr: NUM { $$ = $1; }
| VAR { $$ = $1->u.val; }
| FUNC '(' expr ')' { $$ = (($1->u.ptr)($3)); }
| expr '+' expr { $$ = $1+$3; }
| expr '-' expr { $$ = $1 – $3; }
| expr '*' expr { $$ = $1*$3; }
| expr '/' expr { if ($3!=0) $$ = $1/$3;
else { printf(" Zero divide\n"); $$=$1; }
}
| expr '^' expr { $$ = pow($1,$3); }
| '-' expr %prec UNMIN { $$ = -$2; }
| assign /* присваивание */
| '(' expr ')' { $$ = $2; }
;
%%
Symbol *install(char *s, short t, double d)
{ /* добавление нового элемента в список */
Symbol *sp;
sp = (Symbol *) malloc(sizeof(Symbol)); sp->name = (char *)malloc(strlen(s)+1); strcpy(sp->name,s);
sp->type = t; sp->u.val=d; sp->next=symlist; symlist=sp;
return sp;
}
Symbol *lookup(char *s)
{ /* поиск в списке */
Symbol *sp;
for (sp=symlist; sp!=(Symbol *)0; sp=sp->next) if (strcmp(sp->name,s)==0)
{
return sp;
}
35