BISON (YACC) (Yet Another Compiler Compiler)
Bison Bison, İçerikten/Bağlamdan Bağımsız bir Gramer (Context-Free Grammar, CFG) tanımlaması yapar. CFG ise parser (sözdizimsel analizci) oluşturmaya yarar. CFG’nin elemanları: 1. Terminaller: token’lar ve lexeme’ler 2. Değişkenler (Terminal Olmayanlar): sözdizimsel elemanlar 3. Üretim Kuralları 4. Başlama Kuralı
Bison Bir üretim kuralının formatı: sembol: tanım {aksiyon}; Örnek: <a> → <b>c CFG ifadesi bison da şöyle yazılır: a: b 'c'; (Eğer b bir token değilse) a: B 'c'; (Eğer b bir tokensa)
Bison Dosya Formatı Bison dosyasının formatı: %{ C deklarasyonları Oluşan C programına direkt kopyalanırlar. Örneğin, değişkenler, tipler, makrolar… %} tanımlar %% Üretim kuralları C programları
Tanımlar Tokenları ve onların karakteristik özelliklerini tanımlamak için kullanılırlar: %token: token adları tanımlanır. %left: sol-çağrışımlı operatörler tanımlanır. %right: sağ-çağrışımlı operatörler tanımlanır. %type: değişken tipleri tanımlanır.
Tanımlar %union: semantik değerler için çoklu veri tipleri tanımlanır. %start: başlama sembolü tanımlanır. (verilmemişse kurallardaki ilk değişken, başlama sembolü olarak atanır.) %prec: kurala öncelik atanır. (precedence)
L={ anbn | n>=1} dilini kabul etmek için tasarlanan kolay bir bison programı.
anbn.y %{ #include <stdio.h> /* printf */ %} %token A B %% /* The .y extension is a convention used for Bison input files.*/ %{ #include <stdio.h> /* printf */ %} %token A B %% start: anbn '\n' {printf(" is in anbn\n");return 0;} anbn: A B | A anbn B; int yyerror(char *s) { printf("%s, it is not in anbn\n", s); } int main() yyparse(); return 0; /* The .y extension is a convention used for Bison input files.*/ %{ #include <stdio.h> /* printf */ %} %token A B %% program: start | program start; start: anbn '\n' {printf(" is in anbn\n");} anbn: A B | A anbn B; int yyerror(char *s) { printf("%s, it is not in anbn\n", s); } int main() yyparse(); return 0;
anbn.lex %{ #include "anbn.tab.h" %} %% a return A; b return B; . return yytext[0]; \n return '\n';
flex-bison İkilisini Çalıştırma $ flex anbn.lex (İçinde bulunduğunuz klasörde lex.yy.c dosyası oluşur.) $ bison -d anbn.y (İçinde bulunduğunuz klasörde anbn.tab.h ve anbn.tab.c dosyaları oluşur. anbn.tab.h dosyası TOKEN’lara karşılık gelen tamsayı tanımlamalarını, anbn.tab.c dosyası da yyparse fonksiyonunu içerir.) $ gcc anbn.tab.c lex.yy.c -lfl (İçinde bulunduğunuz klasörde a.out isimli executable dosya oluşur.) $ gcc -o anbn.x anbn.tab.c lex.yy.c -lfl (İçinde bulunduğunuz klasörde anbn.x isimli executable dosya oluşur.) yyparse();
flex-bison İkilisini Çalıştırma $ ./a.out aabb is in anbn $ ./anbn.x acadbefbg syntax error, it is not in anbn yyparse(); Eğer girdi akımı start kuralı ile uyuşmazsa, program klasik “syntax error” mesajı verir ve sonlanır. Ancak, yyerror fonksiyonu içerisinde özel hata mesajları da oluşturulabilir.
Elemanlar İçin Değerlerin Pozisyonel Atamaları $$: Sol taraf $1: Sağ taraftaki ilk eleman $n: Sağ taraftaki n. eleman
Tamsayıları Yazdırma (PrintIntegers.lex) %{ #include "PrintIntegers.tab.h" #include <stdlib.h> /* atoi */ %} %% [0-9]+ {yylval=atoi(yytext);return INTEGER;} \n return NEWLINE; . return yytext[0];
Tamsayıları Yazdırma (PrintIntegers.y) %{ #include <stdio.h> /* printf */ %} %token INTEGER NEWLINE %% lines: /* empty string or epsilon */ | lines NEWLINE | lines line NEWLINE {printf("=%d\n", $2);} | error NEWLINE {yyerror("Tekrar dene:");}; line: INTEGER {$$ = $1;}; int yyerror(char *s) { printf("%s\n", s); } int main() yyparse(); return 0;
Örnek Çıktı $ ./PrintIntegers.x 7 =7 007 zippy syntax error Tekrar dene:
Rekürsif Kurallar Sağ-rekürsif kurallar da kullanılabilir. Ancak, parser verimliliği açısından sol-rekürsif kurallar daha sıklıkla tercih edilir.
yylval yylex() fonksiyonu, okunan token’ın tipini belirten bir sayı döndürür. (token number) Eğer token’la ilişkili bir sayı varsa, dış değişken olan yylval değişkenine atanmalıdır.
yylval Fabrika ayarı olarak yylval ın tipi int dir. Tipi değiştirmek için YYSTYPE makrosunu bison dosyasının deklarasyon kısmında kullanmanız gerekir. %{ #define YYSTYPE double %} Eğer token değerleri için birden fazla veri tipi varsa, yylval “union” olarak tanımlanmalıdır.
yylval yylval için olası 3 tip örneği: %union{ double real; /* real değer */ int integer; /* integer değer */ char str[30]; /* string değer */ } Örnek: yytext = "0012", yylval tipi: int, yylval değeri: 12 yytext = "+1.70", yylval tipi: double, yylval değeri: 1.7
Token Tipleri Token’ların ilişkili değerlerinin (lexeme’lerinin) tipleri şöyle tanımlanır: %token <real> REAL %token <integer> INTEGER %token <str> IDENTIFIER Değişkenlerin tipleri şöyle tanımlanır: %type <real> real-ifade %type <integer> integer-ifade
Sözcüksel Analizci’den Token’ların Değerlerini Geriye Döndürme (LexicalAnalyzer.lex) %{ #include "LexicalAnalyzer.tab.h" #include <string.h> /* strcpy */ %} alphabetic [A-Za-z] digit [0-9] alphanumeric ({alphabetic}|{digit}) %% [+-]?{digit}*(\.)?{digit}+ {sscanf(yytext, "%lf",&yylval.real);return REAL;} {alphabetic}{alphanumeric}* {strcpy(yylval.str,yytext);return IDENTIFIER;}
Operatör Önceliği Aynı satırdaki token’lar aynı öncelik ve ilişki türüne sahiptir. Alt satıra geçtikçe öncelik artar. +, - ile *, / ile aynı önceliğe sahiptir. Ama * ve / daha önceliklidir. Ayrıca 4 operatör de sol-çağrışımlıdır. %left '+' '-' %left '*' '/'
Basit Bir Hesap Makinesi (Calculator.lex) %{ #include "Calculator.tab.h" %} integer [0-9]+ real1 ([0-9]*\.[0-9]+) real2 ([0-9]*\.[0-9]+[Ee][+-]?[0-9]+) real {real1}|{real2} nl \n %% [ \t] ; /* Do nothing for space */ {integer} { sscanf(yytext, "%d", &yylval.integer);return INTEGER;} {real} { sscanf(yytext, "%lf", &yylval.real);return REAL;} \+ { return PLUS;} \- { return MINUS;} \* { return TIMES;} \/ { return DIVIDE;} \( { return LP;} \) { return RP;} {nl} { return NL;} . { return yytext[0];}
Basit Bir Hesap Makinesi (Calculator.y) %{ #include <stdio.h> %} %union{double real;int integer;} %token <real> REAL %token <integer> INTEGER %token PLUS MINUS TIMES DIVIDE LP RP NL %type <real> rexpr %type <integer> iexpr %left PLUS MINUS %left TIMES DIVIDE %% lines: /* nothing */ | lines line; line: NL | iexpr NL { printf("%d\n", $1);} | rexpr NL { printf("%15.8lf\n", $1);}; iexpr: INTEGER | iexpr PLUS iexpr { $$ = $1 + $3;} | iexpr MINUS iexpr { $$ = $1 - $3;} | iexpr TIMES iexpr { $$ = $1 * $3;} | iexpr DIVIDE iexpr { if($3) $$ = $1 / $3; else { yyerror("sıfıra bölme hatası");}} | MINUS iexpr { $$ = - $2;} | LP iexpr RP { $$ = $2;};
Basit Bir Hesap Makinesi (Calculator.y) rexpr: REAL | rexpr PLUS rexpr { $$ = $1 + $3;} | rexpr MINUS rexpr { $$ = $1 - $3;} | rexpr TIMES rexpr { $$ = $1 * $3;} | rexpr DIVIDE rexpr { if($3) $$ = $1 / $3; else { yyerror("sıfıra bölme hatası");}} | MINUS rexpr { $$ = - $2;} | LP rexpr RP { $$ = $2;} | iexpr PLUS rexpr { $$ = (double)$1 + $3;} | iexpr MINUS rexpr { $$ = (double)$1 - $3;} | iexpr TIMES rexpr { $$ = (double)$1 * $3;} | iexpr DIVIDE rexpr { if($3) $$ = (double)$1 / $3; else { yyerror( "sıfıra bölme hatası" );}} | rexpr PLUS iexpr { $$ = $1 + (double)$3;} | rexpr MINUS iexpr { $$ = $1 - (double)$3;} | rexpr TIMES iexpr { $$ = $1 * (double)$3;} | rexpr DIVIDE iexpr { if($3) $$ = $1 / (double)$3; else { yyerror( "sıfıra bölme hatası" );}}; %% int yyerror(char *s) { printf("%s\n", s); } int main() yyparse(); return 0;