A Tutorial Generování syntaktických analyzátorů George J. Klir Jan Konečný State University of New York (SUNY) Binghamton, New York 13902, USA gklir@binghamton.edu Palacky University, Olomouc, Czech Republic prepared for International Centre for Information and Uncertainty, Palacky University, Olomouc!!!! J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 1 / 28
lex & yacc Nástroje pro psaní programů, které zpracovávají (transformují) strukturované vstupy. Dva hlavní požadavky na takové programy: rozdělení vstupu do smysluplných jednotek. nalezení vztahů mezi těmi jednotkami. lex je nástroj pro vytváření lexikálních analyzátorů (též lexerů). yacc (Yet Another Compiler Compiler) je nástroj pro vytváření syntaktických analyzátorů (též parserů). J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 2 / 28
Lex Struktura vstupního souboru: sekce definic %% sekce pravidel %% sekce C kódu Výstupem je kód v C sekce definic definuje makra a importuje hlavičkové soubory v C. Též je možno psát sem jakýkoli kód v C. sekce pravidel spojuje regulární výrazy s kódem v C. Když je rozpoznán text odpovidající regulárnímu výrazu, je spuštěn odpovídající kód. sekce C kódu obsahuje libovolný kód v C, J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 3 / 28
První easy example: Example Specifikace pro desetinná čísla %% [\n\t ] ; -?(([0-9]+) ([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) { printf("number\n"); }. ECHO; %% main() { yylex(); } J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 4 / 28
První easy example: Example (cont.) Překlad: > lex first.l > cc lex.yy.c -o first -ll Spuštění:.65ea12 number eanumber J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 5 / 28
Počítání slov Vytvoříme program na počítání slov, podobný UNIXovému programu wc. Definiční sekce: %{ unsigned charcount = 0, wordcount = 0, linecount = 0; %} word [^ \t\n]+ eol \n Sekce pravidel: %% {word} { wordcount++; charcount += yyleng; } {eol} { charcount++; linecount++; }. charcount++; J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 6 / 28
Sekce C kódu: main() { yylex(); printf("%d %d %d\n", linecount, wordcount, charcount); } nedělá to nic zvláštního, jen využívá toho, že lex defaultně čte ze standardního vstupu. pokud bychom to chtěli vylepšit... J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 7 / 28
main(argc,argv) int argc ; char **argv; { if (argc > 1) { FILE *file; file = fopen(argv[l], "r"); if (!file) { fprintf(stderr,"could not open %s\n",argv[1]); exit(1); } yyin = file; } yylex(); printf("%d %d %d\n",charcount, wordcount, linecount); return 0; } J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 8 / 28
Parsování příkazové řádky % { unsigned verbose; char *progname; % } %% -h "-?" -help { printf("usage is: %s [-help -h -? ]" "[-verbose -v] [(-file -f) filename]\n", progname); } -v -verbose { printf ("verbose mode is on\n"); verbose = 1; } J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 9 / 28
%% main(argc, argv) int argc; char **argv; { progname = *argv; yylex(); } Problém: tohle ale ještě nečte z příkazové řádky, ale ze vstupu. Řešení: můžeme předefinovat funkce input a unput, aby zacházely s argv. Problém: ještě nemáme -file <filename>. Řešení: lex umožňuje použít alternativní počáteční stavy a zahrnout tak kontextovou závislost. J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 10 / 28
Generátor syntaktických analyzátorů vstup: gramatika (LL(1), LR(1), SLR, LALR(1)) výstup: syntaktický analyzátor rozhoduje platná slova gramatiky. První co zkusíme: statement NAME = expr expr NUMBER expr + NUMBER expr - NUMBER J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 11 / 28
Shift/Reduce Parsing; trocha velmi zjednodušené teorie Generátor podle gramatiky vytvoří množinu stavů, každý z nich odpovídá možné pozici v jednom nebo více částečně parsovaného pravidla. Parser čte tokeny: pokud token neukončuje pravidlo, uložíme ho na zásobník a přesuneme se jiného stavu =shift, přesun, symboly na zasobníku tvoří pravou stranu pravidla, popnem je, a pushnem levou stranu pravida =redukce. při redukci je spuštěn odpovídající kousek kódu =akce. http://vychodil.inf.upol.cz/publications/white-papers/lalr.pdf J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 12 / 28
Vstup pro yacc vstup má stejnou strukturu jako vstup lexu. Sekce definic %token NAME NUMBER Sekce pravidel %% statement: NAME = expr expr ; expr: expr + NUMBER expr - NUMBER NUMBER ; J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 13 / 28
Hodnoty symbolů a akce každý symbol má hodnotu neterminální symboly mají hodnotu vytvořenou kódem v parseru (ve skutečných parserech různé datové typy union typedef YYSYTYPE). Defaultně je vše int. Kdykoli parser redukuje, spustí uživatelský kód asociovaný k pravidlu akce. Akce se odkazuje na hodnoty symbolů na pravé straně jako $1, $2... a nastavuje hodnotu symbolu na levé straně přes $$. J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 14 / 28
Sekce pravidel (s doplněnými akcemi) statement: NAME = expr expr { printf("= %d\n",$1); } ; expr: expr + NUMBER { $$ = $1 + $3; } expr - NUMBER { $$ = $1 - $3; } NUMBER { $$ = $1; } ; J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 15 / 28
Lexer Abychom mohli vyzkoušet náš parser, potřebujeme mu dodat tokeny. %{ #include "y.tab.h" extern int yylval; %} %% [0-9]+ { yylval = atoi(yytext) ; return NUMBER;} [ \t] ; /* ignore whitespace */ \n return 0; /* logical EOF */. return yytext[0]; %% J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 16 / 28
> yacc -d calc.y > lex calc.1 > cc -c calc y.tab.c lex.yy.c -ly -ll > calc 99+12 = 111 > calc 2 + 3-14+33 = 24 > calc 100 + -50 syntax error J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 17 / 28
Aritmetické výrazy a nejednoznačnost expr: expr + expr { $$ = $1 + $3; } expr - expr { $$ = $1 -$3; } expr * expr { $$ = $1 * $3; } expr / expr { if ($3 == 0) yyerror( "divide by zero") ; else $$ = $1 / $3; } - expr { $$ = -$2; } ( expr ) { $$ = $2; } NUMBER { $$ = $1; } J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 18 / 28
Ta gramatika má ale problém nejednoznačnost. Example Parsujeme 2+3*4 : 2 přesuň NUMBER E redukce E NUMBER E+ přesuň + E+3 přesuň NUMBER E+E redukce E NUMBER Ted můžeme přesunout * a později redukovat přes pravidlo E E*E nebo rovnou redukovat E E+E Neřekli jsme, který operátor má přednost, ani nic o asociativitě. J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 19 / 28
Mohli bychom to řešit přímo v gramatice: expr: expr + mlexp expr - mlexp mlexp ; mlexp: mlexp * primary mlexp / primary primary ; primary: ( expr ) - primary NUMBER ; J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 20 / 28
Můžeme to ale dodat explicitně %left + - %left * / %nonassoc UMINUS J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 21 / 28
statement: NAME = expr expr { printf ("= %d\n", $1) ; } expr: expr + expr { $$ = $1 + $3; } expr - expr { $$ = $1 -$3; } expr * expr { $$ = $1 * $3; } expr / expr { if ($3 == 0) yyerror("divide by zero"); else $$ = $1 / $3;} - expr %prec UMINUS { $$ = -$2; } ( expr ) { $$ = $2; } NUMBER { $$ = $1; } ; %% J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 22 / 28
Proměnné a typované tokeny (a více vyhodnocovaných výrazů) %{ double vbltable[26]; %} %union { double dval; int vblno; } %token <vblno> NAME %token <dval> NUMBER %left + - %left * / %nonassoc UMINUS %type <dval> expression %% J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 23 / 28
statement-list: statement \n statement-list statement \n statement: NAME = expr { vbltable[$l] = $3; } expr { printf ("= %g\n", $1) ; } expr: NAME { $$ = vbltable[$1]; } ; J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 24 / 28
%{ #include <y.tab.h> #include <math.h> extern double vbltable[26]; %} %% ([0-9]+) ([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?) { yylval.dval=atof(yytext); return NUMBER; } [ \t]; [a-z] { yylval.vblno = yytext[0] - a ; return NAME; } "$" { return 0; /* end of input */ } \n. return yytext[0]; %% J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 25 / 28
v souboru y.tab.h: #define NAME 257 #define NUMBER 258 #define UMINUS 259 typedef union { double dval; int vblno; } YYSTYPE; extern YYSTYPE yylval; Proto uvádíme %token <vblno> NAME %token <dval> NUMBER %type <dval> expression J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 26 / 28
Jak to ještě můžem vylepšit libovolná jména pro proměnné. funkce (sqrt, log, exp) DOMACÍ UKOL... J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 27 / 28
Chcete vědět víc? Bison manual: http://www.gnu.org/software/bison/manual/bison.pdf LALR Gramatiky (VV) http://vychodil.inf.upol.cz/publications/white-papers/lalr.pdf lex & yacc, 2nd Edition By Doug Brown, John Levine, Tony Mason, Publisher: O Reilly Media Released: October 1992 J. Konečný (DAMOL) Generování syntaktických analyzátorů 12. května 2014 28 / 28