Прављење синтаксног анализатора Синтаксни анализатор Ручно: анализатор са рекурзивним спуштањем Уз помоћ алата: сличност са интерпретером 1
Синтаксни анализатор са рекурзивним спуштањем (енгл. recursive-descent) Има по једну (међурекурзивну) функцију за сваки нетерминал (синтаксну класу) Унутар сваке функције switch по врсти улазног симбола са по једном case клаузулом за сваку продукцију Синоним: анализа са предвиђањем (predictive) Ограничење: Ради добро само ако први терминални симбол у сваком подизразу једнозначно одређује продукцију Ако то није случај, постоји конфликт 2
Пример 1 (1/3) Спецификација граматике Направити синтаксни анализатор са рекурзивним спуштањем за задату граматику Нетерминални симболи: S, E, L Дефинишу дозвољене исказе и изразе у језику Терминали: let, in, begin, print, end, ;, =, num Кључне речи и оператори у језику S let E in S S begin S L S print E L end L ; S L E num = num 3
Пример 1 (2/3) Програм S let E in S S begin S L S print E L end L ; S L E num = num enum token {LET, IN, BEGIN, END, PRINT, SEMI, NUM, EQ}; enum token gettoken(); enum token tok; void advance() { tok = gettoken(); } void eat(enum token t) { if (tok == t) advance(); else error(); } void S() { switch (tok) { case LET: eat(let); E(); eat(in); S(); break; case BEGIN: eat(begin); S(); L(); break; case PRINT: eat(print); E(); break; default: error(); } } 4
Пример 1 (3/3) Програм S let E in S S begin S L S print E L end L ; S L E num = num void L() { switch (tok) { case END: eat(end); break; case SEMI: eat(semi); S(); L(); break; default: error(); } } void E() { eat(num); eat(eq); eat(num); } void main() { advance(); S(); } 5
Примери са конфликтом E a b T E a c G Први терминални симбол не одређује продукцију. Али одређују прва два За неке граматике треба више од једног терминалног симбола за предвиђање продукције Није суштински проблем, парсер са рекурзивним спуштањем се оваквом случају релативно лако прилагођава 6
Примери са конфликтом E a b T E a c G Први терминални симбол не одређује продукцију. void E() { switch (tok) { case A_TOK: eat(a_tok); switch (tok) { case B_TOK: eat(b_tok); T(); break; case C_TOK: eat(c_tok); G(); break; } break; } } 7
Примери са конфликтом E a T x E a T z Први терминални симбол не одређује продукцију. И не можемо знати коју продукцију да изаберемо пре нетерминала Т Формалније речено: FIRST(a T x) се преклапа са FIRST(a T y) За низ терминала и нетерминала γ, скуп FIRST чине сви терминали којима започињу реченице развијене из γ Нпр. за γ = T * F у некој граматици, FIRST(γ) = {id, num, (} 8
Примери са конфликтом E a T x E a T z E a T E E x E z Решење конфликта Први терминални симбол не одређује продукцију. И не можемо знати коју продукцију да изаберемо пре нетерминала Т Формалније речено: FIRST(a T x) се преклапа са FIRST(a T y) За низ терминала и нетерминала γ, скуп FIRST чине сви терминали којима започињу реченице развијене из γ Нпр. за γ = T * F у некој граматици, FIRST(γ) = {id, num, (} 9
Примери са конфликтом E a T x E a T E a T E E x E ε Решење конфликта Слично као мало пре Симбол E је поништив (nullable) ако се из њега може развити празан низ Сада нам треба и FOLLOW(T) скуп, да бисмо знали да ли да применимо редукцију која поништава неки нетерминални симбол Скуп FOLLOW(X) је скуп терминала који у реченицама језика могу пратити нетерминал X 10
Примери са конфликтом E a T b c T b T ε Не можемо разликовати прву и другу продукцију T FIRST(T) и FOLLOW(T) скуп се преклапају 11
Примери са конфликтом E a T b c T b T ε E a b b c E ab c Решење конфликта Не можемо разликовати прву и другу продукцију T FIRST(T) и FOLLOW(T) скуп се преклапају Решење конфликта може увести нови конфликт (али другачије врсте) 12
Алгоритам за одређивање FIRST, FOLLOW и NULLABLE Initialize FIRST and FOLLOW to empty sets, and nullable to all false. for each terminal symbol Z FIRST[Z] = {Z} repeat for each production X Y 1 Y 2... Y k for each i from 1 to k, each j from i+1 to k if all the Yi are nullable then nullable[x] true if Y 1... Y i-1 are all nullable then FIRST[X] FIRST[X] U FIRST[Y i ] if Y i+1... Y k are all nullable then FOLLOW[Y i ] FOLLOW[Y i ] U FOLLOW[X] if Y i+1... Y j-1 are all nullable then FOLLOW[Y i ] FOLLOW[Y i ] U FOLLOW[Y j ] until FIRST, FOLLOW, and nullable did not change in this iteration. 13
Пример са конфликтима S E $ E E + T T T * F F id E E - T T T / F F num E T T F F ( E ) Лева рекурзија Специјалан случај преклапања FIRST скупова две продукције 14
Уклањање леве рекурзије Опште правило за преписивање израза са левом рекурзијом у изразе са десном рекурзијом Примењено на посматрани пример Početni skup produkcija X X γ 1 X X γ 2 X α 1 X α 2 Rezultat transformacije X α 1 X X α 2 X X γ 1 X X γ 2 X X α -не садржи X γ -не почиње са X Језик након трансформације: S E $ E T E T F T E + T E T * F T F id E - T E T / F T F num E T F ( E ) 15
Садржаји скупова Поред трансформације језика, потребно је одредити садржаје скупова За сваки нетерминал: nullable, FIRST и FOLLOW Симбол nullable FIRST FOLLOW S false ( id num E false ( id num ) $ E' true + - ) $ T false ( id num ) + - $ T' true * / ) + - $ F false ( id num ) * / + - $ 16
Интерпретер са рекурзивним спуштањем (1/2) F id F num F ( E ) enum token {EOF, ID, NUM, PLUS, MINUS,...}; union tokenval {string id; int num;...}; enum token tok; union tokenval tokval; int lookup(string id) {...} int F_follow[] = {RPAREN, TIMES, DIVIDE, PLUS, MINUS, EOF, -1}; int F(void) { switch (tok) { case ID: {int i = lookup(tokval.id); advance(); return i;} case NUM: {int i = tokval.num; advance(); return i;} case LPAREN: eat(lparen); { int i = E(); eatorskipto(rparen, F_follow); return i; } case EOF: default: printf( Expected ID, NUM, or ( ); skipto(f_follow); return 0; } } 17
Интерпретер са рекурзивним спуштањем (2/2) int T_follow[] = {RPAREN, PLUS, MINUS, EOF, -1}; int T(void) { switch (tok) { case ID: case NUM: case LPAREN: return Tprime(F()); default: printf( Expected ID, NUM, or ( ); skipto(t_follow); return 0; } } int Tprime(int a) { switch (tok) { case TIMES: eat(times); return(a*f()); case RPAREN: case PLUS: case MINUS: case EOF: return a; default:... } } void eatorskipto(int expected, int* stop) { if (tok == expected) eat(expected); else {printf(...); skipto(stop);} } T F T T * F T T / F T T Originalne produkcije: T T * F T T / F T F T se prenosi kroz a 18
Напомене уз претходни Це код Семантика Симболи ID и NUM носе вредности типа sting и int Подразумева се постојање табеле Која пресликава ID-ове на NUM-ове (целе бројеве) Тип придружен нетерминалима E, T, F, итд. је int Семантичке акције се лако реализују Проблем: Семантичка акција за вештачки симбол T Нпр. у продукцији T *FT недостаје леви операнд за * Један начин је да се леви операнд проследи као аргумент Функција T га узима са F() и прослеђује функцији Tprime Развој симбола у празан стринг се препознаје појавом неког од симбола из скупа FOLLOW 19
Однос интерпретера и синтаксног анализатора Постоји велика сличност између њих Ако се користи Bison, разликују се само акције Интерпретер: акције доводе до директног извршења Нпр. исказ доделе вредности заиста уписује вредност у променљиву Исказ штампања (print) садржаја променљиве га заиста приказује на монитору, итд. Синтаксни анализатор: акције изграђују стабло апстрактне синтаксе додавањем нових чворова То стабло ће касније у низу трансформација бити преведено у машински код Тек при његовом извршење добијају се резултати 20
Граматика праволинијских програма SLP интерпретер и синтаксни анализатор за SLP Граматика SLP задата доњим низом продукција Преузимањем структура из ручно писаног SLP интерпретера, долази се до Bison(Yacc) спецификација У случају синтаксног анализатора те структуре података представљају апстрактну синтаксу S S; S E id L E S id := E E num L L, E S print (L) E E B E E S, E B + - * / E (E) 21
Спецификација SLP интерпретера (1/2) %{ #include "table.h" extern table tab; %} %union {int ival; string sval;} %token <sval> ID %token <ival> INT %token ASSIGN PRINT LPAREN RPAREN %type <ival> exp %right SEMICOLON %left MINUS PLUS %left TIMES DIVIDE %start prog %% prog: stm 22
Спецификација SLP интерпретера (2/2) stm: stm SEMICOLON stm stm: ID ASSIGN exp {update(&tab, $1, $3);} stm: PRINT LPAREN exps RPAREN {printf("\n");} exps: exp {printf("%d ", $1);} exps: exps COMMA exp {printf("%d ", $3);} exp: INT {$$ = $1;} exp: ID {$$ = lookup(tab, $1);} exp: exp PLUS exp {$$ = $1 + $3;} exp: exp MINUS exp {$$ = $1 - $3;} exp: exp TIMES exp {$$ = $1 * $3;} exp: exp DIVIDE exp {$$ = $1 / $3;} exp: stm COMMA exp {$$ = $3;} exp: LPAREN exp RPAREN {$$ = $2;} // $1, $2, itd. se odnose na definicione članove produkcije 23
Спецификација SLP синтаксног анализатора (1/2) %{ extern A_stm rootprg; %} %union {int num; string id; A_stm stm; A_exp exp; A_expList explist;} %token <num> INT %token <id> ID %token ASSIGN PRINT LPAREN RPAREN %type <stm> stm prog %type <exp> exp %type <explist> exps %left SEMICOLON %left MINUS PLUS %left TIMES DIVIDE %start prog %% prog: stm {$$=$1; rootprg=$1;} 24
Спецификација SLP синтаксног анализатора (2/2) stm: stm SEMICOLON stm {$$=A_CompoundStm($1,$3);} stm: ID ASSIGN exp {$$=A_AssignStm($1,$3);} stm: PRINT LPAREN exps RPAREN {$$=A_PrintStm($3);} exps: exp exps: exp COMMA exps {$$=A_LastExpList($1);} {$$=A_PairExpList($1,$3);} exp: INT {$$=A_NumExp($1);} exp: ID {$$=A_IdExp($1);} exp: exp PLUS exp {$$=A_OpExp($1,A_plus,$3);} exp: exp MINUS exp {$$=A_OpExp($1,A_minus,$3);} exp: exp TIMES exp {$$=$$=A_OpExp($1,A_times,$3);} exp: exp DIVIDE exp {$$=$$=A_OpExp($1,A_div,$3);} exp: stm COMMA exp {$$=A_EseqExp($1,$3);} exp: LPAREN exp RPAREN {$$=$2;} 25
Апстрактна синтакса за Tiger (1/2) /* Function Prototypes */ A_var A_SimpleVar(A_pos pos, S_symbol sym); A_var A_FieldVar(A_pos pos, A_var var, S_symbol sym); A_var A_SubscriptVar(A_pos pos, A_var var, A_exp exp); A_exp A_VarExp(A_pos pos, A_var var); A_exp A_NilExp(A_pos pos); A_exp A_IntExp(A_pos pos, int i); A_exp A_StringExp(A_pos pos, string s); A_exp A_CallExp(A_pos pos, S_symbol func, A_expList args); A_exp A_OpExp(A_pos pos, A_oper oper, A_exp left, A_exp right); A_exp A_RecordExp(A_pos pos, S_symbol typ, A_efieldList fields); A_exp A_SeqExp(A_pos pos, A_expList seq); A_exp A_AssignExp(A_pos pos, A_var var, A_exp exp); A_exp A_IfExp(A_pos pos, A_exp test, A_exp then, A_exp elsee); A_exp A_WhileExp(A_pos pos, A_exp test, A_exp body); A_exp A_ForExp(A_pos pos, S_symbol var, A_exp lo, A_exp hi, A_exp body); A_exp A_BreakExp(A_pos pos); A_exp A_LetExp(A_pos pos, A_decList decs, A_exp body); A_exp A_ArrayExp(A_pos pos, S_symbol typ, A_exp size, A_exp init); 26
Апстрактна синтакса за Tiger (2/2) A_dec A_FunctionDec(A_pos pos, A_fundecList function); A_dec A_VarDec(A_pos pos, S_symbol var, S_symbol typ, A_exp init); A_dec A_TypeDec(A_pos pos, A_nametyList type); A_ty A_NameTy(A_pos pos, S_symbol name); A_ty A_RecordTy(A_pos pos, A_fieldList record); A_ty A_ArrayTy(A_pos pos, S_symbol array); A_field A_Field(A_pos pos, S_symbol name, S_symbol typ); A_fieldList A_FieldList(A_field head, A_fieldList tail); A_expList A_ExpList(A_exp head, A_expList tail); A_fundec A_Fundec(A_pos pos, S_symbol name, A_fieldList params, S_symbol result, A_exp body); A_fundecList A_FundecList(A_fundec head, A_fundecList tail); A_decList A_DecList(A_dec head, A_decList tail); A_namety A_Namety(S_symbol name, A_ty ty); A_nametyList A_NametyList(A_namety head, A_nametyList tail); A_efield A_Efield(S_symbol name, A_exp exp); A_efieldList A_EfieldList(A_efield head, A_efieldList tail); 27
Yacc спецификација за Tiger exp: lvalue {$$=A_VarExp(EM_tokPos,$1);} NIL {$$=A_NilExp(EM_tokPos);} INT {$$=A_IntExp(EM_tokPos,$1);} STRING {$$=A_StringExp(EM_tokPos,$1);} NEG INT {$$=A_OpExp(EM_tokPos,A_minusOp,A_IntExp(EM_tokPos,1), A_IntExp(EM_tokPos,$2));} functioncall {$$=$1;} arithmetic {$$=$1;} comparison {$$=$1;} booleanexp {$$=$1;} recordcreation {$$=$1;} arraycreation {$$=$1;} ; lvalue: ID {$$=A_SimpleVar(EM_tokPos,S_Symbol($1));} lvalue DOT ID {$$=A_FieldVar(EM_tokPos,$1,S_Symbol($3));} ID LBRACK exp RBRACK {$$=A_SubscriptVar(EM_tokPos, A_SimpleVar(EM_tokPos, S_Symbol($1)),$3);} ;... 28
Пример стабла синтаксне анализе Програм queens...\tigerc.exe..\testcases\queens_book.tig Parsing successful! letexp( declist( functiondec( fundeclist( fundec(try, fieldlist( field(c, int, TRUE), fieldlist()), iffexp( opexp( EQUAL, varexp( simplevar(c)), varexp( simplevar(n))), callexp(printboard, explist()), forexp(r, intexp(0), opexp( MINUS, varexp( simplevar(n)), intexp(1)), iffexp( iffexp( iffexp( opexp( EQUAL, varexp( subscriptvar( simplevar(row), varexp( simplevar(r)))), intexp(0)), 29
Мала илустрација још једног алгоритма за синтаксну анализу Помери и редукуј (енгл. shift-reduce) парсер (LR). Такви парсери се зову још и Од доле ка горе (енгл. bottom-up) због начина на који се гради стабло синтаксне анализе. 30
Синтаксни анализатор Синтаксни анализатор као улазну информацију користи табелу униформних симбола, чији сваки симбол улази на стек. Стек садржи скуп симбола над којима се тренутно ради у фази синтаксне анализе и интерпретације. Елемент са стека може бити унет у табелу униформних симбола и обратно. 31
Пример рада LR парсера 1. S E $ 2. E T 4. T id 3. E E + T 5. T (E ) ( (id (T (E (E + (E + id (E + T (E (E) T E E$ S (id + id)$ id + id)$ + id)$ + id)$ + id)$ id)$ )$ )$ )$ $ $ $ shift shift reduce T->id reduce E->T shift shift reduce T->id reduce E->E + T shift reduce T->(E) reduce E->T shift reduce S->E$ 32
Општи облик редукција (1/2) LABELA: PRETHODNI RUTINA NOVI VRH SLEDEĆA VRH STEKA AKCIJA STEKA REDUKCIJA Лабела је опциона Претходни врх стека Изостављен увек се поклапа Симбол СИНТАКСНИ као што је идентификатор или литерал - поклапа се са неким симболом овог типа било који (*) поклапа се са симболом било ког типа симболички представљена кључна речи као WHILE" или "IF" 33
Општи облик редукција (2/2) LABELA: PRETHODNI RUTINA NOVI VRH SLEDEĆA VRH STEKA AKCIJA STEKA REDUKCIJA Рутина акција назив рутине акције (опционо) Нови врх стека: знак празнине или нула - нема промене одстранити врх стека (поворку која се поклапа) синтаксни тип, кључна реч или елеменат стека - заменити претходни врх стека са овим елементом узети следећи униформни симбол из табеле униформних симбола и ставити га на врх стека 34