Генератор лексичких и синтаксних анализатора Пример спецификације језика Flex генератор лексичких анализатора Bison генератор синтаксних анализатора 1
Граматика => Језик (Бесконачан) скуп реченица 2
Граматика <=> Језик (Бесконачан) скуп реченица 3
Различите граматике могу дефинисати исти језик S -> E E -> T T -> iks E -> E plus T S -> E E -> T T -> iks E -> T plus E S -> E E -> T T -> iks E -> E plus E S -> E S -> iks S -> S plus iks E -> iks E -> E plus E Jezik: iks iks plus iks iks plus iks plus iks iks plus iks plus iks plus iks... 4
Синтаксни анализатор јесте део језика Низ терминалних симбола A(J) G није део језика 5
Синтаксни анализатор јесте део језика Низ терминалних симбола A(G) J није део језика 6
Синтаксни анализатор јесте део језика Низ терминалних симбола A(G) Да ли ово имамо на улазу? J није део језика 7
Синтаксни анализатор јесте део језика Низ терминалних симбола A(G) Да ли ово имамо на улазу? Не, имамо низ знакова J није део језика 8
Синтаксни анализатор Два приступа: Написати граматику језика код које су знакови терминални симболи. Анализу обавити у два корака: Низ знакова L(G) јесте терминални симбол (који?) низ терминалних симбола Лексички анализатор није терминални симбол 9
Лексички анализатор Позови лексер Низ знакова L(G) Добави наредни терминални симбол A(G) 10
Лексички анализатор Слично као синтаксни анализатор Другачија граматика - једноставнија Терминални симболи су знаци Регуларна граматика (нижег нивоа од контекстно слободних граматика): G (N, T, Σ, P) Где је P скуп смена (продукција) које могу имати један од следећа три облика: B -> α, где B припада N, а αприпада T B -> αc (или B -> Cα), где C припада N B -> ε, где је ε празан знаковни низ 11
Лексички анализатор Алгоритам за одређивање да ли је неки улаз део језика дефинисаног регуларном граматиком (лексички исправан) заснива се на детерминистичком аутомату са коначним бројем стања (DFA Deterministic Finite Automata). То је јако повољан и ефикасан алгоритам. 12
Лексички анализатор 13
Прављење лексичког анализатора Можемо га правити ручно Коришћењем DFA Или неком другом, мање ефикасном техником (али опет довољно ефикасном) Можемо га генерисати помоћу алата Алат који на основу спецификације генерише лексички анализатора Један од таквих алата је Flex Flex генерише анализатор који се заснива на DFA Пример лексичког анализатора за језик Tiger 14
Прављење лексичког анализатора Како специфицирамо терминале језика? тј. регуларну граматику којом су дефинисани? Не пишемо продукције Много напорно и нејасно Користимо регуларне изразе Чест механизам за претраживање текста 15
Нотација регуларних израза a Обичан знак који стоји сам за себе ε Празан стринг Такође празан стринг M N Избор М или N M N Спајање М и N, иза М иде N MN Други начин да се напише спајање М и N M* Понављање М, нула или више пута M+ Понављање М, један или више пута M? Једно или ни једно појављивање М [a-za-z] Избор знака из скупа знакова. Тачка, означава један знак, било који, осим новог реда abc Низ знакова, било којих знакова 16
Коришћење регуларних израза Коришћењем овог језика можемо специфицирати лексичке симболе (токене) програмских језика Кључна реч if Идентификатор (почиње словом, а затим могу и слова и цифре) Цели бројеви Позитивни реални бројеви (53.7.28 4.) 17
Коришћење регуларних израза Коришћењем овог језика можемо специфицирати лексичке симболе (токене) програмских језика Кључна реч if if Идентификатор (почиње словом, а затим могу и слова и цифре) [a-z][a-z0-9]* Цели бројеви -?[0-9]+ Позитивни реални бројеви (53.7.28 4.) ([0-9]+. [0-9]*) ([0-9]*. [0-9]+) 18
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације %{ int brojdjura = 0; %} %% djura brojdjura += 1;. ; #include <stdio.h> int main() { yylex(); printf( %d, brojdjura); return 0; } 19
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације %{ %} %% djura return 1; pera return 2; mika return 3; #include <stdio.h> int main() { int toktype = yylex(); while (toktype!= 0) { print( %d, toktype); toktype = yylex(); } return 0; } 20
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације %{ %} %% djura return 1; pera return 2; mika return 3; #include <stdio.h> int main() { int toktype = yylex(); while (toktype!= 0) { print( %s, yytext); toktype = yylex(); } return 0; } 21
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације %{ %} %% djura return 1; pera return 2; mika return 3; #include <stdio.h> int main() { yyin = fopen( datoteka.txt, r ); int toktype = yylex(); while (toktype!= 0) { print( %s, yytext); toktype = yylex(); } return 0; } 22
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације %{ %} %% djura return 1; pera return 2; mika return 3; #include <stdio.h> int main() { yyin = fopen( datoteka.txt, r ); int toktype = yylex(); while (toktype!= 0) { print( %s, %d, yytext, yylen); toktype = yylex(); } return 0; } 23
Flex: генератор лексичких анализатора Генерише Це код на основу лексичке спецификације За сваки тип лексичког симбола наводи се Регуларни израз и акција у Це језику, нпр. %{ /* C declarations */ #include tokens.h /* definitions of IF, ID, NUM, */ #include errormsg.h union {int ival; string sval; double fval;} yylval; int charpos = 1; #define ADJ {EM_tokPos = charpos; charpos += yyleng;} %} digits [0-9]+ /* Lex definition of digits */ %% /* Regular expressions and actions */ if {ADJ; return IF;} [a-z][a-z0-9]* {ADJ; yylval.sval = String(yytext); return ID;} digits {ADJ; yylval.ival = atoi(yytext); return NUM;} (digits. [0-9]*) ([0-9]*. digits) {ADJ; yyval.fval = atof(yytext); return FLOAT} ( -- [a-z]* \n ) ( \n \t )+ { ADJ; /* do nothing */ } 24. {ADJ; error();}
Детаљи Flex спецификације и излаза Излаз Flex-а је Це програм lex.yy.c Који интерпретира DFA и извршава Це код акција Спецификација I део, између %{ и %}: Це #include и декларације Потребне за Це код до краја датотеке II део: скраћенице рег. израза и декларације стања Нпр. digits је скраћеница за [0-9]+ III део: регуларни изрази и акције Акције враћају целобројну вредност (int), која означава врсту пронађеног симбола Даље читање: Flex упутство на Интернету 25
Пример вишег програмског језика Tiger аутор A.W.Apple, Modern Compiler Impl. Tiger је мали језик са Угњежденим функцијама Вредностима слогова са имплицитним показивачима Низовима Целобројним и стринг променљивама Неколико конструкција за структуирану контролу тока Спецификација: Лексика: уобичајени идентификатори и Це коментари Декларације и променљиве и изрази 26
Декларације (1/3) decs је низ декларација типова, вредности и функција decs {dec}, где {x} може бити празан скуп ε dec tydec vardec fundec Типови података tydec type type-id = ty ty type-id {tyfields} овде су { и } терминални симболи array of type-id tyfields ε id: type-id{, id : type-id} 27
Декларације (2/3) Уграђени типови: int и string Свака декларација низа или слога ствара нов тип Чак иако су сва поља идентична са неким претходним Међусобно рекурзивни типови Сваки циклус рекурзије мора проћи кроз тип слога или низа Нпр. саморекурзија: type intlist = {head:int, tail:intlist} Међурекурзија: type tree = {key:int, children:treelist} type treelist = {head:tree, tail:treelist} Декларације променљивих: vardec var id := exp var id:type-id := exp 28
Декларације (3/3) Декларације функција: fundec function id (tyfields) = exp fundec function id (tyfields) : type-id = exp Међусобно рекурзивне процедуре и функције, нпр. function treeleaves(t : tree) : int = if t=nil then 1 else treelistleaves(t.children) function treelistleaves(l : treelist) : int = if L=nil then 0 else treeleaves(l.head) + treelistleaves(l.tail) 29
Променљиве и изрази (1/4) Л-вредност је локација која се чита или у коју се пише lvalue id променљиве Изрази lvalue lvalue. id поља слогова (структура) lvalue [ exp ] елементи низова Изрази без вредности Позив процедуре, додела, if-then, while, break, и понекад if-thenelse nil нула, припада сваком типу слога Низ израза (exp; exp;... exp) израчунава изразе с лева у десно коначна вредност је вредност последњег израза у низу Нема вредност: () и let израз без ичега између in и end 30
Променљиве и изрази (2/4) Целобројни литерал низ децималних цифара Стринг литерал низ знакова између наводника Специјални знаци се наводе након знака \ (\n, \t, итд.) Негација целобројном изразу може претходити знак - Позив функције: id() или id(exp{,exp}) Аритметички израз: exp op exp, gde op +, -, *, /, захтевају и производе целе бројеве Поређење: exp op exp, gde op =, <>, >, <, >=, <=, пореде операнде и производе 1 (тачно) или 0 (нетачно) Поређење стрингова: два стринга су иста ако им је садржај идентичан 31
Променљиве и изрази (3/4) Изрази са Буловим операторима: exp op exp, где је op & или Приоритет оператора: unarni -, *, /, +, -, =, <>, >, <, >=, <=, &, Асоцијативност оператера: *, /, +, -, су асоцијативни у лево; оператори поређења нису асоцијативни. Додела вредности: lvalue := exp If-then-else: if exp1 then exp2 else epx3, где exp2 и exp3 морају бити истог типа, тај тип је и тип резултата If-then: if exp1 then exp2, где exp2 не производи вредност While: while exp1 do exp2, где exp2 не производи вредност For: for id := exp1 to exp2 do exp3, exp3 нема вредност 32
Променљиве и изрази (4/4) Break: прекида извршење најближе петље (while или for) Let: израз let decs in expseq end евалуира декларације decs, а затим израчунава низ израза expseq, чији коначни резултат је резултат задњег израза у низу. Заграде: могу се користити за синтаксно груписање израза 33
Пример: програм queens (1/2) /* A program to solve the 8-queens problem */ let var N := 8 type intarray = array of int var row := intarray [ N ] of 0 var col := intarray [ N ] of 0 var diag1 := intarray [N+N-1] of 0 var diag2 := intarray [N+N-1] of 0 function printboard() = (for i := 0 to N-1 do (for j := 0 to N-1 do print(if col[i]=j then " O" else "."); print("\n")); print("\n")) 34
Пример: програм queens (2/2) function try(c:int) = if c=n then printboard() else for r := 0 to N-1 do if row[r] = 0 & diag1[r+c] = 0 & diag2[r+7-c] = 0 then (row[r] := 1; diag1[r+c] := 1; diag2[r+7-c] := 1; col[c] := r; try(c+1); row[r] := 0; diag1[r+c] := 0; diag2[r+7-c] := 0) in try(0) end 35
Лексички анализатор за Tiger (1/4) tokens.h typedef union { int pos; int ival; string sval; } YYSTYPE; extern YYSTYPE yylval; # define ID 257 # define STRING 258 # define INT 259 # define COMMA 260 # define COLON 261 # define SEMICOLON 262 # define LPAREN 263 # define RPAREN 264 # define LBRACK 265 # define RBRACK 266 # define LBRACE 267 # define RBRACE 268 # define DOT 269 # define PLUS 270 # define MINUS 271 # define TIMES 272... # define IF 284 # define THEN 285 # define ELSE 286 # define WHILE 287 # define FOR 288 # define TO 289 # define DO 290 # define LET 291 # define IN 292 # define END 293 # define OF 294 # define BREAK 295 # define NIL 296 # define FUNCTION 297 # define VAR 298 # define TYPE 299 36
Лексички анализатор за Tiger (2/4) tiger.lex (1/2)... %% " " {adjust(); continue;} \n {adjust(); EM_newline(); continue;} \t {adjust(); EM_newline(); continue;} "," {adjust(); return COMMA;} ":" {adjust(); return COLON;} ";" {adjust(); return SEMICOLON;} "(" {adjust(); return LPAREN;} ")" {adjust(); return RPAREN;} "[" {adjust(); return LBRACK;} "]" {adjust(); return RBRACK;} "{" {adjust(); return LBRACE;} "}" {adjust(); return RBRACE;} "." {adjust(); return DOT;} "+" {adjust(); return PLUS;} "-" {adjust(); return MINUS;} "*" {adjust(); return TIMES;} "/" {adjust(); return DIVIDE;} "=" {adjust(); return EQ;}... 37
Лексички анализатор за Tiger (3/4) tiger.lex (2/2)... while {adjust(); return WHILE;} for {adjust(); return FOR;} to {adjust(); return TO;} break {adjust(); return BREAK;} let {adjust(); return LET;} in {adjust(); return IN;} end {adjust(); return END;} function {adjust(); return FUNCTION;} var {adjust(); return VAR;} type {adjust(); return TYPE;} array {adjust(); return ARRAY;} if {adjust(); return IF;} then {adjust(); return THEN;} else {adjust(); return ELSE;} do {adjust(); return DO;} of {adjust(); return OF;} nil {adjust(); return NIL;} [a-za-z][a-za-z0-9"_"]* {adjust(); yylval.sval=string((char*)yytext); return ID;}... 38
Лексички анализатор за Tiger (3/4) driver.c int main(int argc, char** argv) { char* fname = argv[1]; EM_reset(fname); yyin = fopen(fname, r ); while (1) { int tok = yylex(); if (tok == 0) break; switch(tok) { case ID: case STRING: printf("%10s %4d %s\n", tokname(tok), EM_tokPos, yylval.sval); break; case INT: printf("%10s %4d %d\n", tokname(tok), EM_tokPos, yylval.ival); break; default: printf("%10s %4d\n", tokname(tok), EM_tokPos); } } fclose(yyin); return 0; 39 }
Лексички анализатор за Tiger (4/4) Излаз: прог. queens трансформисан у низ симбола FUNCTION 210 ID 219 printboard LPAREN 229 RPAREN 230 EQ 232 LPAREN 241 FOR 242 ID 246 i ASSIGN 248 INT 251 0 TO 253 ID 256 N MINUS 257 INT 258 1 DO 262 LPAREN 265 FOR 266 ID 270 j... function printboard() = (for i := 0 to N-1 do (for j := 0 to N-1 do print(if col[i]=j then " O" else "."); print("\n")); print("\n"))... 40
Прављење синтаксног анализатора Можемо га правити ручно Разни могући алгоритми. Разликују се по граматикама са којима могу да се носе и по ефикасности (меморијском и процесорском заузећу) Типична ручна техника: Синтаксни анализатор са рекурзивним спуштањем Можемо га генерисати помоћу алата Алат који на основу граматике генерише синтаксни анализатор Један од таквих алата је Bison (део GNU-а) Bison генерише анализатор који се заснива на LALR алгоритму 41
Граматика независна од контекста (контекстно слободна) Спецификација језика у облику низа продукција Пример: језик праволинијских програма Нетерминални симболи: S, E и L; почетни симбол је S Терминални симболи: id print num, + ( ) := ; Пример реченице у овом језику id := num; id := id + (id := num + num, id) Може потицати од следећег улаза (изворног кода): a := 7; b := c + (d := 5 + 6, d) Имена a,b,c и бројеви 5,6,7 су семантичке вредности симбола S S ; S S id := E S print ( L ) E id E num E E + E E (S, E) L E L L, E 42
Изводи реченица и стабла анализе Постоји више извода исте реченица: pera := 15; djole := 13 + pera S S ; S S id := E S print ( L ) E id E num E E + E E (S, E) L E L L, E 43
Изводи реченица и стабла анализе Постоји више извода исте реченице, нпр. Крајњи леви: увек се смењује крајњи леви нетерминал Крајњи десни: увек се смењује крајњи десни нетерминал Стабло синтаксне анализе Сваки симбол у изводу се спаја са симболом из ког је изведен Два различита извода могу имати исто стабло синтаксне анализе S S ; S id := E ; S id := num ; S id := num ; id := E id := num ; id := E + E 44
Нејасне граматике Граматика је НЕЈАСНА ако може да произведе исту реченицу са два различита стабла Нпр. граматика SLP-а је нејасна јер за реченицу id:=id+id+id постоје два стабла синтаксне анализе 45
Проблем нејасне граматике: Различита значења! Пример 1: Граматика Калкулатор 1 Два стабла за реченицу 1-2-3 Ако се стабло користи за интерпретирање значења Прво значење: (1-2)-3 = -4 Друго значење: 1-(2-3) = 2 E id E num E E * E E E / E E E + E E E E E (E ) 46
Други пример различитих значења! Пример 2: Граматика Калкулатор 1 Два стабла за реченицу 1+2*3 Ако се стабло користи за интерпретирање значења Прво значење: (1+2)*3 = 9 Друго значење: 1+(2*3) = 7 E id E num E E * E E E / E E E + E E E E E (E ) 47
Решење проблема: Јасна граматика Калкулатор 2 Задатак: Покушајмо да пронађемо јасну граматику Калкулатор 2, која дефинише (прихвата) исти језик Решење: Прво, оператор * треба да има првенство у односу на + Друго, сви оператори треба да буду асоцијативни у лево Ово се постиже увођењем нових нетерминалних симбола T и F Да би * била асоцијативна у десно, уместо ово продукције: T T * F писали би ову продукцију T F * T. E E + T E E T E T T T * F T T / F T F F id F num F ( E ) 48
Bison: генератор синт. анализатора Генерише Це код на основу улазне спецификације Bison је наследник алата Yacc Улазна спец. се састоји од три дела, раздвојених са %{, %} и %% (врло слично Flex-у) Програмски код: Це код који ће користи акције из доњих делова Декларације: списак терминала, нетерминала, итд. Граматичка правила (продукције): нетерминал : дефиниција, нпр. exp : exp PLUS exp {семантичка акција} где је exp нетерминал (израз) а PLUS терминал (оператор +) Излаз је Це код: y.tab.h и y.tab.c Приликом генерисања води рачуна о конфликтима 49
Пример спецификације (без акција) %{ int yylex(void); void error(char* s) {...} %} %token ID WHILE BEGIN END DO IF THEN ELSE SEMI ASSIGN %start prog %% prog: stmlist stm: ID ASSIGN ID WHILE ID DO stm BEGIN stmlist END IF ID THEN stm IF ID THEN stm ELSE stm stmlist: stm stmlist SEMI stm S id := id S while id do S S begin L end S if id then S S if id then S else S L S L L ; S 50
Синтаксни анализатор за Tiger (1/6) tiger.grm (1/3) %union { int pos; int ival; string sval; } %token <sval> ID STRING %token <ival> INT %token COMMA COLON SEMICOLON LPAREN RPAREN LBRACK RBRACK LBRACE RBRACE DOT PLUS MINUS TIMES DIVIDE EQ NEQ LT LE GT GE... %nonassoc EQ NEQ LT LE GT GE %left MINUS PLUS %left TIMES DIVIDE %left DOT %left NEG 51
Синтаксни анализатор за Tiger (2/6) tiger.grm (2/3) %start input %% input: stm ; stm: assignment ifthenelse ifthen while for break compoundstm let ; exp: lvalue NIL INT STRING NEG INT functioncall arithmetic comparison booleanexp recordcreation arraycreation ; 52
Синтаксни анализатор за Tiger (3/6) tiger.grm (3/3) arithmetic: exp PLUS exp exp MINUS exp exp TIMES exp exp DIVIDE exp MINUS exp %prec NEG ; comparison: exp EQ exp exp NEQ exp exp GT exp exp LT exp exp GE exp exp LE exp ; booleanexp: exp AND exp exp OR exp ; decs: /* empty */ decs dec ; dec: tydec vardec fundec ; tydec: TYPE ID EQ ty ; ty: ID LBRACE tyfields RBRACE ARRAY OF ID ; tyfields: /* empty */ typeidlist ; 53
Синтаксни анализатор за Tiger (4/6) Изгенерисани y.tab.h /* Tokens. */ #ifndef YYTOKENTYPE #define YYTOKENTYPE /* Put the tokens into the symbol table, so that GDB and other debuggers know about them. */ enum yytokentype { ID = 258, STRING = 259, INT = 260, COMMA = 261, COLON = 262, SEMICOLON = 263, LPAREN = 264, RPAREN = 265, LBRACK = 266, RBRACK = 267, LBRACE = 268,... 54
Синтаксни анализатор за Tiger (5/6) parsetest.c програм за тестирање #include <stdio.h> #include <stdlib.h> #include "util.h" #include "errormsg.h int main(int argc, char** argv) { yyin = stdin; yyout = stdout; parse(argv[1]); return 0; } void parse(char* fname) { EM_reset(fname); if (yyparse() == 0) /* parsing worked */ fprintf(stderr, "Parsing successful!\n"); else fprintf(stderr, "Parsing failed\n"); } 55
Синтаксни анализатор за Tiger (6/6) Добро је почети са једноставним тестовима pera pera.mika pera[djoka] pera[mika.djoka] nil () (pera;mika) (pera;mika;djoka) 100 "string" -100 foo() foo(pera) foo(pera, mika) pera + mika pera - mika pera * mika pera / mika -pera + mika pera = mika pera <> mika pera > mika pera < mika pera >= mika pera <= mika "string1" = "string2" pera & mika pera mika pera & mika djoka (pera mika) & djoka pera {} pera {mika=djoka} pera {mika=djoka, laza=nil} type tippera = perintip type tippera = {} type tippera = {id1:id1tip} 56
Bison у практичној употреби Предности: Једноставније специфицирати језик помоћу граматике Ако је граматика ваљана, сигурније је да ће и анализатор бити ваљан Алат нам помаже у откривању неких концептуалних проблема у граматици Мане: Резултујући анализатор је (мало) спорији од ручно писаног Теже организовање смисленог пријављивања грешака Лоше ношење са нејасним граматикама Теже дебаговање саме граматике (метајезик за описивање граматике уноси индирекцију) 57
Bison у практичној употреби Тренутно се користи за Ruby, PHP, Bash... Користио се за GCC do 2004. CLANG (Це предњи део за LLVM) је одмах писан ручно EDG предњи део се користи у већини комерцијалних Це/Це++ компајлера (Intel, пример) Постоје и други алати: ANTLR, JavaCC... 58