Dotychczas nas interpreter obsługiwał zmienne oznaczane w kodzie jedną literą (w sumie mogliśmy wykorzystać 26 zmiennych – od a do z). Dzisiaj dodamy możliwość używania zmiennych, których identyfikator będzie zawierał więcej niż jeden znak. Wykorzystany tu mechanizmy w kolejnych wpisach użyję do wbudowania stałych (np. PI) i funkcji (np. sin() w moim interpreterze)
Zmienne będziemy przechowywać w następującej strukturze:
struct { char *name; double val; struct Symbol *next; } Symbol;
a do manipulowania listą będziemy używać dwóch funkcji: add – w celu dodawania zmiennych do listy, find – w celu wyszukania i pobrania wartości zmiennej.
Stwórzmy zatem plik symbol.h
#ifndef SYMBOL_H #define SYMBOL_H typedef struct Symbol { char *name; double val; struct Symbol *next; } Symbol; Symbol *add(char *s, double d); Symbol *find(char *s); #endif
oraz plik symbol.c
#include <string.h>; #include <stdlib.h>; #include "symbol.h" static Symbol *symlist = NULL; extern void yyerror(char *s); Symbol *add(char *s, double d) { Symbol *sp; if ((sp = (Symbol *) malloc(sizeof(Symbol))) == NULL ) yyerror("out of memory"); if ((sp->name = (char *) malloc(strlen(s)+1)) == NULL ) yyerror("out of memory"); strcpy(sp->name,s); sp->val = d; sp->next = symlist; symlist = sp; return sp; } Symbol *find(char *s) { Symbol *sp; for (sp = symlist; sp != NULL ; sp = sp->next) { if (strcmp(sp->name, s) == 0) return sp; } return NULL; }
Parser
Zmieńmy plik parser.y tak aby parser odpowiednio korzystał ze zmiennych. Pierwszą zmianą będzie zdefiniowanie unii tak aby przechowywać albo nazwę zmiennej albo wartość wyrażenia.
%union { double val; char *sym; }
Tym samym nie będziemy musieli już korzystać z definicji YYTYPE.
#define YYTYPE double
Drugą zmianą jest odpowiednie wskazanie typów dla poszczególnych tokenów i reguł parsera.
%token <val> NUM %token <sym> VARIABLE %type <val> statement expression
Trzecia zmiana dotyczy sposoby zapamiętywania nazw zmiennych i wartości zmiennych (fukcja add) oraz pobieranie tych wartości na potrzeby wyliczenia wyrażenia (funkcja find). Odpowiednie zmiany zostały pokazane poniżej (plik parse.y)
%{ #include <stdio.h> #include "symbol.h" int yylex(void); void yyerror(char *s ); %} %union { double val; char *sym; } %start expression_list %token <val> NUM %token <sym> VARIABLE %type <val> statement expression %left '-' '+' %left '*' '/' %nonassoc UMINUS %% expression_list: expression_list statement '\n' { printf(" = %f\n", $2); } | expression_list '\n' { /* allow blank lines */ } | { /* empty string */ } ; statement: expression | VARIABLE '=' expression { add( $1, $3 ); } expression: NUM | VARIABLE { Symbol *sym; sym = find($1); if(!sym) yyerror("Unknown variable"); else $$ = sym->val; } | '-' expression %prec UMINUS { $$ = -$2; } | '(' expression ')' { $$=$2; } | expression '+' expression { $$ = $1 + $3; } | expression '-' expression { $$ = $1 - $3; } | expression '*' expression { $$ = $1 * $3; } | expression '/' expression { $$ = $1 / $3; } ; %% void yyerror(char *s ) { fprintf (stderr, "%s\n", s); } int yywrap() { return 1; } int main (int argc, char **argv) { return yyparse(); }
Lekser
Poprawmy teraz plik scan.l tak aby lekser obsługiwał łańcuchy znakowe jako nazwy zmiennych.
%{ #include <stdio.h> #include "parse.h" void yyerror(char *s); %} %% [a-zA-Z_][a-zA-Z_0-9]* { if ((yylval.sym = (char *) malloc(strlen(yytext)+1)) == NULL ) yyerror("out of memory"); strcpy(yylval.sym, yytext) ; return VARIABLE ; } [0-9]+ { yylval = atof (yytext); return NUM; } (([0-9]+(\.[0-9]*)?)|([0-9]*\.[0-9]+)) { yylval = atof (yytext); return NUM; } [-+()=/*\n] return *yytext; [ \t] ; /* skip whitespace */ . yyerror("invalid character"); %%
Makefile
Na tym etapie warto jeszcze zmienić plik Makefile tak aby plik symbol.c brał udział w kompilacji.
LEX = flex LFLAGS = YACC = yacc YFLAGS = -d LDFLAGS = -ll OBJ = scan.o parse.o symbol.o SRC = scan.l parse.y symbol.c all: interpreter interpreter: $(OBJ) $(CC) $(LDFLAGS) $(CPPFLAGS) $(CFLAGS) $(OBJ) -o $@ scan.o: parse.c scan.c: scan.l $(LEX) $(LFLAGS) -o $@ $^ parse.c: parse.y $(YACC) $(YFLAGS) -o $@ $^ clean: $(RM) *.o scan.c parse.c parse.h interpreter