You are on page 1of 19

Semantika obrada

6. Strukture za semantiku obradu

Semantika obrada je proces u kojem kompilator ispituje i odreuje semantike znaajke programa. Najznaajniji zadaci semantike obrade su: kontrola tipova pretvorba tipova priprema za generiranje koda

Najvanija podatkovna struktura koja se koriste u semantikoj obradi je tablica simbola. Ona sadrava podatke o svim imenovanim simbolima. Imenovani simboli su: varijable, tipovi, procedure i imenovane konstante. Za sve simbole karakteristina su tri podatka: ime deklaracija (tipa, funkcije) nain spremanja u izvrnom kodu i zauzee memorije Ime se dobija ve tijekom leksike analize. Elementi deklaracije se utvruju tijekom parsiranja. Oni se kod jednoprolaznih kompilatora odmah biljee u tablici simbola, a kod vieprolaznih kompilatora se obino najprije registriraju u apstraktnom sintaktikom stablu AST). Zatim se vri kontrola tipova varijabli koje se koriste u izrazima i kontrola tipova argumenata funkcija i procedura. Rezultat semantike obrade se obino ponovo biljei u sintaktikom stablu (primjerice stablo se proiruje s elementima koji su potrebni za pretvorbu tipova). Takoer se dodaju razni podaci koji su potrebni za generiranje koda ( primjerice, za varijable se oznaava potrebno zauzee memorije). Ovako proireno sintaktiko stablo se naziva dekorirano apstraktno sintaktiko stablo. Najprije e biti pokazano kako se realizira tablica simbola, a zatim e primjerom biti pokazan postupak semantike obrade.

Semantika obrada

6.1 Strukture za jezik Cmm


Jezik Cmm (C minus minus) je opisan u Dodatku. Gotovo je identian C jeziku, ali ne podrava neke konstrukcije i izraze C jezika (pokazivaku aritmetiku i korisniki definirane tipove). Bit e pokazano kako se moe realizirati jednostavni kompilator za Cmm. Prije pisanja svakog kompilatora ili interpretera, treba definirati temeljne strukture podataka. U Cmm kompilatoru koristi se tri temeljne strukture podataka: a) Type - struktura za zapis karakteristike tipova, b) Symbol - struktura za zapis karakteristika simbola (identifikatora), c) Node - struktura za zapis apstraktnog sintaktikog stabla.
typedef typedef typedef typedef struct struct struct struct _type _treenode _symbol _initval Type; Node; Symbol; InitVal;

Karakteristike tipova su zapisane u strukturi Type:


struct _type { int kind; int ref_count; Type *subtype;

/* int, char, pointer, func, proc, array, reference*/ /* reference counting to get less allocation */ /* sub type of array or pointer, return of func *

union { struct _a { int len; } array; struct _f { int flag; Symbol *params; } func;

/* array (or string) length */ /* NotDefined, DefForward, BodyDef*/ /* list of function parameters symbols*/

}; char *name; Type *next; /* name of named types - not implemented*/ /* for link in type table - not implemented */

struct _s { Symbol *members; /* list of structure memebers */ }struc; /* struct type - not implemented */

};

Tri posljednja lana nisu implementirana. Oni , pri proirenju jezika, mogu posluiti za definiranje imenovanih strukturalnih tipova.
/* kind of type */ #define #define #define #define #define #define #define UNKNOWN VOID_TYPE CHAR_TYPE INT_TYPE POINTER_TYPE FUNCTION ARRAY_TYPE 0 1 2 4 8 16 32

Semantika obrada
#define #define REFERENCE_TYPE STRUCT_TYPE 64 128

Podaci o simbolima jezika se biljee u strukturi:


struct _symbol { char *name; Type *type; int scope; int addr; InitVal *init; Symbol *next; };

/* /* /* /* /* /*

lexem of symbol*/ type of symbol */ Global = 0, Param = 1, Local=2,... */ stack address(offset) for locals and arguments*/ symbol init value */ pointer to keep list of symbols in symbol table*/

Pored imena (name) biljei se tip (type) , doseg (global, parametar funkcije ili lokalna varijabla), adresa lokalne varijable i argumenta funkcije na izvrnom stogu, i poetna vrijednost varijable, ako je definirana. Poetna vrijednost se biljei u strukturi:
struct _initval { int flag; union { int i; char *str; IntList *ilist; Node *expr; }; }; /* InitInt, InitIntArray, InitString, */ /* /* /* /* integer or char const string list of integers expression (for local vars) */ */ */ */

Na koji nain se simboli biljee u tablici simbola, bit e opisano u sljedeem poglavlju. Konano, definira se struktura pomou koje se biljei apstraktno sintaktiko stablo:
struct _treenode { int op; Type *expr_type; int i; int reg; int line; Symbol * sp; Node *left, *right, *third; };

/* /* /* /* /* /* /*

kind of node operator*/ type of rvalue used in expressions*/ constant value */ destination register */ line of source file*/ symbol - var, array, ref*/ the content of tree node */

Prvi lan ove strukture (op) opisuje vrstu AST vora. U Cmm koriste se sljedei tipovi vorova
enum _treeOp {OpNone,

/*int ili char konstante*/ OpIntConst, OpCharConst, /* identifikatori var, ptr, elementa niza, referenca niza, arg fun */ OpIdentifier,OpArrayElem, OpPtrIndirection, OpReferenceArg, OpArrayReference,

Semantika obrada

/* zapis naredbi */ OpAssign, OpIf, OpWhile, OpBreak, OpContinue, OpExprStmt, OpFor,OpStatList, /*poziv i povrat iz funkcije s listom argumenata */ OpFuncCall, OpReturn, OpArgList, /* ugraene funkcije */ OpPrint,OpPrintList, OpRead, /* binarni i unarni izrazi*/ OpVarAddress, OpCast, OpUnaryMinus, OpLogNot, OpPreIncr, OpPreDecr,OpPostIncr, OpPostDecr, OpPlus, OpMinus, OpMul, OpDiv, OpMod, OpLogOr, OpLogAnd, OpEq, OpNotEq, OpGr, OpGrEq, OpLess, OpLessEq, };

Ostali lanovi ine potpuni opis vora: expr_type - ako vor opisuje izraz, biljei se tip izraza. i - vrijednost cjelobrojne ili char konstante reg - oznaka registra koji sadri vrijednost izraza sp - pokaziva na simbol, ako vor sadri varijablu ili funkciju left, right i third pokazivai - kod zapisa izraza, left i right pokazuju dva operanda, a kod kontrolnih struktura pokazuju na izraze ili naredbe. Funkcije za formiranja stabla bit e opisane kasnije.

6. 2 Izvedba tablica simbola


Tablica simbola je struktura u kojoj se registrira proizvoljan broj simbola. Temeljni element tablice sadri ime simbola i ostale podatke koji odreuju znaanje simbola i njegovu upotrebu. Mogue su razliite izvedbe, ali uvijek je karakteristino da se odabire jedan element najee ime - koji slui za pretraivanje tablice. Interesira na suelje prema tablici simbola s dvije temeljne funkcije:
Symbol *lookup(char * name)

vraa pokaziva na simbol ako ime name postoji u tablici , a ako ne postoji vraa NULL.
Symbol *insert(Symbol * sym);

dodaje novi simbol u tablicu. Simbol je prethodno formiran dinamikom alokacijom, a sadraj mu se formira iz imena sym->name i ostalih elemenata.

6.2.1 Jednostavna tablica simbola


Najednostavniji nain implementacije tablice simbola je vezana lista. Oznaimo je sa symtable:
Symbol *symtable = NULL; /* symbol table: linked list */

Symbol *insert(Symbol * sym) {

/* umetni u tablicu */

Semantika obrada
Symbol *sp = lookup(sym->name);
if (sp = lookup(name) != 0)

sysError("Symbol %s already defined\n", sym->name); sym->next = symtable; /* umetni na poetak liste */ symtable = sym; /* novi poetak liste */ return sym; } Symbol *lookup(char* name) /* find s in symbol table */ { Symbol *sp; for (sp = symtable; sp != NULL; sp = sp->next) if (strcmp(sp->name, name)== 0) return sp; /* found */ return NULL; /* not found */ }

6.2.2 Efikasniji metod formiranja tablice binarno stablo i hash tablica


Implementacija tablice simbola pomou vezane liste nije prikladna u sluaju kada se analiziraju veliki programi u kojima moe biti vie tisua razliitih simbola. Problem je u funkciji za pretraivanje lookup(name), jer je usporedba stringova spora operacija. Za bri nain pretraivanja tablice koriste se dva naina implementacije tablice: pomou binarnog stabla, pomou hash tablice Pretraivanje binarnog stabla je brza operacija, ali pri umetanju u stablo moe postati nebalansirano stablo, stoga se ono mora realizirati kao AVL-stablo ili red-black-stablo. U praksi se najvie koristi tzv. otvorena hash tablica, koja se realizira kao niz pokazivaa na listu simbola. Identifikator tablice se deklarira sa
Symbol *table[HASHSIZE]; /* hash tablica za simbole */

Temeljna ideja hash tablice je u tome da se neki simbol imena name postavlja u listu table[i], gdje se indeks i odreuje pomou hash funkcije
indeks = hash(name);

Ukoliko ova funkcija daje indeks s jednolikom vjerojatnou za sve vrijednosti iz intervala [0, HASHSIZE-1], tada moemo oekivati da e sve liste imati jednak broj elemenata, a to znai da se veliina liste u kojoj se vri traenja smanjuje za faktor koji jednak HASHSIZE. Ne postoji idealna hash funkcija. Mi emo koristiti funkciju 5

Semantika obrada

unsigned hash(char * s) { unsigned hashval=0; while(*s != '\0') { hashval = *s + 31 * hashval; s++; } return hashval % HASHSIZE; }

koja daje zadovoljavajuu raspodjelu imena u tablici. Poetno se svi elementi tablice postavljaju na vrijednost 0.
#define HASHSIZE 155 /* size of hash table */ /* symbol hash table */

Symbol *table[HASHSIZE];

void inittable () /* initialize the symbol table */ { int i; for (i=0; i < HASHSIZE; ++i) table[i] = NULL; }

Sada se funkcije lookup() i insert() mogu implementirati na sljedei nain:


Symbol *lookup(char *name) /* vraa pokaziva na simbol iz tablice */ { Symbol *sp; for (sp = table[hash(name)]; sp != NULL; sp = sp->next) if (strcmp(sp->name, s) == 0) return sp; return NULL; } Symbol *insert (Symbol *sym) { unsigned hashindex; Symbol *sp; if (sp = lookup(name) != 0) sysError("Greska: postoji simbol imena %s", sym->name); else { hashindex = hash (name); sym->next = table[hashindex]; table[hashindex] = sym; } return(sym); }

6.2.3 Koritenje hash tablice za registriranje literalnih stringova


U programu se esto koriste literalni stringovi. Mogue je i da se isti string koristi vie puta. Korisno bi bilo prije generiranja kda registrirati sve literalne stringove u jednoj hash tablici. Na taj nain se moe utedjeti memorija, jer se isti stringovi ne registriraju viestruko. Potreba je takoer da se string registrira u tablici u onim jezicima koji dozvoljavaju da se literalne stringove koristiti kao reference konstante, primjerice u C jeziku je dozvoljeno pisati:
char *ps = "Hello";

U tom sluaju kompilator interno daje literalnom stringu ime primjerice _str_xx. i generira kod za sljedei zapis 6

Semantika obrada

char str_xx[6] = "Hello"; char *ps = str_xx;

U C-- kompilatoru stringovi se registriraju u hash tablici, kojoj temeljni element ima oblik
typedef struct string_data { char * str; char * name; struct string_data *next; } StringData; /* string */ /* interno ime za string*/

StringData * StringTable[HASHSIZE]; StringData *lookupStringTable(char * str); StringData *insertStringTable(char * str);

6.2.4 Izrada tablice za blok struktuirane jezike


Blok struktuirani zapisi mogu imati ista imena za razliite identifikatore. Pri tome vrijede pravila dosega imena identifikatora. Uzmimo primjer,
int a = 4; char b[7]; int fun(int x, int y) { int b; if (a == 0) return a; b = a +7; return b; } /* globalni doseg */ /* /* /* /* /* ime funkcije globalni doseg *) formalni parametri -lokalni doseg *) lokalna varijabla - b*) globalna var a */ lokalna var b */

Za jezike koji podravaju ovakovu blok strukturu, potrebno je tablicu simbola realizirati tako da se registrira doseg varijable, na nain da u tablici moe biti vie razliitih simbola istoga imena, ukoliko su oni iz razliitog dosega. To se moe postii tablicom u kojoj se uz simbole sprema i oznaka dosega, ili da se generira odvojena tablice za svaki doseg. Analizirat emo oba rjeenja. 1) Stvaranje odvojene tablice za svaki doseg Ideja: Kada "uemo u neki blok" treba stvoriti novu tablicu i zapamtiti tablicu iz prethodnog dosega. Kada "napustimo taj blok" tablica za taj doseg treba prestati biti aktivna. Oito je da se tablice slau po principu stoga. Definirajmo strukturu Scope, pomou koje emo pamtiti stog tablica:
struct scope { Scope *previous; Symbol *hash_table[HASHSIZE]; }; typedef struct scope Scope; /* globalne varijable za globalni i trenutni doseg */ Scope *current_scope, *global_scope;

/* pokaziva na prethodni doseg*/

Za stvaranje tablice pri ulasku u novi blok koristit emo funkciju enterScope();
Scope *enterScope(void)

Semantika obrada
{ unsigned i; Scope *scope = malloc(sizeof(Scope)); scope->previous = current_scope; current_scope = scope; for (i = 0; i < HASHSIZE; i ++) scope->hash_table [i] = 0; return scope; }

Nakon izlaska iz bloka, ponovo treba postati aktivan prethodni doseg. To vri funkcija
leaveScope()
void leaveScope(void) { current_scope = current_scope->previous; }

Brisanje tablice nekog dosega se postie funkcijom delete_scope().


static void delete_symbol(Symbol *sym) { free(sym); }

void delete_scope(Scope *scope) { if (scope != 0) { unsigned i; Symbol *sym, *next_sym; for (i = 0; i < HASHSIZE; i ++) for (sym=scope->hash_table[i]; sym != 0; sym = next_sym) { next_sym = sym->next; delete_symbol(sym); } free(scope); } }

Napomena: funkcija delete_scope() brie kompletnu tablicu za neki doseg. Ona se poziva kada vie nisu potrebni simboli iz tog dosega. Kod jedno-prolaznih kompilatora tablica nekog dosega se brie im se napusti blok, dok se kod vie-prolaznih kompilatora tablice briu tek nakon to je zavreno generiranje koda. Funkcije za traenje simbola sada kao argument primaju i oznaku dosega (pokaziva na trenutni Scope)
void insert(Symbol *sym); Symbol *lookup(const Scope *, const char *name); Symbol *lookup_in_this_scope(const Scope *, const char *name);

Funkcija lookup_in this scope() trai simbol u trenutno aktivnom bloku. Funkcija lookup() trai simbol u trenutno aktivnom bloku. Ako ga ne nae tada trai u tablicama koje su na stogu, zakljuno s tablicom koja predstavlja globalni doseg. Implementacija prethodnih funkcija se moe napraviti na sljedei nain:
static Symbol *search(Scope *scope, const char *name, unsigned index) { Symbol *sym; for (sym = scope->hash_table[index]; sym != 0; sym=sym->next) if (strcmp(sym->name, name) == 0) return sym; return 0; } void insert(Symbol *sym)

Semantika obrada
{ unsigned index = hash(name); if (search(current_scope, name, index) != NULL) error(0, "%s already declared in this scope", name); else { sym->next = scope->hash_table[index]; scope->hash_table [index] = sym; } Symbol *lookup(const Scope *scope, const char *name) { unsigned index = hash(name); Symbol *sym; do { if ((sym = search(scope, name, index)) != 0) return sym; } while ((scope = scope->previous) != NULL); return 0; } Symbol *lookup_in_this_scope(const Scope *scope, const char *name) { return search(scope, name, hash(name)); }

Primijetimo da se pri ovakovom nainu formiranja tablice simbola, u sintaktikom stablu mora uz svaki identifikator biljeiti i njegov doseg (scope). 2) Jedna hash tablica s dodatnim oznaavanjem dosega simbola Prethodno opisani nain formiranja i koritenja tablice je najopenitiji i moe se prilagoditi zahtjevima svakog programskog jeziku. Neto jednostavniji pristup, koji zahtijeva manje memorijskih resursa, je odabran za implementaciju kod C-- jezika. Lokalni simboli postoje samo unutar blokova, a blokovi su unutar neke funkcije. Nakon izlaska iz bloka, lokalni simboli se mogu odstraniti iz tablice. Ideja je da se koristi jedna hash tablica za sve dosege, a da se doseg numerira i pridjeli svakom simbolu.
struct _symbol { char *name; Type *type; int scope;
...........

/* lexem of symbol*/ /* type of symbol */ /* Global = 0, Param = 1, Local=2,3,4...

*/

Na taj nain se mogu razlikovati identifikatori koji imaju isto ime, a nalaze se u razliitim dosezima. Nakon parsiranja neke funkcije moe se odmah provesti daljnu semantiku analizu i generiranje koda. Nakon toga se mogu izbrisati svi elementi tablice s lokalnim dosegom.
Symbol * g_currFunction; int g_currScope = 0; static Symbol * SymTable[HASHSIZE]; /* pokaziva na simbol funkcije */ /* oznaka trenutnog dosega */ /* tablica simbola */

/*pomona funkcija za traenje simbola u

dosegu scope */

Semantika obrada

void enterScope() { g_currScope++; } void leaveScope() { removeScopeSym(g_currScope); /* remove all g_currScope syms from table*/ g_currScope--; } static Symbol *findInScope(int scope, char * name, unsigned hash) {/* returns symbol, if found in current scope or function parameter scope else return NULL */ Symbol * sp = NULL; if (g_currFunction && scope != Global) { /* local or parameters*/ for (sp = SymTable[hash]; sp; sp = sp->next) if ( (sp->scope == scope || sp->scope == Param) && strcmp(sp->name, name) == 0) return sp; /*already there*/ } else if(Global == scope) { /*global*/ for (sp = SymTable[hash]; sp; sp = sp->next) if (strcmp(sp->name, name) == 0) return sp; } return sp; } static void removeScopeSym(int scope) { Symbol * sp; int i; for (i = 0; i < HASHSIZE; i++) { sp = SymTable[i]; while(sp && sp->scope == scope) { SymTable[i] = sp->next; sp = SymTable[i]; } } } Symbol *insertSymTable(Symbol * sym) { unsigned hash; Symbol *sp; if(sym == NULL) sysError("Symbol not defined\n"); hash = sym_hash(sym->name); if(sp = findInScope(sym->scope, sym->name, hash)) { sysError("Symbol %s already defined\n", sym->name); } /* Add symbol to table */ sym->next = SymTable[hash]; SymTable[hash] = sym; } return sym;

10

Semantika obrada

Symbol *lookupSymTable(char * name) { Symbol * sp = NULL; unsigned hash = sym_hash(name); /* first look at local table, from head - to access deepest scope*/ for (sp = SymTable[hash]; sp; sp = sp->next) if (strcmp(sp->name, name) == 0) return sp; return sp; } /* Enter new function scope. */ Symbol * enterFunctionScope(Symbol * sp, Type *ret_type) { Symbol *sym; if (g_currFunction) yyerror("Cannot nest functions"); sym = lookupSymTable(sp->name); if(sym == NULL) { if(isPointerType(sp->type)) /*if return pointer*/ setArrOrPtrBaseType(ret_type, sp->type); else sp->type = ret_type; /*now convert sp type to function type*/ sp->type = mkFuncType(sp->type, NULL); sp->type->func.params=NULL; sp->addr = 0; sp->type->func.flag = ParamsNotDefined; sym = sp; insertSymTable(sym); } else if(sym->type->func.flag == ParamsAndBodyDef) sysError("Line:%d: function %s already defined", g_linenum, sp->name); else /*forward declaration*/ { /*now, check return type*/ /*formal arguments will be entered and checked later*/ if(!equalTypes(sym->type->subtype, ret_type)) sysError("Line:%d: function %s declared with different return type", g_linenum, sp->name); } /*now switch to local scope*/ g_currOffset = 0; g_currFunction = sym; g_currScope++; return sym; } /* Delete scope of local syms */ void exitFunctionScope() { int i; Symbol * sp; /*remove local symbols, keep globals*/ for (i = 0; i < HASHSIZE; i++) { sp = SymTable[i];

11

Semantika obrada
while(sp && sp->scope > Global) { SymTable[i] = sp->next; deleteType(sp->type); free(sp); sp = SymTable[i]; }

} g_currFunction = NULL; g_currOffset = 0; g_currScope = 0;

Primjetite da se nakon generiranja kda za neku Cmm funkciju, mogu izbrisati svi simboli iz lokalne tablice simbola.

6.3. Generiranje AST


Drugi znaajna podatkovna komponenta kompilatora je sintaktiko stablo. Za svaki znaajni korak u parsiranju treba predvidjeti generiranje prikladnog vora sintaktikog stabla. Najjednostavniji je prazan vor, koji sadri nita. Njega se formira funkcijom mkNode();
Type UnknownType = {0}; Node *mkNode(void) { Node *np = (Node *) malloc(sizeof(Node)); if (np == NULL) sysError("Out of Memory"); np->reg = 0; np->i = 0; np->sp = NULL; np->expr_type = &UnknownType; np->line = g_linenum; /*zapamti liniju izvornog koda*/ np->left =np->right = np->third = 0; return np; }

Ako je vor list koji sadri konstantu tipa t, formiramo ga funkcijom:


Node *addConstNode(int op, int t, int value) { Node *np = mkNode(); np->op = op; np->expr_type = mkBaseType(t); np->i = value; return np; }

Ako vor sadri varijablu, formira se funkcijom:


Node *addVarNode(char * name) { Node * np; Symbol * sp = lookupSymTable(name); if(sp == NULL) sysError("Line:%d: Identifier %s not declared", g_linenum, name); if(name != sp->name) /* free name pointer if different from*/ free(name); /* table pointer, as it is alloced in lex*/

12

Semantika obrada
np = mkNode(); np->expr_type = copyType(sp->type); /* propagate type */ np->sp = sp; if(sp->type->kind == REFERENCE_TYPE) np->op = OpReferenceArg; else if(sp->type->kind == ARRAY_TYPE) np->op = OpArrayReference; else np->op = OpIdentifier; return np;

Specijalni sluaj je vor za literalni string, kojeg pretvaramo u interno imenovanu referencu niza:
Node *addLitStringNode(char * name) { Node * np; Symbol * sp = lookupSymTable(name); if(sp == NULL) /*this will never happen*/ sysError("Line:%d: Dummy string not declared", g_linenum); np = mkNode(); np->op = OpArrayReference; np->expr_type = copyType(sp->type); np->sp = sp; return np; }

U sluaju kada je potrebno izvriti pretvorbu tipova, formira se vor (OpCast) pomou funkcije insertCastNode(),
Node *insertCastNode(Node * np, Type * expr_type) { Node * newp = mkNode(); newp->op = OpCast; newp->expr_type = expr_type; newp->left = np; return newp; }

Sintaktiko stablo izraza se zapisuje koristei left i right pokazivae vora, koji se inicijaliziraju tako da pokazuju na operande izraza:
Node *addBinNode(int op, Node *left, Node *right) { Node *np = mkNode(); np->op = op; np->expr_type = &UnknownType; np->left = left; np->right = right; np->third = NULL; return np; }

Vrijednost tipa rezultirajueg izraza (expr_type), bit e odreena kasnije, u semantikoj analizi izraza.

13

Semantika obrada Sintaktiko stablo veine naredbi se formira se pomou funkcije addBinNode(), ali za for i if-else naredbe koristi se funkcija addTriNode():
Node *addTriNode(int op, Node *left, Node *right, Node *third) { Node *np = mkNode(); np->op = op; np->expr_type = &UnknownType; np->left = left; np->right = right; np->third = third; return np; }

6.4. Gramatike akcije za izgradnju sintaktikog stabla i tablice simbola


Sintaktiko stablo se izgrauje u procesu parsiranja. U primjeru izrade kompilatora Cmm, iji je izvorni kod dan u Dodatku, leksiki analizator je generiran pomou programa flex, a parser je generiran pomou programa yacc (byacc). Flex-generirani leksiki analizator vraa parseru, pomou funkcije yylex(), tokene (koji su definirani u datoteci y.tab.h) i alocira imena identifikatora i stringova te daje trenutnu poziciju u izvornom kodu. Primjenom programa yacc
c:> yacc d cmm.y

dobiju se dvije datoteke: y.tab.c i y.tab.h. Datoteka y.tab.h. slui za vezu s leksikim analizatorom ( u njoj je definicija tokena). Datoteka y.tab.c sadri funkciju yyparse() koja sadri parser. Rezultat poziva ove funkcije je izgradnja sintaktikog tabla, izrada tablice simbola. Nakon parsiranja svake funkcije vri se semantika analiza te funkcije. Ako je semantika analiza uspjena vri se generiranje asemblerskog koda. Yacc datoteka zapoima s potrebnim deklaracijama:
%{ #include #include #include #include "cmm.h" "cmm_sym.h" "cmm_tree.h" "cmm_comp.h"

/*Cmm - Simple language with C constructs but with limited types */ extern int yylex(); extern int g_linenum; extern int g_parse_error; %}

/* Zatim slijedi specifikacija unije semantikih atributa: */


%union { int ivalue; char *str; Symbol *sp; Node *np;

14

Semantika obrada
Type *tp; InitVal *initval; IntList *ilist; VarList *vlist; }

/* Pomou deklaracija iz ove unije, vri se deklariranje semantikih atributa terminalnih i neterminalnih simbola. */
%token WHILE_TOK BREAK_TOK CONTINUE_TOK IF_TOK ELSE_TOK FOR_TOK %token VOID_TOK RETURN_TOK PRINT_TOK READ_TOK INT_TOK CHAR_TOK %token <ivalue> INT_LITERAL CHAR_LITERAL %token <str> ID_TOK STRING_LITERAL %token <ivalue> '!' '-' '+' '*' '/' '=' '(' ',' '[' ']' %token <ivalue> GE_TOK LE_TOK EQ_TOK '>' '<' NE_TOK OR_TOK AND_TOK INCR_TOK DECR_TOK %type %type %type %type %type %type %type %type <np> <np> <sp> <tp> <vlist> <initval> <ivalue> <ilist> expression expropt lvalexpr arglist printlist statement statementlist simplestatement blok decl_statement variable dir_variable param_list funcsym type variablelist local_varlist init_value int_const intlist

/* Deklaracija prioriteta i asocijativnosti operatora */


%right ELSE_TOK %right '=' %left OR_TOK %left AND_TOK %left GE_TOK LE_TOK EQ_TOK NE_TOK '>' '<' %left '+' '-' %left '*' '/' '%' %right UMINUS %left DECR_TOK INCR_TOK %left '[' '('

/* Definiranje produkcijskih pravila s pripadnim semantikim akcijama zapoima nakon znaka %% */


%%

/* Startni simbol je "program" koji se definira kao niz deklaracija globalnih varijabli i funkcija: */
program: /* null */ | program function | program global_decl ;

/* Deklaracije globalnih varijabli se vre prema sljedeim pravilima: */


global_decl: type variablelist ';' ; variablelist: variable | variable '=' init_value | variablelist ',' variable { insertGlobalVars($1, $2); } { $$ = addVarList($1, NULL, NULL); { $$ = addVarList($1, NULL, $3); { $$ = addVarList($3, $1, NULL); } } }

15

Semantika obrada
| ; variablelist ',' variable '=' init_value { $$ = addVarList($3, $1, $5);} {$$ = $1;} {$$ = addPointerDecl($2);} { $$ = makeVarDecl($1); } { $$ = addArrayDecl( $1, 0); } { $$ = addArrayDecl($1, $3); }

variable : dir_variable | '*' variable ; dir_variable: ID_TOK | dir_variable '[' ']' | dir_variable '[' int_const ']' ; init_value: int_const | STRING_LITERAL | '{' intlist '}' ;

{ $$ = makeInitVal( InitInt, (void *)$1);} { $$ = makeInitVal( InitString, $1); } { $$ = makeInitVal( InitIntArray, $2); }

/* Funkcije se deklariraju: 1) kao prototip ili 2) s tijelom funkcije koji nazivamo blok. Ukoliko je definiran blok funkcije, tada se pomou stabla, koje opisuje blok, vri semantika analiza i generiranje koda za svaku pojedinu funkciju. */
function: function_header blok { typeCheck($2); generateCode($2); exitFunctionScope();} | function_header ';' { declareFuncPrototype(g_currFunction); exitFunctionScope();} ; function_header: type funcsym '(' param_list ')' {enterFunctionScope($2, $1); insertFuncParams($4, g_currFunction); } ; funcsym: ID_TOK { $$ = makeVarDecl($1); } | '*' funcsym { $$ = addPointerDecl($2); } ; param_list: /* null */ { $$ = NULL;} | type '&' ID_TOK { $$ = addParamList($1, REFERENCE_TYPE, mkSym($3), NULL); } | type variable { $$ = addParamList($1, $1->kind, $2, NULL); } | param_list ',' type '&' ID_TOK { $$ = addParamList($3, REFERENCE_TYPE, mkSym($5), $1); } | param_list ',' type variable { $$ = addParamList($3, $3->kind, $4, $1); } ; blok: '{' {enterScope();} statementlist {leaveScope();} | '{' '}' {$$ = NULL;} ; '}' {$$ = $3;}

/* Blok ine deklaracije lokalnih varijabli i naredbe */


statementlist: statement | decl_statement | statementlist statement | statementlist decl_statement ; {$$ {$$ {$$ {$$ = = = = appendStatList(NULL, $1);} appendStatList(NULL, $1);} appendStatList($1, $2); } appendStatList($1, $2); }

/* Deklaracija lokalnih varijabli se vri slino kao i globalne varijable, razlika je u inicijalizaciji. Varijable se inicijaliziraju pomou izraza, a ne pomou konstanti. */
decl_statement: type local_varlist ';' { $$ = declareLocalVarList($1, $2); } ; local_varlist: variable { $$ = appendLocVarList($1, NULL, NULL); } | variable '=' expression { $$ = appendLocVarList($1, NULL, $3); } | local_varlist ',' variable { $$ = appendLocVarList($3, $1, NULL); } | local_varlist ',' variable '=' expression {$$ = appendLocVarList($3, $1, $5); } ;

16

Semantika obrada

/* Naredbe se definiraju na isti nain kao i u C jeziku (sve osim naredbi print i read) */
statement: blok {$$ = $1;} | simplestatement {$$ = $1;} | WHILE_TOK '(' expression ')' statement { $$ = addBinNode(OpWhile, $3, $5); } | FOR_TOK '(' expropt ';' expropt ';' expropt ')' statement { $$ = addTriNode(OpFor, $3, $5, addBinNode(OpNone, $7, $9));} | IF_TOK '(' expression ')' statement ELSE_TOK statement { $$ = addTriNode(OpIf,$3,$5,$7); } | IF_TOK '(' expression ')' statement %prec ELSE_TOK { $$ = addTriNode(OpIf,$3,$5,NULL); } | error ';' { $$ = NULL; yyerrok; } ; simplestatement: ';' { $$ = NULL; } | expression ';' { $$ = addBinNode(OpExprStmt, $1, NULL);} | BREAK_TOK ';' { $$ = addBinNode(OpBreak, NULL, NULL); } | CONTINUE_TOK ';' { $$ = addBinNode(OpContinue, NULL, NULL); } | RETURN_TOK expression ';' { if (g_currFunction == NULL) yyerror("return not in function"); else if(isVoidFunctionType(g_currFunction->type)) yyerror("Cant return value in procedure"); else $$ = addBinNode(OpReturn, $2, NULL); } | RETURN_TOK ';' { if (g_currFunction == NULL) yyerror("return not in function"); else if(! isVoidFunctionType(g_currFunction->type)) yyerror("Must return a value in function"); else $$ = addBinNode(OpReturn, NULL, NULL); } | PRINT_TOK printlist ';' {$$ = addBinNode(OpPrint, $2, NULL); } | READ_TOK lvalexpr ';' {$$ = addBinNode(OpRead, $2, NULL); } ; printlist: expression | printlist ',' expression ; {$$ = appendNodeList(OpPrintList, $1, NULL);} {$$ = appendNodeList(OpPrintList, $3, $1);}

/* Izrazi se definiraju jednostavnije nego u C jeziku, jer je ogranien oblik lijeve strane izraza pridjele vrijednosti */
lvalexpr : ID_TOK | lvalexpr '[' expression ']' | '*' lvalexpr ; expression: INT_LITERAL { | CHAR_LITERAL { | STRING_LITERAL { | ID_TOK '(' arglist ')' { | lvalexpr | '(' expression ')' | lvalexpr '=' expression | expression '+' expression | expression '-' expression | expression '*' expression | expression '%' expression | expression '/' expression | expression '<' expression | expression '>' expression $$ $$ $$ $$ { $$ = addVarNode($1); } { $$ = appArrayNode($1, $3); } { $$ = addBinNode(OpPtrIndirection, $2, NULL);}

= addConstNode(OpIntConst, INT_TYPE, $1); } = addConstNode(OpCharConst, INT_TYPE, $1); } = addLitStringNode(makeStrVar($1));} = addBinNode(OpFuncCall, addVarNode($1), $3); } { $$ = $1;} { $$ = $2; } { $$ = addBinNode(OpAssign, $1, $3); } { $$ = addBinNode(OpPlus, $1, $3); } { $$ = addBinNode(OpMinus, $1, $3); } { $$ = addBinNode(OpMul, $1, $3); } { $$ = addBinNode(OpMod, $1, $3); } { $$ = addBinNode(OpDiv, $1, $3); } { $$ = addBinNode(OpLess, $1, $3); } { $$ = addBinNode(OpGr, $1, $3); }

17

Semantika obrada
| expression GE_TOK expression { $$ = | expression LE_TOK expression { $$ = | expression NE_TOK expression { $$ = | expression EQ_TOK expression { $$ = | expression OR_TOK expression { $$ = | expression AND_TOK expression { $$ = | '-' expression %prec UMINUS { $$ = | '!' expression %prec UMINUS { $$ = | INCR_TOK lvalexpr %prec UMINUS { $$ = | DECR_TOK lvalexpr %prec UMINUS { $$ = | '&' lvalexpr %prec UMINUS { $$ = | lvalexpr INCR_TOK { $$ = | lvalexpr DECR_TOK { $$ = ; expropt: /*null*/ {$$ = NULL;} | expression {$$ = $1;} ; addBinNode(OpGrEq, $1, $3); } addBinNode(OpLessEq, $1, $3); } addBinNode(OpNotEq, $1, $3); } addBinNode(OpEq, $1, $3); } addBinNode(OpLogOr, $1, $3); } addBinNode(OpLogAnd, $1, $3); } addBinNode(OpUnaryMinus, $2, NULL); } addBinNode(OpLogNot, $2, NULL); } addBinNode(OpPreIncr, $2, NULL); } addBinNode(OpPreDecr, $2, NULL); } addBinNode(OpVarAddress, $2, NULL);} addBinNode(OpPostIncr, $1, NULL); } addBinNode(OpPostDecr, $1, NULL); }

/* Lista argumenata funkcije se registrira kao lista izraza*/


arglist: /* null*/ | expression | arglist ',' expression ; {$$ = NULL; } {$$ = addBinNode(OpArgList, $1, NULL);} {$$ = addBinNode(OpArgList, $3, $1);}

/* Cmm poznaje samo 3 prosta tipa: int, char i void . Korisniki tipovi nisu implementirani*/
type: | | ; INT_TOK CHAR_TOK VOID_TOK {$$ = mkIntType();} {$$ = mkCharType();} {$$ = mkVoidType();}

int_const: INT_LITERAL | CHAR_LITERAL ; intlist: int_const | intlist ',' int_const ; %%

{$$ = appendIntList($1, NULL); } {$$ = appendIntList($3, $1); }

/* Funkcija dojave greke yyerror() ispisuje broj linije izvornog koda i poruku o greki te biljei broj greaka u varijabli g_parse_error */
void yyerror(char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stdout,"Line %d:", g_linenum); vfprintf(stdout, fmt, args); fprintf(stdout,"\n"); va_end(args); g_parse_error++; } /*Kraj datoteke cmm.y*/

6.5. Kontrola i pretvorba tipova

18

Semantika obrada Unutar postupka parsiranja (funkcija yyparse()) izgrauje se sintaktiko stablo za svaku funkciju, i zatim pozivaju funkcije typeCheck(np); i generateCode(np), gdje np predstavlja korijen sintaktikog stabla funkcije.
function: function_header blok { typeCheck($2); generateCode($2); deleteScope();}

Funkcija typeCheck($2)vri semantiku analizu, a funkcija generateCode($2) vri generiranje


koda. U semantikoj analizi vri se:

provjera tipova izraza i odreivanje tipa kojim rezultiraju izrazi (expr_type), ako je potrebno, dodaju se vorovi za pretvorbu tipa (OpCast), provjera tipa Lvrijednosti kod izraza pridjele vrijednosti te inkrement i dekrement operatora, provjera tipova argumenata i parametara funkcije, za argumente funkcije, koji se prenose po referenci, provjerava se da li se argumentu moe odrediti potrebna adresa i dodaje se vor OpVarAddress

Konani rezultat semantike analize je da se dobije "dekorirano" sintaktiko stablo s ispravno odreenim expr_type i dodanim granama za OpCast i OpVarAdress.

bit e dopisano

6.6 Generiranje - koda


U kompilatoru Cmm generiranje koda vri funkcija
void generateCode(Node * np) { if(g_parse_error) return; clearUsedRegs(); assignRegisters(np); funcProlog(); contList = NULL; exitList = NULL; generateAsm(np); funcEpilog(); delAllNodes(np); }

/* raspodjela registara Pentiuma*/ /* generiranje koda */

Kasnije e biti pokazano kako se vri generiranje koda.

19

You might also like