420 likes | 569 Views
Εισαγωγή στο Flex. ΗΥ – 340 Γλώσσες και Μεταφραστές Φροντιστήριο. Τι είναι το Flex. Μία γεννήτρια λεξικογραφικών αναλυτών Παράγει λεξικογραφικούς αναλυτές στις γλώσσες C/C++ (target language). Γενικά Χαρακτηριστικά του Flex. Υλοποίηση από την αρχή του “κλασσικού” Lex του AT&T Unix
E N D
Εισαγωγή στο Flex ΗΥ – 340 Γλώσσες και Μεταφραστές Φροντιστήριο
Τι είναι το Flex • Μία γεννήτρια λεξικογραφικών αναλυτών • Παράγει λεξικογραφικούς αναλυτές στις γλώσσες C/C++ (target language)
Γενικά Χαρακτηριστικά του Flex • Υλοποίηση από την αρχή του “κλασσικού” Lex του AT&T Unix • Υπάρχει για όλα τα διαδεδομένα λειτουργικά συστήματα (πχ. GNU/Linux, Solaris, Mac OS-X, Win32) • Παράγει πολύ γρήγορους λεξικογραφικούς αναλυτές • Flex σημαίνει “Fast”- Lex ...
Περιγραφή ενός λεξικογραφικού αναλυτή στη γλώσσα του lex/flex Για την περιγραφή ενός λεξικογραφικού αναλυτή στο flex/lex, χρησιμοποιούνται • Κανονικές εκφράσεις (regular expressions ή regex) που περιγράφουν τα λεξικογραφικά πρότυπα μιας γλώσσας • Actions, δηλαδή κώδικας σε μία γλώσσα προγραμματισμού που εκτελείται όταν αναγνωριστεί μια κανονική έκφραση (στην οποία αντιστοιχεί το action)
Κανονικές Εκφράσεις (1/8) • x - αναγνωρίζει τον χαρακτήρα x και μόνο αυτόν • “abcd...” - αναγνωρίζει την ακολουθία χαρακτήρωνabcd... • . - (τελεία) αναγνωρίζει οποιονδήποτε χαρακτήρα ή σύμβολο πλην του newline • [xyz] – αναγνωρίζει οποιονδήποτε (έναν μόνο) από τους χαρακτήρες που βρίσκονται μέσα στο σύνολο. Δηλαδή ή το x ή το y ή το z
Κανονικές Εκφράσεις (2/8) • [ab-e] – αναγνωρίζει τους χαρακτήρες a ή b ή c ή d ή e. Δηλαδή ο συμβολισμός b-e δημιουργεί μία ακολουθία χαρακτήρων που ξεκινούν από το b και καταλήγουν στο e σύμφωνα με το ascii char set. • [^ab-e] – Όταν το σύμβολο ^ εμφανίζεται στην αρχή του συνόλου σημαίνει την ΑΡΝΗΣΗ του συνόλου. Δηλαδή το παραπάνω αναγνωρίζει ΟΛΟΥΣ τους χαρακτήρες, εκτός από τους a, b, c, d, e.
Κανονικές Εκφράσεις (3/8) • r* - kleen star, αναγνωρίζει καμία ή περισσότερες επαναλήψεις της κανονικής έκφρασης 'r' • παράδειγμα • η a* περιγράφει τα ε, a, aa, aaaa, aaaaaaaaaaaa, ... • η [ab-e]*περιγράφει τα ε, a, c, ac, cab, bbbe,cbaead… • r+ - αναγνωρίζει μία ή περισσότερες επαναλήψεις της κανονικής έκφρασης r • παράδειγμα • η a+ περιγράφει τα a, aa, aaaa, αλλά όχι το ε • η [ab-e]*περιγράφει τα a, c, ac, cab, bbbe,cbaead…
Κανονικές Εκφράσεις (4/8) • r? - αναγνωρίζει μία ή καμία επανάληψη της κανονικής έκφρασης r (διαβάζεται ως προαιρετικό r) • r{i, j} – 0 < i < j, αναγνωρίζει i, i+1, i+2, ..., j-1, j επαναλήψεις της κανονικής έκφρασης r • παράδειγμα • η .{2, 3} περιγράφει τις “λέξεις” που αποτελούνται από 2 ή 3 χαρακτήρες, πχ. ant, ///, ..., 3/2, 23, 2b, or,not, ... • r{i,} - 0 < i, αναγνωρίζει i ή περισσότερες επαναλήψεις της κανονικής έκφρασης r
Κανονικές Εκφράσεις (5/8) • r{i} – 0 < i, αναγνωρίζει i ακριβώς επαναλήψεις της κανονικής έκφρασης r • rs – αναγνωρίζει τις ακολουθίες που αναγνωρίζει η συνένωση των κανονικών εκφράσεων r και s (concatenation) • παράδειγμα • Η abc αναγνωρίζει την abc • H [a-z]”foo”[1-3] αναγνωρίζει όλες τις λέξεις 5 χαρακτήρων που ξεκινούν με πεζό γράμμα, περιέχουν το string “foo” και τελειώνουν με έναν αριθμό από το 1 μέχρι το 3, πχ. afoo1, lfoo3, ...
Κανονικές Εκφράσεις (6/8) • (r) – οι παρενθέσεις γύρω από μία κανονική έκφραση r, απλώς αλλάζουν την προτεραιότητα εφαρμογής των τελεστών • παράδειγμα • Η abc+ αναγνωρίζει τις abc, abcc, abcccccc, ... • Ενώ η (abc)+ αναγνωρίζει τις abc, abcabc, ... • Επίσης η “abc”+ αναγνωρίζει τις ίδιες ακολουθίες χαρακτήρων που αναγνωρίζει και η (abc)+ • r|s – ικανοποιείται από τις ακολουθίες συμβόλων που ικανοποιούν την r ή τις ακολουθίες που ικανοποιούν την s όχι και τις δύο(xor).
Κανονικές Εκφράσεις (7/8) • παράδειγμα • Η (”abc” | “ABC”){2,3} αναγνωρίζει τις abcabc, abcABC, ABCABC, abcabcabc, ... • ^r – ικανοποιείται όταν ικανοποιείται η r και η ακολουθία που την ικανοποιεί βρίσκεται στην αρχή της γραμμής • r$ – ικανοποιείται όταν ικανοποιείται η r και η ακολουθία που την ικανοποιεί βρίσκεται στο τέλος της γραμμής
Κανονικές Εκφράσεις (8/8) • \\, \”, \(, \), \*, \+, \[, \], \$, \^, \{, \}, ... οι ίδιοι οι χαρακτήρες \, ”, (, *, ... (escaped) • Οι κανονικές εκφράσεις παρατέθηκαν σύμφωνα με την προτεραιότητά τους (από την υψηλότερη προς την χαμηλότερη). • Παραδειγμα: • Η foo|bar*είναι ισοδύναμη με την (foo)|(ba(r*)), επειδή ο τελεστής '*' έχει μεγαλύτερη προτεραιότητα από την παράθεση και αυτή από το τελεστή '|' (alternation).
Προκαθορισμένες κλάσεις χαρακτήρων • Το flex υποστηρίζει κάποιες προκαθορισμένες κλάσεις χαρακτήρων που μπορούν να χρησιμοποιηθούν σε κανονικές εκφράσεις. Ο συμβολισμός των κλάσεων αυτών έχει τη μορφή [:Χ:], όπου 'Χ' το όνομα της κλάσης. • Συγκεκριμένα οι χαρακτήρες της κάθε κλάσης, ικανοποιούν τις συναρτήσεις 'isX(int c)' της επικεφαλίδας “ctype.h”. Οι κυριότερες από αυτές είναι:
Προκαθορισμένες κλάσεις χαρακτήρων • [:alnum:] - όλα τα αλφαριθμητικά: [a-zA-Z0-9] στο c (default) locale • [:alpha:] - όλα τα γράμματα του αλφαβήτου: [a-zA-Z] στο c locale • [:digit:] - όλα τα ψηφία του δεκαδικού συστήματος: [0-9] • [:lower:] - όλα τα πεζά γράμματα του αλφαβήτου: [a-z] • [:upper:] - όλα τα κεφαλαία γράμματα του αλφαβήτου: [A-Z] • [:print:] - όλοι οι εκτυπώσιμοι χαρακτήρες • [:space:] - όλοι οι χαρακτήρες whitespace • [:xdigit:]- όλα τα ψηφία του δεκαεξαδικού συστήματος: [0-9a-eA-E]
Δομή ενός προγράμματος (f)lex Ορισμοί %% Κανόνες %% Κώδικας χρήστη
Τμήμα ορισμών (1/10) • Σε αυτό το τμήμα, ο χρήστης μπορεί να ορίσει: • Κώδικα που θα αντιγραφεί χωρίς αλλαγές στο τελικό αρχείο .c που θα περιέχει τον κώδικα του παραγόμενου λεξικογραφικού αναλυτή • Macros (aliases), που υποκαθιστούν μία κανονική έκφραση. • Διάφορες παραμέτρους που επηρεάζουν τα χαρακτηριστικά του παραγόμενου λεξικογραφικού αναλυτή • Δήλωση user-defined conditions
Τμήμα ορισμών (2/10) • Ο κώδικας που θέλουμε να εισάγουμε, θα πρέπει να περικλείεται στα σύμβολα “%{“ ... “%}” ή στα “%top{“ ... “}” • Μπορούμε να κάνουμε 'include' headers για να χρησιμοποιήσουμε συναρτήσεις που είναι υλοποιημένες σε μία βιβλιοθήκη • Μπορούμε να υλοποιήσουμε συναρτήσεις που θα χρησιμοποιούνται από τον παραγόμενο συντακτικό αναλυτή
Τμήμα ορισμών (3/10) • Παράδειγμα:
Τμήμα ορισμών (4/10) • Σύνταξη: “Όνομα” “Κανονική Έκφραση” • Ο ορισμός των συντομεύσεων βοηθάει κυρίως • Στην αναγνωσιμότητα του προγράμματος • Το συμβολικό όνομα “literal” είναι πιο ευανάγνωστο από την αντίστοιχη κανονική έκφραση που πιθανώς το περιγράφει: “\"[^\n"]*\"”
Τμήμα ορισμών (5/10) • Παράδειγμα
Τμήμα ορισμών (6/10) • Οι παράμετροι που επηρεάζουν τα χαρακτηριστικά του παραγόμενου λεξικογραφικού αναλυτή ξεκινούν με το σύμβολο “%option”. Σχεδόν όλοι οι παράμετροι υπάρχουν και σαν command line options • %option header-file=”./scanner.h” • Αναγκάζει το flex να δημιουργήσει μία επικεφαλίδα που περιέχει τις δηλώσεις για τους τύπους και τις συναρτήσεις που χρησιμοποιούνται από τον παραγόμενο λεξικογραφικό αναλυτή
Τμήμα ορισμών (7/10) • %option noyywrap • Δεν καλεί τη συνάρτηση “yywrap”, η οποία καλείται από τον παραγόμενο λεξικογραφικό αναλυτή όταν τελειώσει το διάβασμα ενός αρχείου (εάν επιστρέψει 0 συνεχίζει το scanning από την είσοδο) • %option yylineno • Δηλώνει μία καθολική μεταβλητή με το όνομα “yylineno”, που κρατάει τον αριθμό της τρέχουσας γραμμής του αρχείου εισόδου • %option case-insensitive • Ο παραγόμενος λεξικογραφικός αναλυτής είναι case insensitive. Δηλαδή η λέξη “ClaSS” θα είναι “ίση” με την λέξη “class”
Τμήμα ορισμών (8/10) • %option reentrant • Ο παραγόμενος λεξικογραφικός αναλυτής είναι reentrant (βλ. reentrant scanners). Προσοχή αλλάζει ο τρόπος κλήσης της κύριας συνάρτησης του αναλυτή • %option prefix="PREFIX" • Εξ' ορισμού οι τύποι και οι συναρτήσεις που χρησιμοποιεί ο παραγόμενος λεξικογραφικός αναλυτής έχουν το πρόθεμα “yy” πχ. yylex, yyin, yylineno, yytext, yywrap, ... Επομένος αυτή η παράμετρος, αλλάζει το πρόθεμα στο string “PREFIX” • %option nounistd • Δεν κάνει 'include' την επικεφαλίδα “unistd.h”, η οποία υπάρχει μόνο στα UNIX συστήματα
Τμήμα ορισμών (9/10) • Ο χρήστης μπορεί να ορίσει δικές του “καταστάσεις” (conditions) και να ενεργοποιεί ορισμένους κανόνες μόνο εάν ο λεξικογραφικός αναλυτής βρίσκεται σε συγκεκριμένη user-defined κατάσταση • Inclusive (start) conditions • Ορίζονται με την εντολή “%s condition_name” • Μπορούν να ενεργοποιήσουν όλους τους κανόνες χωρίς “<>” πρόθεμα και αυτούς με πρόθεμα “<condition_name>” • Exclusive conditions • Ορίζονται με την εντολή “%x condition_name” • Μπορούν να ενεργοποιήσουν μόνο τους κανόνες με πρόθεμα “<condition_name>”
Τμήμα ορισμών (10/10) • Παράδειγμα
Τμήμα κανόνων (1/8) • Αυτό είναι το κύριο τμήμα του προγράμματος περιγραφής του λεξικογραφικού αναλυτή, αφού σε αυτό καθορίζεται η λειτουργικότητά του τελευταίου • Σύνταξη: <condition1, condition2, ...> Regular expression { action } • Τα conditions ανάμεσα στα “<”, “>” αναφέρονται στις καταστάσεις που έχει ορίσει ο χρήστης (με “%s” ή “%x”)
Τμήμα κανόνων (2/8) • Η παράθεση των conditions είναι προαιρετική. Εάν παραληφθούν, ο κανόνας μπορεί να ενεργοποιηθεί είτε από την προκαθορισμένη κατάσταση (INITIAL), είτε από τα inclusive conditions • Η προκαθορισμένη κατάσταση στην οποία βρίσκεται στην αρχή ο λεξικογραφικός αναλυτής (και η οποία είναι “inclusive”), είναι η INITIAL και μπορεί να συμμετέχει κανονικά στην λίστα των conditions που προηγείται της κανονικής έκφρασης • Η κατάσταση “<*>” είναι συντομογραφία για ΟΛΕΣ τις καταστάσεις που έχει δηλώσει ο χρήστης και την INITIAL
Τμήμα κανόνων (3/8) • Μετά από την παράθεση των user-defined καταστάσεων (conditions), ακολουθεί η κανονική έκφραση η οποία όταν ικανοποιηθεί ενεργοποιεί τον κανόνα • Η κανονική έκφραση μπορεί να περιλαμβάνει ή να αποτελείται αποκλειστικά από τα macros που έχουν δηλωθεί στο τμήμα ορισμών, τα οποία θα πρέπει να περικλείονται στα σύμβολα “{“, “}”, πχ. {comment} • Το ειδικό σύμβολο “<<EOF>>” ικανοποιείται όταν τελειώσει η ανάγνωση του τρέχοντος αρχείου
Τμήμα κανόνων (4/8) • Τέλος, όταν ικανοποιηθεί η δήλωση των καταστάσεων ΚΑΙ η κανονική έκφραση που ακολουθεί, ο κανόνας ενεργοποιείται. • Αυτό σημαίνει ότι εκτελείται ο κώδικας που υπάρχει δεξιά της κανονικής έκφρασης • Εάν ο κώδικας καταλαμβάνει πάνω από μία γραμμή, θα πρέπει να περικλείεται σε “{“, “}” • Οι κυριότερες συναρτήσεις και μεταβλητές του παραγόμενου λεξικογραφικού αναλυτή που είναι διαθέσιμα και μπορούν να χρησιμοποιηθούν από τους κανόνες είναι:
Τμήμα κανόνων (5/8) • yytext ένας pointer σε char (ή char array στον κλασσικό lex) που περιέχει το κομμάτι του κειμένου που έχει ικανοποιήσει την κανονική έκφραση • yyleng ένας ακέραιος που δηλώνει το μέγεθος του yytext • BEGIN(condition_name) αναγκάζει τον λεξικογραφικό αναλυτή να μεταβεί στην κατάσταση που έχει ορίσει ο χρήστης με το όνομα “condition_name” • ECHO αντιγράφει τα περιεχόμενα του yytext στην έξοδο • REJECT εξαναγκάζει τον λεξικογραφικό αναλυτή να ενεργοποιήσει τον “δεύτερο καλύτερο” κανόνα • Προσοχή: Η χρήση του REJECT σε οποιονδήποτε κανόνα κάνει πολύ πιο αργό ολόκληρο τον λεξικογραφικό αναλυτή • input() διαβάζει και επιστρέφει τον επόμενο χαρακτήρα από την ακολουθία εισόδου (look-ahead) • unput(c) τοποθετεί τον χαρακτήρα c στην ακολουθία εισόδου. Έτσι ο επόμενος χαρακτήρας που θα διαβάσει ο λεξικογραφικός αναλυτής, θα είναι ο c
Τμήμα κανόνων (6/8) • Όταν παραπάνω από ένας κανόνας ικανοποιείται τότε επιλέγεται αυτός που καταναλώνει τους περισσότερους χαρακτήρες. • Εάν βρεθούν δύο ή παραπάνω κανόνες που καταναλώνουν τον ίδιο αριθμό χαρακτήρων, τότε επιλέγεται αυτός που έχει δηλωθεί πρώτος στο lex αρχείο.
Τμήμα κανόνων (7/8) • Παράδειγμα
Τμήμα κανόνων (8/8) • Παράδειγμα
Τμήμα κώδικα χρήστη (1/2) • Το τμήμα κώδικα χρήστη είναι προαιρετικό • Όταν αυτό παραλειφθεί, μπορεί να παραλειφθεί και το δεύτερο σύμβολο “%%” • Σκοπός της είναι μόνο η εύκολη και άμεση προσθήκη υλοποιήσεων των συναρτήσεων που χρησιμοποιούνται στον παραγόμενο λεξικογραφικό αναλυτή • Ό, τι προστίθεται σε αυτό το τμήμα, αντιγράφεται χωρίς αλλαγές στο παραγόμενο αρχείο .c που περιέχει τον κώδικα του λεξικογραφικού αναλυτή
Τμήμα κώδικα χρήστη (1/2) • Παράδειγμα
Ένας ολοκληρωμένος λεξικογραφικός αναλυτής
Διαδικασία για την παραγωγή του τελικού προγράμματος Meta compiler για λεξικογραφικούς αναλυτές (flex/lex) scanner.l scanner.c βιβλιοθήκες εκτελέσιμο: scanner C/C++ Compiler
Είσοδος από c strings και char buffers • Ο παραγόμενος λεξικογραφικός αναλυτής, διαβάζει την ακολουθία χαρακτήρων εξ' ορισμού από τον global FILE* pointer με το όνομα “yyin” (και μπορούμε να τον “κατευθύνουμε” με τον τρόπο που φαίνεται στην main() του αρχείου scanner.l του προηγούμενου σχήματος)
Είσοδος από c strings και char buffers • Πολλές φορές όμως δε θέλουμε να διαβάζουμε την ακολουθία χαρακτήρων προς επεξεργασία από αρχεία (FILE* pointers) • Θέλουμε να διαβάζουμε χαρακτήρες από το δίκτυο • Θέλουμε να διαβάζουμε χαρακτήρες που υπάρχουν σε memory buffers • Έτσι, για να διαβάσει ο παραγόμενος λεξικογραφικός αναλυτής μία ακολουθία από χαρακτήρες που βρίσκονται στη μνήμη (NULL terminated char* pointers, buffers)
Είσοδος από c strings και char buffers • Πριν καλέσουμε τη συνάρτηση yylex(), θα πρέπει να καλέσουμε • Την συνάρτηση yy_scan_string(const char* str) για να διαβάσουμε από το string str • Την συνάρτηση yy_scan_bytes(char* buffer, size_t size) για να διαβάσουμε από ένα buffer μεγέθους size • Οι συναρτήσεις αυτές επιστρέφουν ένα handle τύπου YY_BUFFER_STATE το οποίο θα πρέπει να “ελευθερώσουμε” όταν θα έχει ολοκληρωθεί το scanning, μέσω της συνάρτησης yy_delete_buffer(YY_BUFFER_STATE buffer)
Παράδειγμα Προσοχή Τι θα γινόταν εάν η σειρά των κανόνων ήταν διαφορετική;
Παραπομπές • Flex Home Page • http://www.gnu.org/software/flex • Flex Manual • http://www.gnu.org/software/flex/manual/