slide 1/52 ΗΥ – 340 Γλώσσες και Μεταφραστές Φροντιστήριο Yacc
slide 2/52 Τι είναι το yacc Ο yacc είναι ένας γενικού σκοπού parser generator. μετατρέπει την περιγραφή μίας context- free γραμματικής σε C/C++ πρόγραμμα. είναι optimized για τις LALR (Look-Ahead, Left-to-right parse, Rightmost-derivation) γραμματικές. Εσείς θα χρησιμοποιήσετε τον bison μία βελτιωμένη έκδοση του yacc.
slide 3/52 Δομή ενός αρχείου yacc %{ Πρόλογος %} Δηλώσεις yacc % Περιγραφή γραμματικής % Επίλογος
slide 4/52 Πρόλογος Ο πρόλογος (prologue) περιέχει δηλώσεις macro, μεταβλητών και συναρτήσεων. %{ #include #include “def.h” void print_token_value (FILE *, int, YYSTYPE); int lineno; %}
slide 5/52 Δηλώσεις yacc Σε αυτό το τμήμα δηλώνονται τα σύμβολα που απαρτίζουν την γραμματική καθώς και κάποια χαρακτηριστικά αυτών. Δήλωση τερματικών και μη τερματικών συμβόλων. Δήλωση αρχικού συμβόλου. Καθορισμός προτεραιότητας.
slide 6/52 Περιγραφή γραμματικής Η περιγραφή μίας context-free γραμματικής γίνεται μέσω γραμματικών κανόνων. expr: NUM | expr ‘+’ expr | expr ‘-’ expr | expr ‘*’ expr | expr ‘/’ expr | ‘(‘ expr ‘)’ ;
slide 7/52 Επίλογος Στον επίλογο (epilogue), γίνεται η υλοποίηση των συναρτήσεων που δηλώθηκαν στον πρόλογο καθώς και η υλοποίηση της συνάρτησης main. % int main() { yyparse(); }
slide 8/52 Lex & Yacc Στον πρόλογο δηλώνεται η συνάρτηση yylex (η συνάρτηση που είναι υπεύθυνη για την λεξικογραφική ανάλυση) Στο lex αρχείο γίνεται include το header file που παράγεται από τον yacc Το header file ονομάζεται y.tab.h και περιέχει τα tokens που αναγνωρίζονται από τον λεξικογραφικό αναλυτή, τα οποία ειναι τα τερματικά σύμβολα της γραμματικής. Αφαιρείται η συνάρτηση main από το flex αρχείο
slide 9/52 Διφορούμενη Ανάλυση (1/2) Όταν μπορούμε να παράγουμε μια έκφραση με δύο ή περισσότερους διαφορετικούς τρόπους. expr : expr ADD expr | expr SUB expr | expr MUL expr | expr DIV expr | INTCONST ;
slide 10/52 Διφορούμενη Ανάλυση (2/2) ex pr - * INTCON ST 31 4 (3*1)-43*(1-4) ex pr * INTCON ST 3 ex pr - INTCON ST 14 3*1-4
slide 11/52 Προτεραιότητα Τελεστών (1/4) %left - %right - %nonassoc %prec Τοποθετούνται στο τμήμα των ορισμών (εκτός του %prec) Επιβάλουν προτεραιότητες στα token τα οποία συνοδεύουν και τα οποία πρέπει να βρίσκονται στην ίδια γραμμή Αυξανόμενη προτεραιότητα από πάνω προς τα κάτω
slide 12/52 Προτεραιότητα Τελεστών (2/4) %left - %right π.χ. %left ADD, SUB %left MUL, DIV %right EXP/* ^ */ Τα τερματικά σύμβολα ADD και SUB έχουν την ίδια προτεραιότητα μεταξύ τους Αν υπάρχουν δύο ή περισσότερες προσθέσεις ή αφαιρέσεις σε μια έκφραση τότε μεγαλύτερη προτεραιότητα έχει η πιο αριστερή πράξη Τα ADD και SUB έχουν μικρότερη προτεραιότητα από τα MUL και DIV 2^2^3 υπολογίζεται ως 2^(2^3) = 2^8 = 256
slide 13/52 Προτεραιότητα Τελεστών (3/4) %prec Ορίζει εκ νέου την προτεραιότητα του κανόνα στον οποίο εμφανίζεται και του δίνει την προτεραιότητα του τερματικού συμβόλου που το συνοδεύει %nonassoc Χρησιμοποιείται για την περιγραφή τελεστών που δεν μπορούν να συνδυαστούν μεταξύ τους π.χ. τελεστής.LT. (Less than) της Fortran A.LT. B.LT. C (illegal expression)
slide 14/52 Προτεραιότητα Τελεστών (4/4) a = b = c*d - e - f * -g a = ( b = ( ((c*d)-e) - (f*(-g)) ) ) %right EQ /* = */ %left ADD SUB /* +,- */ %left MUL DIV /* *,/ */ %left UMINUS /* unary minus */ … % expr : expr EQ expr | expr ADD expr | expr SUB expr | expr MUL expr | expr DIV expr | SUB expr % prec UMINUS | ID ;
slide 15/52 Παράδειγμα Γραμματική για έναν απλό υπολογιστή αριθμητικών εκφράσεων Υποστηρίζει: Ανάθεση μίας αριθμητικής έκφρασης σε μία μεταβλητή Παράθεση αριθμητικών εκφράσεων που χωρίζονται με χαρακτήρες τέλους γραμμής
slide 16/52 Λεξικογραφικός Αναλυτής (1/2) Θέλουμε να αναγνωρίζουμε τα σύμβολα +, -, *, /, (, ) = \n ακεραίους αναγνωριστικά μεταβλητών
slide 17/52 Λεξικογραφικός Αναλυτής (2/2) το header αυτό δημιουργήται από τον yacc. οι τιμές των συμβολικών ονομάτων INTEGER, ID είναι ορισμένες στο header parser.h μετατρέπει πολλαπλά \n \t σε ένα
slide 18/52 Συντακτικός αναλυτής (1/3) Δηλώσεις καλείται από τον yacc με ένα μήνυμα όταν “ανακαλύψει” κάποιο λάθος αρχικό σύμβολο δήλωση των τερματικών συμβόλων που χρησιμοποιούνται από τον yacc και τον flex
slide 19/52 Συντακτικός αναλυτής (2/3) Κανόνες ε κανόνας
slide 20/52 Συντακτικός αναλυτής (3/3) Συναρτήσεις παράγεται από τον yacc
slide 21/52 Παραγωγή τελικού εκτελέσιμου parser.y scanner.l scanner.c parser.c parser.h calc yacc/bison lex/flex Compiler
slide 22/52 Προγράμματα (1/3) Έγκυρο πρόγραμμα
slide 23/52 Προγράμματα (2/3) Μη έγκυρο πρόγραμματα
slide 24/52 Προγράμματα (3/3) το expr δεν έχει \n στο τέλος Είναι έγκυρα προγράμματα;
slide 25/52 Ασάφειες στη γραμματική (1/3) Η περιγραφή μιας γραμματικής μπορεί να παράγει μία συγκεκριμένη ακολουθία συμβόλων με δύο ή περισσότερους τρόπους πχ. η έκφραση (expr) 1+2*3 μπορεί να παραχθεί ως expr -> expr '+' expr -> expr '+' expr '*' expr -> NUMBER '+' NUMBER '*' NUMBER -> 1+2*3 expr -> expr '*' expr -> expr '+' expr '*' expr -> NUMBER '+' NUMBER '*' NUMBER -> 1+2*3 expr: expr '+' expr | expr '*' expr | NUMBER ;
slide 26/52 Ασάφειες στη γραμματική (2/3) Αυτή η αμφισημία στις γραμματικές δεν είναι επιθυμητή επειδή μπορούμε να καταλήξουμε σε λάθος συμπεράσματα (βλ. actions) Απαλοιφή ασάφειας με τη χρήση προτεραιοτήτων τελεστών όπως είδαμε πριν με την προσθήκη επιπλέον κανόνων
slide 27/52 Ασάφειες στη γραμματική (3/3) Έτσι το προηγούμενο παράδειγμα παράγεται με μοναδικό τρόπο expr -> expr '+' expr1 -> expr1 '+' expr1 -> expr1 '+' expr1 '*' NUMBER -> expr1 '+' NUMBER '*' NUMBER -> NUMBER '+' NUMBER '*' NUMBER -> 1+2*3 expr: expr '+' expr1 | expr1 ; expr1: expr1 '*' NUMBER | NUMBER ;
slide 28/52 Inside Look Yacc (1/6) Στην πραγματικότητα το parsing μίας ακολουθίας εισόδου γίνεται από κάτω προς τα πάνω (bottom- up parsing) π.χ η έκφραση 3*4*(3+2) ανάγεται σε expr ως εξής: 3*4*(3+2) -> NUMBER '*' 4 '*' '(' 3 '+' 2 ')‘ -> expr '*' 4 '*' '(' 3 '+' 2 ')‘ -> expr '*' NUMBER '*' '(' 3 '+' 2 ')‘ -> expr '*' expr '*' '(' 3 '+' 2 ')‘ -> expr '*' '(' 3 '+' 2 ')‘ -> expr '*' '(' NUMBER '+' 2 ')‘ -> expr '*' '(' expr '+' 2 ')‘ -> expr '*' '(' expr '+' NUMBER ')‘ -> expr '*' '(' expr '+' expr ')‘ -> expr '*' '(' expr ')‘ -> expr '*' expr -> expr expr: expr '+' expr | expr '*' expr | '(' expr ')' | NUMBER ;
slide 29/52 Inside Look Yacc (2/6) Για την υλοποίηση μίας “από κάτω προς τα πάνω” συντακτικής ανάλυσης θα χρησιμοποιήσουμε Μία στοίβα στην οποία θα αποθηκεύονται τα σύμβολα που αποτελούν τη γλώσσα Μία ενέργεια “shift” η οποία θα τοποθετεί ένα σύμβολο από την είσοδο στη στοίβα Μία ενέργεια “reduce” η οποία θα αντικαθιστά ένα σύμβολο ή μία ακολουθία συμβόλων που εμφανίζονται στο δεξιό μέρος ενός κανόνα με το σύμβολο που εμφανίζεται στο αριστερό μέρος του κανόνα
slide 30/52 Inside Look Yacc (3/6) Μία ενέργεια “accept” που εφαρμόζεται όταν διαβαστεί ολόκληρη η είσοδος και έχει αναγνωριστεί το αρχικό σύμβολο Σηματοδοτεί την εγκυρότητα της ακολουθίας εισόδου σύμφωνα με την γραμματική Μία ενέργεια “error” που εφαρμόζεται όταν δεν μπορεί να εφαρμοστεί καμία ενέργεια “reduce” πάνω στη στοίβα, δηλαδή τα σύμβολα που υπάρχουν σε αυτήν δεν υπάρχουν στο δεξί μέρος κανενός κανόνα Σηματοδοτεί την αδυναμία της γραμματικής να περιγράψει την ακολουθία εισόδου
slide 31/52 Inside Look Yacc (4/6) Έτσι ο αλγόριθμος που εφαρμόζεται για την συντακτική ανάλυση της ακολουθίας εισόδου, είναι ο εξής Ανάλογα με την κατάσταση στην οποία βρίσκεται, ο συντακτικός αναλυτής, αποφασίζει εάν χρειάζεται το επόμενο σύμβολο από την είσοδο (lookahead token) για να επιλέξει την επόμενη ενέργεια Ανάλογα με την τρέχουσα κατάσταση και το επόμενο σύμβολο (εάν χρειάζεται) αποφασίζει να εκτελέσει μία από τις ενέργειες “shift”, “reduce”, “accept” ή “error”
slide 32/52 Inside Look Yacc (5/6) Παράδειγμα expr: expr '+' expr | expr '*' expr | '(' expr ')' | NUMBER ;
slide 33/52 Inside Look Yacc (6/6) Παράδειγμα expr: expr '+' expr | expr '*' expr | '(' expr ')' | NUMBER ;
slide 34/52 Yacc και αμφισημία (1/4) Συμπεριφορά του yacc όταν για μία παραγωγή υπάρχουν δύο ή περισσότεροι “δρόμοι” Ουσιαστικά όταν υπάρχουν δύο ή παραπάνω διαφορετικές παραγωγές για μία ακολουθία εισόδου, ο yacc σε κάποιο σημείο θα βρεθεί στο δίλημμα να κάνει “reduce” με τον κατάλληλο κανόνα ή να κάνει “shift” και να κάνει “reduce” με έναν επόμενο κανόνα (ο οποίος περιέχει και όλα τα σύμβολα του πρώτου κανόνα) Αυτό το “δίλημμα” λέγεται shift/reduce conflict
slide 35/52 Yacc και αμφισημία (2/4) Παράδειγμα expr: ifelse |NUMBER ; ifelse:IF '(' NUMBER ')' expr else ; else:ELSE expr | /* empty */ ;
slide 36/52 Yacc και αμφισημία (3/4) Επίσης, όταν υπάρχουν δύο διαφορετικοί κανόνες με πανομοιότυπο δεξιό μέρος, όταν βρεθεί, ο yacc, σε θέση να κάνει “reduce” την ακολουθία συμβόλων που περιγράφεται από αυτό, δεν θα ξέρει ποιον κανόνα να χρησιμοποιήσει για να κάνει το “reduce” Αυτό το “δίλημμα” λέγεται reduce/reduce conflict
slide 37/52 Yacc και αμφισημία (4/4) Ο yacc παράγει συντακτικούς αναλυτές, ακόμα κι αν η γραμματική περιέχει shift/reduce ή reduce/reduce conflicts Κατά τη διάρκεια shift/reduce conflict ο yacc κάνει εξ' ορισμού “shift” Κατά τη διάρκεια reduce/reduce conflict ο yacc κάνει “reduce” με τον κανόνα που βρίσκεται πιο κοντά στην αρχή του αρχείου “.y” Εάν τρέξουμε τον yacc (ή bison) με την παράμετρο “-v”, ο τελευταίος δημιουργεί ένα αρχείο με κατάληξη “.output” στο οποίο επισημαίνει τα σημεία των κανόνων που οδηγούν σε conflicts
slide 38/52 Actions (1/10) Μπορούμε να παρεμβάλλουμε ανάμεσα στα σύμβολα των δεξιών μερών των κανόνων κάποια actions, δηλαδή c, c++ κώδικα μέσα σε { } Εκτελούνται κάθε φορά που ενεργοποιείται η συγκεκριμένη παραγωγή Παράδειγμα expr: ID { printf(“Found ID\n”); } | expr '+' { int a = 3; } expr { int b; } | /* empty */ { printf(“empty\n”); } ;
slide 39/52 Actions (2/10) Επίσης μπορούμε να αναθέσουμε έναν τύπο στα τερματικά και τα μη τερματικά σύμβολα πού εμφανίζονται στη γραμματική και να τους αναθέτουμε τιμές Καταρχήν θα πρέπει να ορίσουμε ένα “union” το οποίο θα περιλαμβάνει όλους τους primitive τύπους που θα ανατεθούν στα τερματικά και μη τερματικά σύμβολα %union { char*stringV; intintV; doublerealV; ConfigToken*tokenV; }
slide 40/52 Actions (3/10) Για να αναθέσουμε έναν από τους διαθέσιμους τύπους του “union” στα τερματικά σύμβολα της γραμματικής, αρκεί να παρεμβάλλουμε στην αρχή της δήλωσης “%token”, το όνομα του τύπου του “union” πού θέλουμε να αντιστοιχίσουμε στο τερματικό σύμβολο %token CONF_ID CONF_TYPE_STRING %token CONF_TYPE_INT %token CONF_TYPE_REAL
slide 41/52 Actions (4/10) Για να αντιστοιχίσουμε έναν τύπο στα μη τερματικά σύμβολα της γραμματικής, αρκεί να δηλώσουμε τα ονόματά τους δεξιά μιας οδηγίας “%type” με τον τύπο να ακολουθεί %type expr list_elements %type hash_decl
slide 42/52 Actions (5/10) Τέλος όλα τα σύμβολα που παραλείπουμε, δεν έχουν κανένα τύπο και έτσι δεν μπορούμε να αναφερθούμε σε αυτά Μπορούμε μέσα από τα actions να αναφερθούμε σε όλα τα σύμβολα που έχουμε δηλώσει τον τύπο τους Στο αριστερό μέρος ενός κανόνα αντιστοιχεί το σύμβολο $$ Κάθε δεξιό σύμβολο ενός κανόνα, μπορεί να αναφερθεί μέσω του συμβολικού ονόματος $N (N = 1, 2,...) ανάλογα με την σχετική του θέση
slide 43/52 Actions (6/10) Παράδειγμα
slide 44/52 Actions (7/10) Η σειρά αρίθμησης των συμβόλων λαμβάνει υπόψιν της και τα blocks κώδικα non_terminal:FOO { g_var = $1; } BAR { g_var2 = $3; } $1, $FOO Βρίσκεται στην 3η θέση $BAR
slide 45/52 Actions (8/10) Οι τιμές στα τερματικά σύμβολα δίνονται από τον λεξικογραφικό αναλυτή Ο yacc δηλώνει μία καθολική μεταβλητή, στιγμιότυπο του “union” που έχουμε ορίσει, με το όνομα yylval Κάθε φορά που αναγνωρίζουμε ένα σύμβολο στον lex/flex, πριν επιστρέψουμε τον “κωδικό” του, αποθηκεύουμε στο κατάλληλο μέλος του “union” την τιμή του
slide 46/52 Actions (9/10)
slide 47/52 Actions (10/10)
slide 48/52 Επαναφορά μετά από συντακτικό λάθος (1/5) Ο yacc εξ' ορισμού, όταν δεν μπορεί να αναγνωρίσει την ακολουθία συμβόλων εισόδου, καλεί την συνάρτηση yyerror και τερματίζει την λειτουργία του Πολλές φορές δε θέλουμε να τερματίζουμε τη λειτουργία του προγράμματος μας στο πρώτο συντακτικό λάθος Ειδικά σε “interactive” προγράμματα όπως το “bc” του UNIX
slide 49/52 Επαναφορά μετά από συντακτικό λάθος (2/5) Η επαναφορά της λειτουργίας του συντακτικού αναλυτή μετά από συντακτικό λάθος (error recovery) είναι πολύ δύσκολη διαδικασία, επειδή πρέπει να “μαντέψουμε” που τελειώνει το κομμάτι που δεν συμφωνεί με την γραμματική... και που ξεκινάει το κομμάτι που είναι εν δυνάμει σωστό Ο yacc προσφέρει ένα σύμβολο με το όνομα error το οποίο καταναλώνει τα περιεχόμενα της στοίβας και τα επόμενα σύμβολα εισόδου, μέχρι να ανακαλύψει τουλάχιστον τρία σύμβολα που ξεκινούν ένα άλλο κανόνα n_term: FOO | error ;
slide 50/52 Επαναφορά μετά από συντακτικό λάθος (3/5) Μπορούμε επίσης να κατευθύνουμε την διαδικασία επαναφοράς προσθέτοντας σύμβολα στην παραγωγή που περιέχει το σύμβολο error και προσθέτοντας actions Παράδειγμα όταν συμβεί λάθος ο συντακτικός αναλυτής αγνοεί όλα τα σύμβολα μέχρι να συναντήσει ';'
slide 51/52 Επαναφορά μετά από συντακτικό λάθος (4/5) Ο yacc προσφέρει τα εξής macros που μπορούν να χρησιμοποιηθούν σε κανόνες yyerrok – εξαναγκάζει τον συντακτικό αναλυτή να θεωρήσει ότι βγήκε από την κατάσταση επαναφοράς μετά από λάθος και έτσι να αρχίσει κατευθείαν να ειδοποιεί για καινούρια λάθη yyclearin – καθαρίζει το lookahead
slide 52/52 Επαναφορά μετά από συντακτικό λάθος (5/5) Ο bison (και όχι ο yacc), προσφέρει μία ειδική εντολή που εκτελείται όταν ο τελευταίος καθαρίζει τη στοίβα μετά από error κώδικας που εκτελείται όταν καθαρίζεται από την στοίβα ένα σύμβολο ID σύνολο τερματικών ή μη τερματικών συμβόλων που χωρίζονται με κενό