Μεταγλωττιστές Εργαστήριο 5 Εισαγωγή στο BISON Γεννήτρια Συντακτικών Αναλυτών 2 η Φάση Μεταγλώττισης Συντακτική Ανάλυση Διδάσκοντες: Δρ. Γεώργιος Δημητρίου Δρ. Άχμεντ Μάχντι 2015-1016
Φάσεις Μεταγλώττισης 1 η Φάση: Η Λεξική Ανάλυση (lexical analysis) ομαδοποιεί τους χαρακτήρες σε λεξικές μονάδες (lexical units) ή αναγνωριστικά (tokens). Η είσοδος (input) σε αυτήν την φάση είναι ένα ρεύμα χαρακτήρων (character stream). Η έξοδος είναι ένα ρεύμα αναγνωριστικών (stream of tokens). Οι κανονικές εκφράσεις (regular expressions) χρησιμοποιούνται για να προσδιορίσουν τα αναγνωριστικά που προέκυψαν από τον λεξικό αναλυτή. Ο λεξικός αναλυτής ή αλλιώς σαρωτής (scanner) λειτουργεί ως μηχανή πεπερασμένων καταστάσεων. Παράδειγμα εργαλείων για την δημιουργία λεξικών αναλυτών είναι τα Lex και Flex. Τα προγράμματα αυτά αναγνωρίζουν λεξικά πρότυπα (ή δείγματα) μέσα σε κείμενο. 2 η Φάση: Συντακτική Ανάλυση (syntax analysis or parse) ομαδοποιούνται τα αναγνωριστικά σε συντακτικές μονάδες (syntactical units). Η έξοδος σε αυτή την φάση είναι μια δενδρική αναπαράσταση της σύνταξης του προγράμματος. Το δένδρο χρησιμοποιεί φυσική γλώσσα και καθορίζει την δομή του προγράμματος που αναγνωρίστηκε απ τον συντακτικό αναλυτή.
Χαρακτηριστικά του bison Γεννήτρια συντακτικών αναλυτών σε C/C++. Συµβατό µε το εργαλείο του Unix yacc. Σχετικά εύκολο στη χρήση. Υψηλές επιδόσεις. υνατότητα δηµιουργίας συντακτικών αναλυτών από γραµµατικές χωρίς συµφραζόµενα (context free) τύπου LALR(1). υνατότητα περιγραφής σηµασιολογίας.
Συνήθης λειτουργία του bison Περιγραφή λεκτικού αναλυτή Περιγραφή συντακτικού αναλυτή flex bison Συντακτικός αναλυτής σε C Λεκτικός αναλυτής σε C Βοηθητικά µέρη Μεταγλωττιστής C Κατασκευή µεταγλωττιστή Χρήση µεταγλωττιστή Αρχικό πρόγραµµα Λεκτικός αναλυτής Συντακτικός αναλυτής Αποτελέσµατα συντακτικής ανάλυσης
Δομή αρχείου περιγραφής Αποτελείται από τρία τμήματα (όπως και στο flex) %{ ηλώσεις C/C++ (μακροεντολές, τύποι δεδομένων, δηλώσεις μεταβλητών και συναρτήσεων) %} ηλώσεις bison (Δηλώσεις λεκτικών Μονάδων) %% Κανόνες γραµµατικής %% Επιπρόσθετος κώδικας C Τμήμα Ορισμών Τμήμα Κανόνων Γραμματικής Συναρτήσεις Χρήστη
Δηλώσεις-Ορισμοί bison Γίνονται με το πρόθεμα %token ηλώσεις τερµατικών συµβόλων, π.χ. %token %token TK_void TK_PLUS 43 ήλωση αρχικού συµβόλου %start program ηλώσεις προτεραιότητας και προσεταιριστικότητα τελεστών, π.χ. %left %left %right %nonassoc TK_PLUS TK_MINUS TK_TIMES TK_DIV TK_MOD TK_EXPON = < > TK_EQ
Προτεραιότητα και Προσεταιριστικότητα Τελεστών (1/2) Τα %left, %right δηλώνουν τις προσεταιριστικότητες των tokens που τα ακολουθούν. Η σειρά δήλωσής τους καθορίζει την προτεραιότητα των αντίστοιχων τελεστών. Ισχύουν τα εξής: τα tokens που εμφανίζονται στην ίδια γραμμή έχουν την ίδια προτεραιότητα η προτεραιότητα αυξάνεται από πάνω προς τα κάτω
Προτεραιότητα και Προσεταιριστικότητα Τελεστών (2/2) Π.χ. %left + - %left * / TK_DIV TK_MOD Τα + και - έχουν μικρότερη προτεραιότητα από τα * και / Η έκφραση a+b+c υπολογίζεται ως (a+b)+c Το %nonassoc χρησιμοποιείται για τελεστές που δεν μπορούν να συνδυαστούν μεταξύ τους, π.χ., το = Το %prec δηλώνει τη προτεραιότητα μέσα σε έναν κανόνα της γραμματικής
Κανόνες γραµµατικής Γενική µορφή: αριστερό_µέλος: δεξιό_µέλος ; Το αριστερό µέλος είναι ένα µη τερµατικό σύµβολο. Το δεξιό µέλος µπορεί να περιέχει µηδέν ή περισσότερα τερµατικά και µη τερµατικά σύµβολα, π.χ. Π.χ., expression: term TK_PLUS expression ; Kατά σύμβαση, τα τερματικά σύμβολα παριστάνονται με κεφαλαία ενώ τα μη τερματικά με πεζά
Κανόνες γραµµατικής Επίσης µπορούν να δίνονται σηµασιολογικές ρουτίνες. Ιδιαίτερα χρήσιµες είναι αυτές στο τέλος ενός δεξιού µέλους, π.χ. statement: TK_STOP { } printf("stop requested.\n"); exit(0);
Κανόνες Παραγωγής Γραμματικής Μπορούν να δίνονται περισσότερα εναλλακτικά δεξιά μέλη: arithmetic_expr: TK_NUM arithmetic_expr + arithmetic_expr arithmetic_expr - arithmetic_expr arithmetic_expr * arithmetic_expr arithmetic_expr / arithmetic_expr - arithmetic_expr %prec UMINUS ( arithmetic_expr ) ; id_list: /* empty */ TK_ID TK_ID TK_COMMA id_list;
Ένα απλό παράδειγμα Απλή γλώσσα αριθμητικών εκφράσεων Γραμματική E -> T E + T T -> F T * F F -> (E) num Υλοποίηση σε bison %% %token %left %left TK_NUM '+ '*' program :expression ; expression :term expression + term ; term :factor term * factor ; factor : ( expression ) TΚ_num ;
Συναρτήσεις Χρήστη: Επιπρόσθετος κώδικας C Το τρίτο µέρος της περιγραφής είναι προαιρετικό (όπως και ο διαχωριστής %%). Περιέχει κώδικα C που ενσωµατώνεται όπως είναι. Χρησιµεύει συνήθως για τον ορισµό βοηθητικών συναρτήσεων που καλούνται από τις διάφορες σηµασιολογικές ρουτίνες.
Συντακτική Ανάλυση Bottom-Up Ο συντακτικός αναλυτής που παράγεται απο τον bison είναι αναλυτής από κάτω προς τα επάνω (bottom-up) Για την υλοποίηση μιας από κάτω προς τα επάνω ανάλυσης, χρησιμοποιούνται τα εξής: μια στοίβα αποθήκευσης των στοιχείων της γλώσσας η ενέργεια μετάθεση/ολίσθηση (shift) που τοποθετεί το επόμενο στοιχείο εισόδου στην στοίβα η ενέργεια αναγωγή/ελάττωση (reduce) που εφαρμόζεται στην κορυφή της στοίβας όταν έχει εμφανιστεί το δεξί μέλος ενός κανόνα και το αντικαθιστά με το αριστερό μέλος του οι ενέργειες accept και abort που δηλώνουν την επιτυχημένη ή όχι ανάλυση της εισόδου
Επίλυση Συγκρούσεων (I) Στη διάρκεια της ανάλυσης μιας ακολουθίας εισόδου μπορεί να εμφανιστούν καταστάσεις όπου ο συντακτικός αναλυτής έχει δύο επιλογές (διφορούμενη γραμματική): είτε να συνεχίσει με ολίσθηση είτε να κάνει ελλάτωση σε κάποιο κανόνα (shift reduce conflict) Ο bison δημιουργεί συντακτικό αναλυτή παρά τα conflicts. Σε αυτή την περίπτωση κάνει shift stmt -> IF expr THEN stmt IF expr THEN stmt ELSE stmt other
Επίλυση Συγκρούσεων (II) να κάνει ελλάτωση σε περισσότερους απο έναν κανόνες (reduce reduce conflict) Ο bison κάνει reduce στον κανόνα που έχει περιγραφεί πρώτος στο αρχείο γραμματικής Αν χρησιμοποιήσουμε την επιλογή bison v παράγεται ένα αρχείο (.output) με περιγραφές των συγκρούσεων
Κυριότερες συναρτήσεις του παραγόµενου συντακτικού αναλυτή Σύµβολο int yyparse() Περιγραφή Η κύρια συνάρτηση του συντακτικού αναλυτή, που παράγεται βάσει της περιγραφής. Επιστρέφει 0 αν η συντακτική ανάλυση τελειώσει χωρίς σφάλµατα, 1 αν βρέθηκαν συντακτικά σφάλµατα. int yylex() void yyerror(s) const char *s Η κύρια συνάρτηση του λεκτικού αναλυτή που πρέπει να δίνεται από τοχρήστη. Επιστρέφει έναν ακέραιο αριθµό, που αντιστοιχεί σε ένα τερµατικό σύµβολο. Η συνάρτηση χειρισµού σφαλµάτων που πρέπει να δίνεται από το χρήστη. Η συµβολοσειρά s περιέχει το µήνυµα σφάλµατος που παρουσιάστηκε.
Σύµβολο Τύπος ΥΥSTYPE Περιγραφή Ο τύπος της σηµασιολογικής τιµής των συµβόλων. YYSTYPE yylval Η µεταβλητή όπου η συνάρτηση yylex() πρέπει να τοποθετεί τις σηµασιολογικές τιµές των τερµατικών συµβόλων.
Παράδειγμα Απλής Αριθμομηχανής %{ #include <stdio.h> #include <ctype.h> void yyerror (char const *s ) { fprintf (stderr, "%s\n", s ) ; } %} %token DIGIT %% Στο πρώτο μέρος κάνουμε απαραίτητες δηλώσεις για την γλώσσα C. Η συνάρτηση yyerror() καλείται από τον συντακτικό αναλυτή άμα δεν μπορέσει να διαβάσει την είσοδο Δηλώνουμε επίσης τα διάφορα τερματικά σύμβολα.
Παράδειγμα Απλής Αριθμομηχανής line : expr '\n' { printf ("%d\n", $1 ) ; } expr : expr '+' term { $$ = $1 + $3 ; } term ; term : term '*' factor { $$ = $1 * $3 ; } factor ; factor : '(' expr ')' { $$ = $2 ; } DIGIT ; %% Στο δεύτερο μέρος γράφουμε την γραμματική. Κάθε κανόνας μπορεί να έχει και σημασιολογικές πράξεις. Η έκφραση $$ αφορά το σύμβολο που είναι στην αριστερή πλευρά ενός κανόνα. Οι εκφράσεις $1, $2, κ.τ.λ. αφορά τα σύμβολα που είναι στην δεξιά πλευρά του κανόνα.
Παράδειγμα Απλής Αριθμομηχανής yylex ( ) { int c ; c = getchar ( ) ; if (isdigit (c ) ) { yylval = c - 0' ; return DIGIT ; } return c ; } Στο τελευταίο μέρος μπορούμε να δώσουμε επιπλέον βοηθητικές συναρτήσεις. Πρέπει να υπάρχει ένας λεκτικός αναλυτής με μια συνάρτηση yylex(). Στο παρόν παράδειγμα υλοποιούμε εμείς ένα πολύ βασικό λεκτικό αναλυτή.
Παράδειγμα Απλής Αριθμομηχανής Εκτέλεση Προγράμματος Για να χρησιμοποιήσουμε το προηγούμενο μεταπρόγραμμα καλούμε > bison grammar.y > gcc -c grammar.tab.c Στην συνέχεια με ένα απλό πρόγραμμα όπως το παρακάτω int yyparse (void ) ; int main ( ) { return yyparse ( ) ; } καλούμε τον συντακτικό αναλυτή. Για να μεταγλωττίσετε το παραπάνω αρχείο με όνομα simple.c > gcc grammar.tab.o -o simple-calc
Προχωρημένη Αριθμομηχανή %{ #include <ctype.h> #include <stdio.h> #define YYSTYPE double / * double type f o r Bison stack * / void yyerror (char const *s ) { fprintf (stderr, "%s\n", s ) ; } %} %token NUMBER %left '+' '-' %left '*' '/' %right UMINUS %% lines : lines expr '\n' { printf ("%g\n", $2 ) ; } lines '\n' / * empty * / ;
Προχωρημένη Αριθμομηχανή (Συνέχεια) expr : expr '+' expr { $$ = $1 + $3 ; } expr '-' expr { $$ = $1 - $3 ; } expr '*' expr { $$ = $1 * $3 ; } expr '/' expr { $$ = $1 / $3 ; } '(' expr ')' { $$ = $2 ; } '-' expr %prec UMINUS { $$ = -$2 ; } NUMBER ; %% yylex ( ) { int c ; while ( (c=getchar ( ) ) == ' ' ) ; if ( (c=='.' ) (isdigit (c ) ) ) { ungetc (c, stdin ) ; scanf ("%lf", &yylval ) ; return NUMBER ; } return c ; }