Μεταγλωττιστές μια φοιτητική προσέγγιση
i. Περιεχόμενα i. Περιεχόμενα ii.πηγές 1. Εισαγωγή στη Μεταγλώττιση 1.1 Η διαδικασία της μεταγλώττισης Μεταγλωττιστής: Ανάλυση Σύνθεση 1.2 Οι φάσεις της μεταγλώττισης Λεκτική Ανάλυση Συντακτική Ανάλυση Σημασιολογική Ανάλυση Παραγωγή Κώδικα 1.3 Διερμηνείς, Συμβολομεταφραστές 1.5 Η Ανάπτυξη ενός Μεταγλωττιστή 2. Στοιχεία Θεωρίας Γλωσσών Κανόνες Παραγωγής Συντακτικά Δέντρα Συμβολοσειρές 2.2 Γραμματικές Σύμβολο εκκίνησης / Αρχικό σύμβολο: (συνήθως συμβολίζεται S). Όλες οι προτάσεις προέρχονται από αυτό το σύμβολο μέσα από διαδοχικές αντικαταστάσεις, χρησιμοποιώντας τους κανόνες γραμματικής. Κανόνες γραμματικής / Κανόνες παραγωγής: υποδεικνύουν μια ομάδα συμβόλων (συμβολοσειρά) στα δεξιά του κανόνα. Η συμβολοσειρά αντικαθιστά το αριστερό μέλος της παραγωγής. Συνήθεις συμβολισμοί: Συνεπώς: Πεπερασμένα Αυτόματα: Αυτόματα Στοίβας 3. Τρόποι Προσδιορισμού Σύνταξης 3.1 Σύνολα 3.a Γραμματικές Χωρίς Συμφραζόμενα (Context Free Grammars) 3.2 BNF 3.3 Συντακτικά διαγράμματα 3.4 EBNF 3.4.3 Συσχετισμός EBNF με συντακτικά διαγράμματα 4 Λεκτική Ανάλυση 4.1 Λεκτικός Αναλυτής 4.2.1 Σχεδίαση Πεπερασμένου Αυτόματου που αντιστοιχεί σε κανονική έκφραση 4.2.1.1 Μετατροπή κανονικής έκφρασης σε Μη ντετερμινιστικό Πεπερασμένο Αυτόματο * *
* 4.2.1.2 Αλγόριθμος μετατροπής απο Μη ντετερμινιστικό Πεπερασμένο σε Ντετερμινιστικό Πεπερασμένο Αυτόματο Δεύτερο παράδειγμα: ΜΠΑ σε ΝΠΑ ( f g )* x: 4.2.2.3 Ελαχιστοποίηση καταστάσεων * * * 5. Συντακτική και σημασιολογική ανάλυση 5.1 Στρατηγικές συντακτικής ανάλυσης 5.2 Αναλυτές από πάνω προς τα κάτω ( Top down Parsers) Υπολογισμός FIRST * Υπολογισμός Follow Συνάρτηση Empty Συνάρτηση LOOKAHEAD Ορισμός γραμματικών LL(1) Ισοδύναμος ορισμός LL(1) 5.2.3 Συντακτικοί Αναλυτές LL(1) Προετοιμασία για εξετάσεις Λυμένες ασκήσεις Ασκήσεις Θέματα παλαιοτέρων ετών
ii.πηγές Μεταγλωττιστές Μ.Κ.Βίρβου Εκδώσεις Βαρβαρήγου ISBN:960 7996 15 1 Compilers Principles, Techniques, and Tools 2nd edition / Alfred V. Aho,Ravi Sethi, Jeffrey D. Ullman 1986 ISBN:0 321 48681 1 Wikipedia Coursera Compilers
1. Εισαγωγή στη Μεταγλώττιση 1.1 Η διαδικασία της μεταγλώττισης Μεταγλωττιστής: μεγάλο και πολύπλοκο πρόγραμμα διαβάζει άλλο πρόγραμμα (πηγαίο πρόγραμμα), γραμμένο σε μια γλώσσα προγραμματισμού (πηγαία γλώσσα) μεταφράζει το πηγαίο πρόγραμμα σε ένα ισοδύναμο πρόγραμμα (πρόγραμμα-στόχος / τελικό πρόγραμμα), γραμμένο σε άλλη γλώσσα προγραμματισμού (γλώσσα-στόχος / τελική γλώσσα, συνήθως γλώσσα μηχανής) παράγει μηνύματα λάθους, εφόσον αυτά υπάρχουν στο πηγαίο πρόγραμμα 3 γλώσσες προγραμματισμού χαρακτηρίζουν έναν μεταγλωττιστή: πηγαία γλώσσα (γι' αυτήν έχει αναπτυχθεί, αυτή θα μεταφράζει ) γλώσσα-στόχος (=σε ποιον επεξεργαστή τρέχει, σε αυτή θα μεταφράζει ) γλώσσα υλοποίησης (σε ποια γλώσσα είναι γραμμένος) Οι γλώσσες αυτές μπορούν να αναπαρασταθούν στα Τ-διαγράμματα (Bratman). Πλεονέκτημα Τ-διαγράμματος: αναπαράσταση πολύπλοκων λειτουργιών μεταγλώττισης. Τ-διαγράμματα σελ.8 Η διαδικασία της μεταγλώττισης μπορεί να διαιρεθεί σε δύο μεγάλες υποενότητες (για τη λογική κατανόηση και τη μεταφερσιμότητα ενός μεγάλου μέρους του μεταγλωττιστή): Ανάλυση πηγαίου προγράμματος Σύνθεση τελικού προγράμματος
Ανάλυση Ο μεταγλωττιστής σπάει το πηγαίο στα μέρη που το αποτελούν και δημιουργεί μια ενδιάμεση αναπαράσταση του τελικού προγράμματος, η οποία έχει τη μορφή δέντρου (συντακτικό δέντρο). Σύνθεση Κατά τη διάρκεια της σύνθεσης συνήθως έχουμε παραγωγή κώδικα, ώστε να προκύψει ο τελικός κώδικας χρησιμοποιώντας την ενδιάμεση αναπαράσταση.
1.2 Οι φάσεις της μεταγλώττισης Για να μπορούμε να χειριζόμαστε εύκολα μεγάλα προγράμματα, τα τμηματοποιούμε σε λογικές υποενότητες. Μεταγλώττιση: Υποενότητα Ανάλυσης: Φάση Λεκτικής Ανάλυσης Φάση Συντακτικής Ανάλυσης Φάση Σημασιολογικής Ανάλυσης Υποενότητα Σύνθεσης: Φάση Παραγωγής Κώδικα Όλες αυτές οι φάσεις λειτουργούν διαδοχικά, ενώ το σύστημα διεπαφής τους εξαρτάται από τον μεταγλωττιστή. Οι 4 αυτές φάσεις μπορούν να λειτουργούν ως 4 διαφορετικά προγράμματα, όπου η έξοδος της μίας φάσης αποτελεί την είσοδο της επόμενης (μεταγλωττιστές τεσσάρων περασμάτων). Πιο διαδεδομένοι ωστόσο είναι οι μεταγλωττιστές μονού περάσματος, οι οποίοι περνούν τα δεδομένα από τη μια φάση στην άλλη κατά μία ομάδα (token) τη φορά, χωρίς να χρειάζεται να γράφονται προσωρινές εκδόσεις του πηγαίου κώδικα στον δίσκο. Υπάρχουν και μεταγλωττιστές δύο ή τριών περασμάτων. Λεκτική Ανάλυση Διαβάζει τους χαρακτήρες στο πηγαίο πρόγραμμα και τους ομαδοποιεί σε μια σειρά από ομάδες (tokens). Η κάθε ομάδα αναπαριστά μια σειρά χαρακτήρων που αποτελεί α) μια λογική ενότητα (π.χ. λέξη κλειδί: if, do, while...) β) ένα αναγνωριστικό (identifier) που δηλώνει όνομα μεταβλητής. Η σειρά των χαρακτήρων που αποτελεί μια ομάδα λέγεται λεκτική μονάδα για την ομάδα αυτή. Λεκτική τιμή: σελ.12-13 π.χ.: if (i == j) z = 0; else z = 1; Tokens: Operators, Whitespaces, Keywords, Identifiers, Numbers, (, ), ;, =. \tif (i == j)\n\t\tz = 0;\n\telse\n\t\tz = 1;
WK I O I W I N W K W I N (καθένα από τα if, i, ==, κλπ είναι λεκτικές μονάδες (lexemes), συνεπώς κάθε αναγνωρίσιμη συμβολοσειρά είναι μία λεκτική μονάδα που ανήκει σε κάποιο token). Συντακτική Ανάλυση Θέτει μια ιεραρχική δομή (σε μορφή συντακτικού δέντρου) στην ακολουθία των ομάδων. Κάθε κόμβος του συντακτικού δέντρου είναι μια εγγραφή με ένα πεδίο για τη λειτουργία και δύο πεδία με δείκτες προς τις εγγραφές του αριστερού και δεξιού παιδιού. (σελ. 14-15) Σημασιολογική Ανάλυση Καθορίζει την έννοια του πηγαίου προγράμματος. Ασχολείται με θέματα όπως δηλώσεις μεταβλητών και των περιοχών ισχύος τους, τον έλεγχο τύπων κτλ. Παραγωγή Κώδικα Αποτελεί την τελική φάση της μεταγλώττισης. Παίρνει σαν είσοδο την έξοδο της Σημασιολογικής Ανάλυσης. Η έξοδος της Παραγωγής Κώδικα είναι ο κώδικας μηχανής ή η γλώσσα assembly για το υλικό-στόχο. Η παραγωγή κώδικα μπορεί να γίνει κατευθείαν σε γλώσσα μηχανής ή να γίνει πρώτα σε μια ενδιάμεση αναπαράσταση (ανεξάρτητη της μηχανής), να βελτιστοποιηθεί (για βέλτιστη διαχείριση των πόρων) και να μεταφραστεί τελικά σε γλώσσα μηχανής.! Για να γραφεί ένας καλός γεννήτορας κώδικα απαιτείται πολύ καλή γνώση της αρχιτεκτονικής της μηχανής-στόχου.
1.3 Διερμηνείς, Συμβολομεταφραστές Συνήθως υπάρχουν δύο τρόποι να τρέξει ένα πρόγραμμα γραμμένο σε γλώσσα υψηλού επιπέδου: 1. Να μεταφραστεί στη γλώσσα μηχανής του υπολογιστή (με μεταγλωττιστή) 2. Να διαβάζονται και να μεταφράζονται οι εντολές του μία προς μία (με διερμηνέα) Μεταγλωττιστής Διερμηνέας Πλεονεκτήματα Ανάλυση και μετάφραση του προγράμματος μόνο μία φορά ώστε αυτό να τρέξει. Εύκολη ανάπτυξη και αποσφαλμάτωση προγραμμάτων (πιθανό λάθος στον πηγαίο κώδικα εμφανίζεται αμέσως, λόγω της σταδιακής μετάφρασης). Μειονεκτήματα Πιθανό λάθος στο πηγαίο θα υπάρχει και στον κώδικα μηχανής. Εκεί, θα το εντοπίσει ο μεταγλωττιστής και θα το αναζητήσει στον πηγαίο κώδικα. Πιο αργός από μεταγλωττιστή. Η επιλογή ανάμεσα σε μεταγλωττιστή ή διερμηνέα εξαρτάται και από τη φύση της εκάστοτε γλώσσας υψηλού επιπέδου. (π.χ. σελ. 20) Συμβολομεταφραστές: σελ. 20-21
1.5 Η Ανάπτυξη ενός Μεταγλωττιστή Κριτήρια επιλογής γλώσσας υλοποίησης: Η γλώσσα υλοποίησης πρέπει: 1. Να είναι κατάλληλη για την ανάπτυξη μεγάλων προγραμμάτων 2. Να υποστηρίζει χειρισμό χαρακτήρων 3. Να υλοποιείται εύκολα, για να μην είναι αργός ο μεταγλωττιστής Παλαιότερα, η γλώσσα υλοποίησης ήταν κατά κανόνα η συμβολική γλώσσα της μηχανής-στόχου. Όντως, η συμβολική γλώσσα είναι αποδοτική και με εύκολη υλοποίηση, ωστόσο, επειδή είναι γλώσσα χαμηλού επιπέδου, δε διευκολύνει τη συγγραφή μεγάλων προγραμμάτων και δεν είναι μεταφέρσιμη. Χαρακτηριστικά ποιότητας μεταγλωττιστή: 1. Ορθότητα (λειτουργεί χωρίς λάθη) 2. Μεταφερσιμότητα (ανεξάρτητος υλικού) (π.χ. αν ο μεταγλωττιστής σχεδιαστεί έτσι ώστε το σύστημα διεπαφής μεταξύ της φάσης της ανάλυσης και της φάσης της σύνθεσης είναι σχετικά απλό και ορατό) 3. Αποδοτικότητα (γρήγορος, απαιτεί λίγη μνήμη) 4. Συντηρησιμότητα (εύκολη πραγματοποίηση αλλαγών) 5. Ανθεκτικότητα (μπορεί να συνεχίζει σωστά μετά την εύρεση λαθών) 6. Φιλικότητα προς το χρήστη (κατανοητά διαγνωστικά μηνύματα) Ταχύτητα και αρχιτεκτονικές υπολογιστών: σελ. 27-29
2. Στοιχεία Θεωρίας Γλωσσών Οι γραμματικές ορίζουν τη σύνταξη της πηγαίας γλώσσας. Χρησιμοποιούμε κανόνες γραμματικής ή κανόνες παραγωγής ώστε να μπορέσουμε να χωρίσουμε μια γλώσσα στα απλούστερα μέρη της, φτάνοντας τελικά στα πιο απλά στοιχεία της, τους χαρακτήρες. Κανόνες Παραγωγής Παράδειγμα: εντολή_αντικατάστασης μεταβλητή':=' έκφραση Δηλαδή μια εντολή αντικατάστασης είναι μια μεταβλητή ακολουθούμενη από το σύμβολο ':=' (οντότητα που εμφανίζεται ακριβώς έτσι στο πρόγραμμα, γι' αυτό χρησιμοποιούμε τις αποστρόφους), το οποίο με τη σειρά του ακολουθείται από μία έκφραση. Η σύνταξη μιας γλώσσας ορίζεται με ένα σύνολο τέτοιων κανόνων παραγωγής. Στην κορυφή της σύνταξης μιας γλώσσας πρέπει να υπάρχει μια μοναδική οντότητα από την οποία προκύπτουν όλα τα συντακτικά σωστά προγράμματα: S A 1 A 2 A 3...A n Το S είναι σύμβολο εκκίνησης ή αρχικό σύμβολο. Μια πρόταση πρέπει να προέρχεται από το S χρησιμοποιώντας τους κανόνες παραγωγής, μέσα από διαδοχικές αντικαταστάσεις. (σε μία πραγματική γλώσσα το S είναι το πρόγραμμα το οποίο ορίζεται από κάποια παραγωγή, ανάλογα με την γλώσσα) Αν έχουμε περισσότερους από έναν κανόνες παραγωγής που δίνουν διαφορετικούς ορισμούς του ίδιου συμβόλου, δηλαδή: A X 1 X 2 X 3...X n A Y 1 Y 2 Y 3...Y m τότε γράφουμε: A X 1 X 2 X 3...X n Y 1 Y 2 Y 3...Y m Επιπλέον, οι κανόνες παραγωγής μπορεί να είναι αναδρομικοί, π.χ.: A Ax y Τέλος, μπορεί να θέλουμε να τονίσουμε για κάποιο λόγο πως ένα σύμβολο δεν μπορεί να αντικατασταθεί καθόλου. Αυτό γίνεται χρησιμοποιώντας την κενή συμβολοσειρά, π.χ.: Α Β ε Συντακτικά Δέντρα
σελ. 34-35 Συμβολοσειρές Συμβολοσειρά ονομάζεται μια πεπερασμένη ακολουθία συμβόλων a 1 a 2 a 3...a n, όπου κάθε a i ανήκει σε κάποιο πεπερασμένο αλφάβητο Σ. Οι επαναλήψεις επιτρέπονται. Το μήκος x μιας συμβολοσειράς δίνεται από τον αριθμό συμβόλων της. Η κενή συμβολοσειρά ε δεν περιέχει κανένα σύμβολο, συνεπώς x =0. Αλφάβητο: μη κενό πεπερασμένο σύνολο συμβόλων. Τα σύμβολα αυτά εμφανίζονται στις συμβολοσειρές. Εάν το κενό σύμβολο εμφανίζεται στις συμβολοσειρές, τότε πρέπει να ανήκει στο αλφάβητο. Το κενό σύμβολο για αποφυγή σύγχυσης συμβολίζεται ^. Προσοχή, το ^ έχει x =1 και δεν είναι το ίδιο με την κενή συμβολοσειρά ε! Σ*: το σύνολο όλων των συμβολοσειρών πεπερασμένου μήκους (υπερσύνολο του Σ). Έχει άπειρο αριθμό στοιχείων και περιλαμβάνει και το ε. π.χ.: Σ = { 0, 1 }, Σ* = { ε, 0, 1, 00, 01, 10, 11, 000, } Παράθεση: Έστω συμβολοσειρά x, μήκους m (x ϵ Σ*), και y μήκους n (y ϵ Σ*). Η παράθεση των x και y συμβολίζεται xy και είναι συμβολοσειρά μήκους m+n. Τα m πρώτα σύμβολα είναι μια συμβολοσειρά ίση με την x και τα n επόμενα ίση με την y. Προσοχή: xy yx εx = xε = x, x ϵ Σ*.
2.2 Γραμματικές Οι κανόνες μιας γραμματικής κάνουν χρήση συμβόλων, τα οποία αποτελούν το αλφάβητο της γραμματικής. Πρόκειται για σύμβολα που: α) εμφανίζονται σε προτάσεις γραμματικής ή β) εμφανίζονται στο αριστερό μέλος των κανόνων γραμματικής, ορίζοντας κάποιες ομαδοποιήσεις συμβόλων. Αυτά όμως δε λαμβάνουν μέρος σε προτάσεις της γραμματικής! Σύμβολο εκκίνησης / Αρχικό σύμβολο: (συνήθως συμβολίζεται S). Όλες οι προτάσεις προέρχονται από αυτό το σύμβολο μέσα από διαδοχικές αντικαταστάσεις, χρησιμοποιώντας τους κανόνες γραμματικής. Κανόνες γραμματικής / Κανόνες παραγωγής: υποδεικνύουν μια ομάδα συμβόλων (συμβολοσειρά) στα δεξιά του κανόνα. Η συμβολοσειρά αντικαθιστά το αριστερό μέλος της παραγωγής. Το αλφάβητο χωρίζεται σε δύο σύνολα, η τομή των οποίων είναι το κενό σύνολο: Το αλφάβητο των τερματικών συμβόλων. Τα τερματικά σύμβολα δεν αναλύονται περισσότερο και εμφανίζονται σε προτάσεις γραμματικής. Προσοχή, δεν αποκλείεται να βρίσκονται όμως και στο αριστερό μέλος ενός κανόνα, π.χ. CC cc. Το αλφάβητο των μη τερματικών συμβόλων. Σε αυτά ανήκουν όλα τα υπόλοιπα σύμβολα της γραμματικής, στα οποία περιλαμβάνεται και το αρχικό σύμβολο (S). Συνήθεις συμβολισμοί: S : Αρχικό σύμβολο / Σύμβολο εκκίνησης V : Αλφάβητο (γενικά) T ή V T : Τερματικό αλφάβητο N ή V N : Μη τερματικό αλφάβητο Συνεπώς: Μία γραμματική G είναι μία τετράδα { S, P, N, T }, όπου: S είναι το σύμβολο εκκίνησης ή αρχικό σύμβολο και S ϵ N, P είναι ένα σύνολο κανόνων παραγωγής, N είναι ένα σύνολο από μη τερματικά σύμβολα, T είναι το σύνολο των τερματικών συμβόλων.
Πρόταση: Μία σειρά συμβόλων που ανήκουν στο T (άρα τερματικών) και προέρχονται από το S χρησιμοποιώντας τους κανόνες του συνόλου P. Προτασιακός τύπος: Μία σειρά συμβόλων που προέρχεται από το S και ανήκουν είτε στο T είτε στο N (άρα μπορούν να είναι και μη τερματικά). Η γλώσσα L(G) που ορίζεται από τη γραμματική G, είναι το σύνολο όλων των προτάσεων που μπορούν να παραχθούν από τη G. σελ. 43-48 Δύο γραμματικές G και G' λέγονται ισοδύναμες, αν οι γλώσσες που παράγουν είναι ίδιες. Αυτό όμως δε σημαίνει απαραίτητα πως για κάθε πρόταση έχουν τα ίδια συντακτικά δέντρα! Το συντακτικό δέντρο συχνά δείχνει κάποια σημασιολογική δομή της γλώσσας. Πολλές φορές είναι πιο βολικό να μετασχηματισθεί η γραμματική μιας γλώσσας σε μία ισοδύναμη (για να διευκολυνθεί η συντακτική ανάλυση). Οι αλλαγές που θα προκληθούν όμως στο συντακτικό δέντρο μπορεί να απωλέσουν κάποια σημασιολογική πληροφορία. Διφορούμενη είναι μια γραμματική που επιτρέπει περισσότερα του ενός συντακτικά δέντρα για κάποιες προτάσεις. Για να αποφευχθεί αυτό, ορίζουμε κάποιους κανόνες κάθε φορά (όπως πχ η προτεραιότητα των προσήμων * και / έναντι των + και -) ή ξαναγράφουμε τη γραμματική αναλυτικότερα. Οι αναδρομικοί κανόνες είναι της μορφής: λίστα_μεταβλητών μεταβλητή λίστα_μεταβλητών, μεταβλητή Χωρίζονται στους αριστερά αναδρομικούς (Α u Αν), και τους δεξιά αναδρομικούς (Α u να). Η αριστερή αναδρομικότητα μπορεί να προκαλέσει προβλήματα σε κάποιες μεθόδους συντακτικής ανάλυσης, γι' αυτό χρησιμοποιούμε έναν απλό μετασχηματισμό που δίνει μια δεξιά αναδρομική ισοδύναμη μεταβλητή. Αυτός ο μετασχηματισμός βέβαια κάνει τη γραμματική διφορούμενη! Μετασχηματισμός: ο αριστερά αναδρομικός κανόνας A u να
αντικαθίσταται από 2 κανόνες: Α uβ, (σελ. 56) Β vb ε Περισσότερο-αριστερή(/δεξιά) παραγωγή: πάντα αναπτύσσουμε το περισσότερο αριστερό(/δεξί) μη τερματικό σύμβολο σε έναν προτασιακό τύπο. (σελ. 57-60) Πεπερασμένα αυτόματα κανονικές γραμματικές Αυτόματα στοίβας γραμματικές χωρίς συμφραζόμενα Πεπερασμένα Αυτόματα: Κάθε κανονική γραμματική μπορεί να αναπαρασταθεί σαν ένα γράφημα μετάβασης με κατευθυνόμενα βέλη και κόμβους. Κάθε κόμβος έχει ονομαστεί με κάποιο μη τερματικό σύμβολο που ανήκει στο Ν. Ο κόμβος που συμβολίζει το τέλος (τελικός κόμβος) έχει ονομαστεί με το σύμβολο # και συμβολίζεται με τετράγωνο, ενώ όλοι οι υπόλοιποι με κύκλο. Ο κόμβος που έχει ονομαστεί με το σύμβολο εκκίνησης S χαρακτηρίζεται από ένα τόξο που δείχνει προς αυτόν. Αν υπάρχει μια παραγωγή A ab στο P, τότε ο κόμβος Α θα ενώνεται Αν υπάρχει μμε τον κόμβο Β με ένα βέλος. Το βέλος θα ονομάζεται a και θα δείχνει προς τον Β. Για παραγωγή A a στο P, τότε ο κόμβος Α θα ενώνεται με τον # με ένα βέλος που δείχνει προς τον # και θα ονομάζεται a. Τα ονόματα των τόξων που περιέχονται σε ένα μονοπάτι αποτελούν μια συμβολοσειρά που είναι πρόταση της γραμματικής. Πεπερασμένο Αυτόματο: ένα γράφημα μετάβασης με αρχικό κόμβο και έναν ή περισσότερους τελικούς κόμβους. Ντετερμινιστικό Πεπερασμένο Αυτόματο: το πολύ ένα τόξο με ένα συγκεκριμένο όνομα μπορεί να φύγει από κάθε κόμβο. Μη-ντετερμινιστικό Πεπερασμένο Αυτόματο: περισσότερα από ένα βέλη με το ίδιο όνομα ξεκινούν από τον ίδιο κόμβο (σύνολο πιθανών επόμενων καταστάσεων) Ένα ΝΠΑ είναι μια πεντάδα M = ( K, T, δ, S, F ), όπου: 1. Κ : πεπερασμένο σύνολο καταστάσεων (κάθε κατάσταση παριστάνεται με κόμβο). 2. Τ : πεπερασμένο αλφάβητο εισόδου. 3. δ : συνάρτηση μετάβασης KxT K. η οποία όταν δίνονται μία
κατάσταση και ένα σύμβολο εισόδου καθορίζει την επόμενη κατάσταση. 4. S K : η αρχική κατάσταση (η αρχική κατάσταση έχει ένα βέλος που δείχνει προς τον κόμβο που την περιέχει). 5. F K : σύνολο τελικών καταστάσεων (οι τελικές καταστάσεις παριστάνονται με τετράγωνους κόμβους αντί κυκλικούς). (στις παρενθέσεις περιγράφεται η παράσταση ενός ΝΠΑ ως γράφημα μετάβασης) σελ. 63 παράδειγμα Παράσταση ενός ΝΠΑ με τη μορφή του πίνακα αλλαγής κατάστασης: Πρώτη γραμμή: Τ Πρώτη στήλη: Κ Υπόλοιπα κελιά: επόμενες νέες καταστάσεις. Αν δεν υπάρχουν επόμενες, μένουν κενά. Σύμβολο $ στην πρώτη γραμμή: τέλος εισόδου. Σελ. 64 παράδειγμα Ένα ΠΑ (ΝΠΑ/ΜΠΑ) λειτουργεί όχι ως γεννήτρια, αλλά ως αναγνωριστής συμβολοσειρών μιας γραμματικής. σελ. 65 παράδειγμα Ένα Μη-ντετερμινιστικό Πεπερασμένο Αυτόματο (ΜΠΑ) είναι μια πεντάδα Μ = ( Κ, Τ, δ, S, F ), όπου: 1. Κ : πεπερασμένο σύνολο καταστάσεων 2. Τ : πεπερασμένο αλφάβητο εισόδου 3. δ : συνάρτηση μετάβασης ΚxΤ 2 Κ 4. S : αρχική κατάσταση 5. F K : σύνολο τελικών καταστάσεων Αν δεν υπάρχουν επόμενες καταστάσεις για ένα ζεύγος ( k, a ) KxT, τότε δ ( k, a ) = O ΝΠΑ προτιμότερο από ΜΠΑ. Υπάρχει αλγόριθμος μετατροπής ΜΠΑ σε ΝΠΑ.
Αυτόματα Στοίβας Χειρισμός γλωσσών χωρίς συμφραζόμενα Σχεδιασμός και υλοποίηση συντακτικών αναλυτών Χρήση μνήμης σε μορφή στοίβας Το ένα άκρο της στοίβας είναι η κορυφή και το άλλο η βάση της. Οι βασικές λειτουργίες της στοίβας είναι: 1. PUSH (τοποθετεί ένα στοιχείο στην κορυφή της) 2. POP (αφαιρεί το στοιχείο που βρίσκεται στην κορυφή της) V : σύνολο στοιχείων που μπορούν να εμφανιστούν στη στοίβα. V* : περιεχόμενα στοίβας Το τέλος της συμβολοσειράς συμβολίζει την κορυφή της στοίβας. Τα σύμβολα στη στοίβα (βάση κορυφή) γράφονται σαν συμβολοσειρά από αριστερά προς τα δεξιά. π.χ. Σελ 67-68 σελ. 68-89
3. Τρόποι Προσδιορισμού Σύνταξης Τρόποι προσδιορισμού της σύνταξης: 1. Γραμματικές 2. Σύνολα 3. BNF (Backus-Naur Form) 4. Συντακτικά διαγράμματα 5. EBNF (Extended BNF) 3.1 Σύνολα Για πολύ απλές γλώσσες ενδεχομένως να μπορεί να φτιαχτεί μια πλήρης λίστα των έγκυρων συμβολοσειρών. Η λίστα αυτή αποτελεί το σύνολο που ορίζει τη γλώσσα. Ο συμβολισμός αυτός είναι σπάνιος. 3.a Γραμματικές Χωρίς Συμφραζόμενα (Context Free Grammars) Οι γραμματικές χωρίς συμφραζόμενα αποτελούνται από τερματικά, μη τερματικά, αρχικά σύμβολα και παραγωγές. τερματικά είναι τα βασικά σύμβολα από τα οποία σχηματίζονται τα αλφαριθμητικά. Πολλές φορές προέρχονται από το αποτέλεσμα του λεξικού αναλυτή. Παράδειγμα για την ακόλουθη δήλωση: stmt if ( expr ) stmt else stmt τα τερματικά θα ήταν if, else, (, ) μη τερματικά είναι οι μεταβλητές που υποδηλώνουν περιέχουν σύνολα αλφαριθμητικών. Για το παραπάνω παράδειγμα, μη τερματικά θα ήταν τα stmt και expr.τα μη τερματικά επιβάλλουν ιεραρχική δομή στην γλώσσα και αυτό είναι το κλειδί για την συντακτική ανάλυση και μεταγλώττιση. Στην γραμματική ένα μη τερματικό ορίζεται σαν αρχικό σύμβολο και τα σύνολα που υποδηλώνει είναι αυτά που παράγουν την γλώσσα με βάση την γραμματική. Οι παραγωγές της γλώσσας ορίζουν τον τρόπο με τον οποίο μπορούν να συνδυαστούν τερματικά και μη τερματικά σύμβολα. 3.2 BNF Μεταγλώσσα που χρησιμοποιείται συχνά για να οριστεί η σύνταξη
μιας γλώσσας προγραμματισμού. Τεχνική παράστασης κανόνων για την παραγωγή προτάσεων μιας γλώσσας. Γλώσσες χωρίς συμφραζόμενα Σύμβολα Χρησιμότητα < > περικλείουν μη τερματικά σύμβολα ::= το μη τερματικό αριστερά πρέπει αντικατασταθεί από την έκφραση που βρίσκεται δεξιά δηλώνει την επιλογή μεταξύ της έκφρασης εξ αριστερών και εκ δεξιών του Αντιστοιχία συμβολισμού BNF και συμβολισμού γραμματικής: 1. < > : τερματικά σύμβολα 2. ::= : 3. : χωρίζει τα δεξιά μέλη μεταξύ τους, όταν στο αριστερό μέλος αντιστοιχούν περισσότεροι από ένας συντακτικούς κανόνες. Παράδειγμα αριθμητικών εκφράσεων σε BNF : <expression> ::= <term> <expression> "+" <term> <term> ::= <factor> <term> "*" <factor> <factor> ::= <constant> <variable> "(" <expression> ")" <variable> ::= "x" "y" "z" <constant> ::= <digit> <digit> <constant> <digit> ::= "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" Σελ. 94-99 παραδείγματα 3.3 Συντακτικά διαγράμματα Γραφικός τρόπος προσδιορισμού της σύνταξης, με κύκλους για τα τερματικά σύμβολα και τετράγωνα για τα μη τερματικά. Κάθε μη τερματικό σύμβολο ορίζεται από άλλο συντακτικό διάγραμμα (ώσπου να φτάσουμε σε συντακτικό διάγραμμα αποτελούμενο μόνο από κύκλους).
3.4 EBNF Συμβολισμός: 1. : τερματικά σύμβολα (αντί < >). 2. Τελεία στο τέλος κάθε συντακτικού κανόνα. 3. = αντί ::= 4. [ ] : σύμβολα που εμφανίζονται 0 ή 1 φορά. 5. { } : σύμβολα που εμφανίζονται 0 ή περισσότερες φορές. 6. ( ) για ομαδοποίηση συμβόλων. Παράδειγμα Αριθμητικών Εκφράσεων σε EBNF expression = term, {"+", term}; term = factor, {"*", factor}; factor = constant variable "(", expression, ")"; variable = "x" "y" "z"; constant = digit, {digit}; digit = "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"; το αντίστοιχο σε BNF βρίσκετε πιο πάνω Διαφορές/Αντιστοιχίες BNF EBNF BNF EBNF μη τερματικά σύμβολα μέσα σε < > τερματικά σύμβολα μέσα σε σύμβολο ανάθεσης ::= σύμβολο ανάθεσης = δύο σύμβολα που παρατίθενται χωρίζονται με το κενό δύο σύμβολά που παρατίθενται χωρίζονται με κόμμα, ακολουθεί το αντίστοιχο συντακτικό διάγραμμα : Σελ. 103-105 για περισσότερα παραδείγματα 3.4.3 Συσχετισμός EBNF με συντακτικά διαγράμματα Εάν έχουμε κάποιους συντακτικούς κανόνες σε EBNF μπορούμε να τους παραστήσουμε σε συντακτικά διαγράμματα με βάση τους παρακάτω κανόνες:
τα τερματικά συμβολίζονται με οβάλ, ενώ τα μη τερματικά με ορθογώνια κανόνας EBNF Αναπαράσταση συνεκτικού διαγράμματος A = α1 α2 αn Α = α1, α2,, αn {α} α{α} [α]
Παράδειγμα Αριθμητικές Εκφράσεις : για τo αντίστοιχο EBNF παράδειγμα δείτε παραπάνω περισσότερα σελ. 106-111
4 Λεκτική Ανάλυση 4.1 Λεκτικός Αναλυτής 4.2.1 Σχεδίαση Πεπερασμένου Αυτόματου που αντιστοιχεί σε κανονική έκφραση Για να μετατρέψουμε μία κανονική έκφραση σε πεπερασμένο αυτόματο, αρκεί να ακολουθήσουμε τα παρακάτω βήματα: 1. Κατασκευάζουμε ένα Μη Ντετερμινιστικό Πεπερασμένο Αυτόματο από μια κανονική έκφραση. 2. Μετατρέπουμε το Μη Ντετερμινιστικό Πεπερασμένο Αυτόματο σε Ντετερμινιστικό Πεπερασμένο Αυτόματο. 3. Ελαχιστοποιούμε το Ντετερμινιστικό Αυτόματο.
4.2.1.1 Μετατροπή κανονικής έκφρασης σε Μη ντετερμινιστικό Πεπερασμένο Αυτόματο Στο Μη Ντετερμινιστικό Πεπερασμένο αυτόματο έχουμε την δυνατότητα να μεταβούμε από μία κατάσταση σε μία άλλη με κατανάλωση του κενού συμβόλου. Το κενό συμβολίζεται με ε και οι μεταβάσεις λέγονται ε μεταβάσεις. Οι ε μεταβάσεις είναι χρήσιμες, γιατί διευκολύνουν την διαδικασία της μετατροπής. Το ΜΠΑ για την κανονική έκφραση a*b* Κανόνες Παραγωγής : Κανονική έκφραση ΜΠΑ α R1R2 R1 R2 R *
συμβολισμοί κατάσταση, το όνομα επιλέγεται αυθαίρετα αλλά πρέπει να είναι μοναδικό, συνηθίζεται το Si κατάσταση που θέλει επέκταση, το όνομα επιλέγεται αυθαίρετα μπορεί να είναι είτε Ri, είτε η κανονική έκφραση που θα επεκταθεί τελική κατάσταση Τεχνική κατασκευής: Προσπαθούμε να μετασχηματίσουμε την κανονική έκφραση με την χρήση μεταβλητών ( Ri ) σε κάποια μορφή που να έχουμε κατάλληλο κανόνα παραγωγής, ξεκινώντας από ολόκληρη την πρόταση και πηγαίνοντας αναδρομικά σε κάθε κομμάτι της. παράδειγμα : Αν έχουμε το a( a b ) που δεν έχουμε κανόνα παραγωγής γίνετε R2 R1 το R2 θα αντικατασταθεί από το a που έχουμε κανόνα παραγωγής το R1 που περιέχει a b θα γίνει R3 R4 που έχουμε κανόνα παραγωγής και τα R3, R4 θα γίνουν a, b που έχουμε κανόνα παραγωγής έτσι έχουμε : R = a( a b) = R2R1 R2 = a R1 = R3 R4 R3 = a R4 = b ακολουθεί η υλοποίηση :
προσοχή αν έχουμε τελική κατάσταση και αυτή ακολουθείται απο ε μετάβαση, τότε αυτή παύει να είναι τελική. Σημείωση: αν δεν έχουμε εμφωλευμένους κανόνες (πχ a(b c) ) αλλά παρατεταγμένους (πχ abc), η πιο ασφαλής λύση είναι να τους εμφωλεύσουμε, ώστε να τους αντιμετωπίσουμε όπως στο προηγούμενο παράδειγμα δηλαδή το abc να το αντιμετωπίσουμε σαν a(bc), οπότε και θα ορίσουμε το R1 = a και το R2 = bc όμως δεν υπάρχει κανόνας αρά το R2 = R3R4 με R3 = b και R3 = c.
Για πολύπλοκες ή επαναλαμβανόμενες εκφράσεις μπορούμε να κάνουμε την ανάπτυξη τους σε διαφορετικό σχήμα. Για παράδειγμα αν έχουμε R = (a b c )(a b c )( a b c) * μπορούμε εξαρχής να ορίσουμε a b c ως R1 και το R να γίνει R1R1R1*. Έτσι, μπορούμε να δουλέψουμε ξεχωριστά τα R1 και R. Εδώ χρειάζεται ιδιαίτερη προσοχή για να ακολουθήσουμε τον κανόνα για τις τελικές καταστάσεις που ακολουθούνται από ε μεταβάσεις, καθώς στην R1 μπορεί να μην είναι ορατή η ε μετάβαση. Ακολουθεί η επίλυση. για το R: R = R1,1 R1,2 R1,3* = (R1,1 R1,2 )(R1,3 *) = R2R3 R2 = R1R1 R3 = R1* στην φάση αυτή θα μπορούσαμε να επεκτείνουμε το R1,όμως για λόγους εξοικονόμισης χρόνου, χώρου και ευκολία ανάγνωσης επιλέγουμε να το σχεδιάσουμε ξεχωριστά. για το R1 R1 = a b c => R1 = ( a b ) c => R1 = R4 R5 R5 = c R4 = R6 R7 R6 = a R7 = b
επειδή τα R1,1 R2,2 R3,3 ακολουθούνται από ε μεταβάσεις, η τελική κατάσταση S6,j θα γίνει μη τελική.
4.2.1.2 Αλγόριθμος μετατροπής απο Μη ντετερμινιστικό Πεπερασμένο σε Ντετερμινιστικό Πεπερασμένο Αυτόματο Για τον αλγόριθμο μας είναι χρήσιμη η έννοια της ε πληρότητας ε πληρότητα μια κατάστασης S ενός Μη ντετερμινιστικού Πεπερασμένου Αυτομάτου είναι το σύνολο των καταστάσεων του ΜΠΑ που μπορούν να προσεγγιστούν από την S αναδρομικά μόνο μέσω ε μεταβάσεων. Έπειτα θα χρειαστούμε και την πληρότητα ενός συνόλου T. πληρότητα ενός συνόλου Τ είναι το σύνολο των καταστάσεων που μπορούν να προσεγγισθούν μέσω ε μεταβάσεων από καταστάσεις που είναι μέλη του συνόλου Τ. Η ε πληρότητα της S περιέχει την S, καθώς και η ε πληρότητα Τ περιέχει τις καταστάσεις του Τ. Παράδειγμα: ε πληρότητα(1) = {1} ε πληρότητα(2) = {2,3,5,7} ε πληρότητα(3) = {3,5,7} ε πληρότητα(4) = {4} ε πληρότητα(5) = {5} ε πληρότητα(6) = {6,4} ε πληρότητα(7) = {7} ε πληρότητα(8) = {8,4} σημείωση: Δεν είναι ανάγκη να προϋπολογίσουμε τις ε πληρότητες όλων των καταστάσεων, ίσως κάποιες να μην χρειαστούν. παράδειγμα αλγορίθμου: 1.Ξεκινάμε από την αρχική κατάσταση του ΜΠΑ και βρίσκουμε την ε πληρότητά της. 2.Φτιάχνουμε μια αρχική κατάσταση Α στο ΝΠΑ (η Α θα αντιστοιχεί στην ε πληρότητα του S1)
3.Από την ε πληρότητα του S1 βρίσκουμε όλες τις δυνατές μεταβάσεις με βάση την είσοδο (εδώ για παράδειγμα έχουμε δυνατή μετάβαση μόνο αν έχουμε είσοδο το a). Για κάθε δυνατή μετάβαση δημιουργούμε μια νέα κατάσταση στο ντετερμινιστικό (εδώ για παράδειγμα την Β). Η νέα κατάσταση του ντετερμινιστικού θα αντιστοιχεί με την ε πληρότητα της κατάστασης στην οποία μεταβαίνει το μη ντετερμινιστικό (εδώ για παράδειγμα την ε πληρότητα της S2). Από την ε πληρότητα της S2 είναι δυνατές δύο μεταβάσεις: για είσοδο a είναι δυνατή η μετάβαση στο S6, και για είσοδο b είναι δυνατή η μετάβαση στο S8. Επομένως, θα δημιουργήσουμε δύο νέες καταστάσεις στο ντετερμινιστικό: την C και την D, που θα αντιστοιχούν με την ε πληρότητα της S6 και της S8 αντίστοιχα. Μάλιστα, επειδή η ε πληρότητα της S6 και της S8 περιέχουν τελικές καταστάσεις, οι C και D θα είναι τελικές καταστάσεις στο ΝΠΑ.
Δεύτερο παράδειγμα: ΜΠΑ σε ΝΠΑ ( f g )* x: αρχική κατάσταση ΜΠΑ S3 ε πληρότητα(3) = { 5,9,6,4,1} δημιουργούμε την κατάσταση Α στο ΝΠΑ βρίσκουμε όλες τις δυνατές μεταβάσεις για είσοδο f είναι { 10 }, για g είναι {7}, για x είναι {2}
Για κάθε μια από τις δυνατές μεταβάσεις του S3 στο ΜΠΑ φτιάχνουμε μια νέα κατάσταση στο ΝΠΑ, όπου θα αντιστοιχεί με την ε πληρότητα των καταστάσεων που μεταβαίνει η S3 στο ΜΠΑ. Έτσι, έχουμε τις B, C, D που αντιστοιχούν στην ε πληρότητα του S10, S7 και S2 αντίστοιχα. Η D είναι τελική κατάσταση καθώς η ε πληρότητα του S2 περιέχει τελική κατάσταση. Για οποιαδήποτε είσοδο δεν υπάρχει δυνατή μετάβαση από την S2 και άρα και από την D. Η S10, άρα και η B έχει δυνατές μεταβάσεις: για f στην S10, όμως η πληρότητα της S10 έχει αντιστοιχιστεί στην B, άρα έχουμε μετάβαση στον εαυτό της.
για g στην S7, όμως η πληρότητα της S7 έχει αντιστοιχιστεί στην C, άρα έχουμε μετάβαση στην C για x στην S2, όμοια με τα παραπάνω έχουμε μετάβαση στην D για την C όμοια με την Β θα έχουμε για το f στο B για το g στο C για το x στο D
Πίνακας καταστάσεων Κ\Τ f g x $ A B C D B B C D C B C D D Τελική
4.2.2.3 Ελαχιστοποίηση καταστάσεων K\T e f g x $ A B C D B A C B C D D Τελική χωρίζουμε τις καταστάσεις σε τελικές και μη τελικές μη τελικές: { A,B,C} = P τελικές : {D} = Q μπορούμε να αναπαραστήσουμε τα παραπάνω σύνολα με το ακόλουθο τρόπο Βήμα 1:
Στον πίνακα P δηλαδή έχουμε: Από την κατάσταση Α με είσοδο e οδηγούμαστε στην κατάσταση B. Από την κατάσταση Α με είσοδο f οδηγούμαστε στην κατάσταση C. Από την κατάσταση A με είσοδο g δεν οδηγούμαστε σε καμία κατάσταση. Από την κατάσταση Α με είσοδο x οδηγούμαστε στην κατάσταση D. Από την κατάσταση B με είσοδο e δεν οδηγούμαστε σε καμία κατάσταση. Από την κατάσταση B με είσοδο f δεν οδηγούμαστε σε καμία κατάσταση. Από την κατάσταση B με είσοδο g οδηγούμαστε στην κατάσταση A. Από την κατάσταση B με είσοδο x δεν οδηγούμαστε σε καμία κατάσταση. Από την κατάσταση C με είσοδο e οδηγούμαστε στην κατάσταση B. Από την κατάσταση C με είσοδο f οδηγούμαστε στην κατάσταση C. Από την κατάσταση C με είσοδο g δεν οδηγούμαστε σε καμία κατάσταση. Από την κατάσταση C με είσοδο x οδηγούμαστε στην κατάσταση D. Στον πίνακα Q έχουμε την τελική κατάσταση D, η οποία με οποιαδήποτε είσοδο δεν οδηγεί σε καμία κατάσταση. Επομένως, έχουμε: P = { A, B, C }, Q = { D } Βήμα 2: Έτσι, μπορούμε να αντικαταστήσουμε τα A, B, C με P και το D με Q: Βήμα 3: Θα χωρίσουμε τους πίνακες σύνολα, έτσι ώστε ο κάθε πίνακας να περιέχει
καταστάσεις που με ίδιες εισόδους οδηγούν σε ίδια σύνολα καταστάσεων P, Q. Ο αριθμός των πινάκων συνόλων προφανώς θα είναι 3: R = { A, C }, S = { B }, T = { D } Βήμα 4: Τώρα, θα κάνουμε τον τελευταίο διαχωρισμό. Χρησιμοποιώντας τους αρχικούς πίνακες (του βήματος 1), αντικαθιστούμε τις καταστάσεις με τα σύνολα R,S,T στα οποία ανήκουν:
Ο τελευταίος διαχωρισμός μας έδωσε τον ίδιο διαχωρισμό με το Βήμα 3, επομένως η διαδικασία ολοκληρώθηκε. Συμπεραίνουμε ότι οι καταστάσεις A, C είναι ισοδύναμες, οπότε αντικαθίστανται από την κατάσταση R (αφού, από Βήμα 3 έχουμε R = { A, C } ). Οι καταστάσεις B, D παραμένουν (ονομάζονται όμως πλέον S και T αντίστοιχα, αφού από Βήμα 3 έχουμε S = { B }, T = { D }). Πίνακας μεταβάσεων Κ\Τ e f g x $ R S R T S R T Τελική Η κανονική έκφραση που αναγνωρίζει το αυτόματό μας είναι η (eg f)*x.
Συντακτικό διάγραμμα 5. Συντακτική και σημασιολογική ανάλυση 5.1 Στρατηγικές συντακτικής ανάλυσης Η συντακτική ανάλυση είναι η φάση του μεταγλωττιστή που δέχεται σαν είσοδο (input) ένα αρχικό πρόγραμμα (με τη μορφή συμβολοσειράς λεκτικών μονάδων) και δίνει σαν έξοδό (output) ένα συντακτικό δέντρο ή μια ένδειξη ότι το αρχικό πρόγραμμα δεν είναι συντακτικά ορθό. Δύο κατηγορίες συντακτικών αναλυτών: 1. Από πάνω προς τα κάτω ( Top down parsing): παράγουν το συντακτικό δέντρο ξεκινώντας από τη ρίζα (σύμβολο εκκίνησης) και πηγαίνουν προς τα φύλλα. 2. Από κάτω προς τα πάνω ( Bottom up parsing): ξεκινούν από τα φύλλα και πηγαίνουν προς τη ρίζα 5.2 Αναλυτές από πάνω προς τα κάτω ( Top down Parsers) Οι Top down parsers χωρίζονται σε 2 κατηγορίες 1. Συντακτικός Αναλυτής αναδρομικής κατάβασης ( recursive descent parser ή predictive parser). 2. Συντακτικούς αναλυτές LL(1).
Και οι δύο κατηγορίες προϋποθέτουν ότι η γραμματική της αντίστοιχης γλώσσας είναι LL(1). Για να ορίσουμε πότε μια γραμματική είναι LL(1), πρέπει να ορίσουμε τα σύνολο FIRST και FOLLOW για κάποιο X Ε N T καθώς επίσης και τη συνάρτηση LOOKAHEAD για κάποιο κανόνα παραγωγής pep, όπου P το σύνολο κανόνων της γραμματικής. FIRST(a) Το σύνολο FIRST για κάποιο σύμβολο α της γραμματικής FIRST(a), είναι το σύνολο των τερματικών συμβόλων που εμφανίζονται στην αριστερή πλευρά των συμβολοσειρών που παράγονται απο το α. Υπολογισμός FIRST 1. Αν α * t β, τότε το t First(a) 2. First(t) = { t }, αν το t είναι τερματικό 3. το ε First(X) αν X ε αν Χ > Α1 Αn και ε First(Ai) για i <= n 4. First(α) υποσύνολο του First(X) Αν Χ Α1...Αn α και ε First(Ai) με i <= n Παράδειγμα για την γραμματική: Ε Τ Χ Τ ( Ε ) int Y X + E ε Υ * Τ ε ξεκινάμε με τα τερματικά First(+) = {+} First(*) = {*} First( ( ) = { ( }
First( ) ) = { ) } First( int ) = { int } Για τα μη τερματικά Ξεκινάμε από το Ε: First(E) First(T) από την στιγμή που δεν έχουμε άλλες πληροφορίες για το E ψάχνουμε το T Για το T : επειδή όλες οι πιθανές αντικαταστάσεις έχουν στην πρώτη θέση τερματικά First(T) = { (, int } Τώρα μπορούμε να γυρίσουμε και να δούμε το E. το ε δεν ανήκει στο First(T) αυτό σημαίνει οτι : First(E) = First(T) = { (, int } (Αυτό συμβαίνει γιατί από την στιγμή που το Τ δεν περιέχει το ε, δεν υπάρχει περίπτωση το πρώτο στοιχείο του Ε να υπάρχει στο Χ) Συνεχίζουμε για τα Χ, Υ. Το Χ στην μια περίπτωση στη πρώτη θέση έχει τερματικό και στην δεύτερη το ε άρα: First(X) = { +, ε} όμοια για το Y First(Y) = { *, ε} Βρήκαμε όλα τα First, άρα τελειώσαμε. Υπολογισμός Follow τα σύνολα follow δεν έχουν να κάνουν με το τι μπορεί να παράγει ένα σύμβολο, αλλά με το τι μπορεί να ακολουθήσει αυτό το σύμβολα σε οποιαδήποτε περίπτωση για μια συγκεκριμένη γραμματική. Ορισμός: Follow(X) = { t S *β X t δ } Κανόνες: Αν X A B τότε First(B) Follow(A) Αν X A B τότε Follow(X) Follow(B) αν B * ε τότε Follow(X) Follow(Α) Αν το S είναι το αρχικό σύμβολο τότε το $ Follow(S) Αλγόριθμος
1. $ Follow(S) 2. First(β) {ε} Follow(X) για κάθε παραγωγή Α α X β 3. Follow(A) Follow(X) για κάθε παραγωγή A α Χ β με ε First(β) Παράδειγμα για την γραμματική: Ε Τ Χ Τ ( Ε ) int Y X + E ε Υ * Τ ε για την οποία ήδη έχουμε υπολογίσει τα σύνολα first πιο πάνω εξ αρχής γνωρίζουμε ότι : { $ } Follow(E) έπειτα συνεχίζουμε διερευνώντας περαιτέρω το E, έτσι πάμε να δούμε σε ποία σημεία χρησιμοποιείται: χρησιμοποιείται στο T ( E ) int Y και βλέπουμε ότι ακολουθείται από τερματικό σύμβολο,άρα { $, ) } Follow (E) ακόμα χρησιμοποιείται στο X + Ε ε επειδή το Ε είναι στο τέλος του X συμπεραίνουμε ότι Follow(X) Follow(E) (καλό είναι να το σημειώνουμε στην άκρη) δεν υπάρχουν άλλες θέσεις που να αχρησιμοποίητε το Ε, ας συνεχίσουμε με το Χ: το X εμφανίζεται στο Ε Τ Χ, από την στιγμή που είναι το τελευταίο στοιχείο : Follow(Ε) Follow(Χ) όμως ποίο πάνω βρήκαμε ότι Follow(X) Follow(E),άρα Follow(E) = Follow(X) σε αυτό το σημείο έχουμε εξετάσει κάθε σημείο που χρησιμοποιείτε το X,E μέσα στην γραμματική μας και άρα: Follow(E) = { $, ) } Follow(X) = { $, ) } Στην συνέχεια θα ασχοληθούμε με το T. το Τ εμφανίζεται στο E T X, το στοιχείο Χ ακολουθεί άρα: First(X) Follow(T), δηλαδή { + } Follow(T) επιπροσθέτως το Χ ε :
Follow(Ε) Follow(Τ), δηλαδή { +, $, ) } Follow(T) το Τ εμφανίζεται στο Y * T ε και είναι το τελευταίο σύμβολο Follow(Υ) Follow(Τ) έχουμε εξαντλήσει τις περιπτώσεις εμφάνισης του Τ, επομένως είναι καλή ιδέα να πάμε στο Y για να πάρουμε περισσότερα στοιχεία. Το Υ εμφανίζεται μόνο στο T (E) int Y είναι το τελευταίο σύμβολο, θα ισχύει: Follow(Τ) Follow(Υ),όμως ποίο πάνω δείξαμε και το Follow(Υ) Follow(Τ), άρα: Follow(Τ) = Follow(Υ) απο την στιγμή που έχουμε εξαντλήσει όλες της περιπτώσεις για Τ,Υ έχουμε : Follow(T) = { +, $, ) } Follow(Y) = { +, $, ) } Τώρα μένουν τα μη τερματικά: Για το ( : το ( μπορεί να βρεθεί μόνο στο T ( E ) int Y, επίσης Ε * ε οπότε : Follow( ( ) First(E) { ε } = { (, int } και επειδή έχουμε εξαντλήσει όλες τις περιπτώσεις: Follow( ( ) = { (, int } Για το ) : το ) μπορεί να βρεθεί μόνο στο Τ ( Ε ) int Y, βρίσκετε στο τέλος, άρα: Follow(T) Follow( ) ) και επειδή έχουμε εξαντλήσει όλες τις περιπτώσεις: Follow( ) ) = Follow(T) = { +, $,) } και έτσι βρήκαμε όλα τα σύνολα follow για την συγκεκριμένα γραμματική Για το + μπορεί να βρεθεί μόνο στο Χ + Ε ε, επίσης Ε * ε οπότε : Follow( + ) First(E) { ε } = { (, int } και επειδή έχουμε εξαντλήσει όλες τις περιπτώσεις: Follow( + ) = { (, int } Για το * : το + μπορεί να βρεθεί μόνο στο Υ * Τ ε, βρίσκεται στο τέλος, άρα: Follow(T) Follow( + ) και επειδή έχουμε εξαντλήσει όλες τις περιπτώσεις: Follow( + ) = Follow(T) = { +, $,) }
και έτσι βρήκαμε όλα τα σύνολα follow για την συγκεκριμένα γραμματική Για το int : το int μπορεί να βρεθεί μόνο στο T ( E ) int Y, επιπροσθέτως Y * ε First(Y) Follow( int ) και Follow(T) Follow( int ) αρα { *, +, $, ) } Follow( int ) και επειδή έχουμε εξαντλήσει όλες τις περιπτώσεις: { *, +, $, ) } = Follow( int ) και έτσι βρήκαμε όλα τα σύνολα Follow.
Συνάρτηση Empty Η συνάρτηση empty επιστρέφει True εάν X * ε (δλδ το Χ με κάποιο τρόπο να πάρει την τιμή ε) False σε κάθε άλλη περίπτωση Κανόνες: Empty(ε) = True Empty(AB) = True μόνο εάν Empty(A) = true και Empty(B) = true Παράδειγμα: για την γραμματική: Ε Τ Χ Τ ( Ε ) int Y X + E ε Υ * Τ ε Ποία η τιμής της Empty(E) ; Για να απαντήσουμε πρέπει να ελέγξουμε τις Empty(T) Empty(X) η Empty(T) είναι FALSE αρά και Emtpy(E) ειναι FALSE Emtpy(X) είναι TRUE Emtpy(Y) είναι TRUE Συνάρτηση LOOKAHEAD Ορίζεται η σε παραγωγές την μορφής Α Χ1 Χ2... Χn : εάν empty(x1x2...xn) = FALSE και empty(x1x2...xi 1) = True με i <= n τότε LOOKAHEAD(Α X1X2 Xi 1 Xi Xn ) = U { First(Xj) με j<=i } (τα υπογραμμισμένα έχουμε empty false, χρησιμοποιούμε την ένωση όσων είναι σκιασμένα) εάν empty(x1x2 Xn) = TRUE τότε : LOOKAHEAD(A X1X2...Xn) = U { First(Xi) με i <= n } U Follow(A) Παράδειγμα: για την γραμματική: Ε Τ Χ Τ ( Ε ) int Y X + E ε Υ * Τ ε LOOKAHEAD(E TX ) = First(T), σε αυτή την περίπτωση empty(tx) = False LOOKAHEAD(T ( E ) ) = First( ( )
LOOKAHEAD(T int Y ) = First( int ) LOOKAHEAD(X +E ) = First( + ) = {+} LOOKAHEAD(X ε ) = Follow(X) = { $, ) } LOOKAHEAD(Y *T ) = First( * ) = { * } LOOKAHEAD(Y ε ) = Follow(Y) = { +, $, ) } Ορισμός γραμματικών LL(1) Μια γραμματική χωρίς συμφραζόμενα G = ( N,T,P,S ) είναι LL(1) εάν και μόνο εάν για κάθε ζεύγος διαφορετικών παραγωγών Α α και Α β στη γραμματική που έχουν το ίδιο αριστερό μέλος, ισχύει: LOOKAHEAD(A α) LOOKAHEAD(A β) = Παράδειγμα: Ας εξετάσουμε ότι η παρακάτω γραμματική είναι LL(1) έχουμε είδη υπολογίσει τα σύνολα FIRST και FOLLOW και τις συναρτήσεις LOOKAHEAD αρκεί να εφαρμόσουμε τον κανόνα. LOOKAHEAD(X ε) LOOKAHEAD(Χ +Ε) = {$, ) } { + } = LOOKAHEAD(Υ ε) LOOKAHEAD(Υ ε) = { +, $, ) } { * } = Άρα η γλώσσα είναι LL(1) Παράδειγμα 2: Ισοδύναμος ορισμός LL(1) Μια γραμματική είναι LL(1) αν και μόνο εάν για οποισδήποτε συντακτικούς κανόνες της A α β, ισχύουν οι ακόλουθες συνθήκες: 1. Για κανένα τερματικό σύμβολο x και οι a και η β δεν παράγουν συμβολοσειρές που να αρχίζουμε με x. 2. Το πολύ ένα απο τα α και β παράγει την κενή συμβολοσειρά. 3. Εάν β * τότε το α δεν παράγει συμβολοσειρές που να αρχίζουν με έαν τερματικό σύμβολο το οποίο υπάρχει στις τιμές της συνάρτησης FOLLOW(A)
5.2.3 Συντακτικοί Αναλυτές LL(1) Μία πιο αυστηρή μέθοδος κατασκευής Συντακτικού Αναλυτή αναδρομικής κατάβασης προϋποθέτει την κατασκευή προβλέποντων συντακτικών πινάκων (predictive parsing tables).! Πριν ξεκινήσουμε την κατασκευή του προβλέποντος συντακτικού πίνακα, πρέπει να έχουμε πρώτα ορίσει τα σύνολα FIRST και FOLLOW για κάθε σύμβολο της γραμματικής. Κατασκευή προβλέποντος συντακτικού πίνακα: Πρόκειται για πίνακα του οποίου κάθε στοιχείο είναι: α) είτε κανόνας παραγωγής της γραμματικής β) είτε το κενό. Γραμμές: μη τερματικά σύμβολα Στήλες: τερματικά σύμβολα (αποτελούν τα σύμβολα εισόδου και διαβάζονται από τον αναλυτή) και $ Στοιχεία του πίνακα: εξετάζουμε όλους τους κανόνες παραγωγής. α) Αν υπάρχει παραγωγή Α x, τότε εξετάζουμε το FIRST(x). β) Για κάθε σύμβολο α!= ε (α FIRST(x) ), ο κανόνας Α x τοποθετείται στη γραμμή Α και στήλη α. γ) Αν ε FIRST(x), τότε η παραγωγή Α x τοποθετείται στη γραμμή Α και στήλη b, b FOLLOW(A). Εδώ το b μπορεί να είναι και το $. Παράδειγμα: Κανόνες παραγωγής: 1. S ( D ) 2. D EF 3. E a b S 4. F *D +D ε Σύνολα FIRST και FOLLOW: FIRST (S) = { ( } FOLLOW (S) = { $, *, +, ) } FIRST (D) = { a, b, ( } FOLLOW (D) = { ) } FIRST (E) = { a, b, ( } FOLLOW (E) = { *, +, ) } FIRST (F) = { *, + ε } FOLLOW (F) = { ) } Έστω Μ ο υπό κατασκευή πίνακας. Παίρνουμε έναν έναν τους κανόνες παραγωγής: 1. S ( D ) FIRST ( (D) ) = { ( } M ( S, ( ) = S ( D ) 2. D EF FIRST ( EF ) = FIRST ( E) = { a, b, ( } M ( D, a ) = D EF M ( D, b ) = D EF M ( D, ( ) = D EF Ο κανόνας 2 συνεισφέρει τρία στοιχεία στον πίνακα, διότι το FIRST (EF) έχει τρία
στοιχεία: a, b, (. 3. 1) E a FIRST ( a ) = { a } 3. 2) E b FIRST ( b ) = { b } 3. 3) E S FIRST ( S ) = { ( } M ( E, a ) = E a M ( E, b ) = E b M ( E, ( ) = E S 4. 1) F *D FIRST ( * ) = { * } M ( F, * ) = F *D 4. 2) F +D FIRST ( + ) = { + } M ( F, + ) = F +D 4. 3) F ε FIRST ( ε ) = { ε } ε FIRST(ε) άρα εξετάζουμε το FOLLOW (F) = { ) } M ( F, + ) = F ε Επομένως, προκύπτει ο συντακτικός πίνακας: V\T ( ) * + a b $ S S (D) D D EF D EF D EF E E S E a E b F F ε F *D F +D Ο συντακτικός πίνακας μπορεί να χρησιμοποιηθεί για την κατασκευή ενός αποδοτικού
συντακτικού αναλυτή από πάνω προς τα κάτω. Αυτός ο συντακτικός αναλυτής διατηρεί μια στοίβα συμβόλων, η οποία αρχικά δίνει: α) το σύμβολο $ κάτω β) το αρχικό σύμβολο S στην κορυφή. Ο συντακτικός αναλυτής διαβάζει ένα ένα τα σύμβολα από την είσοδο. Οι ενέργειες του αναλυτή καθορίζονται από: α) το τρέχον σύμβολο εισόδου α β) το σύμβολο x στην κορυφή της στοίβας Υπάρχουν 4 περιπτώσεις για τον συντακτικό αναλυτή: Αν x τερματικό: 1) Αν x = α και α!= $, τότε το σύμβολο εισόδου έχει αναγνωριστεί και το x βγαίνει από τη στοίβα. 2) Αν x = α = $, τότε ο αναλυτής έχει δεχθεί ολόκληρη τη συμβολοσειρά εισόδου και έχει τελειώσει την εργασία αναγνώρισης. Αν x μη τερματικό, ο αναλυτής ανατρέχει στο Μ(x,α) του συντακτικού πίνακα: 3) Αν στη θέση αυτή υπάρχει κανόνας παραγωγής, τότε το x στη στοίβα αντικαθίσταται με τα σύμβολα στο δεξί μέλος της παραγωγής. Τα σύμβολα μπαίνουν στη στοίβα με αντίστροφη σειρά (δηλαδή το αριστερότερο σύμβολο της παραγωγής θα είναι στην κορυφή της στοίβας).! Αν υπάρχει παραγωγή... ε, το ε δε μπαίνει στη στοίβα. 4) Αν η θέση αυτή είναι κενή, ο αναλυτής βγάζει μήνυμα λάθους (η συμβολοσειρά δεν αναγνωρίζεται). παράδειγμα σελ. 203 204 Γραμματικές LL(1) Μία γραμματική είναι LL(1) όταν μπορεί να φτιαχτεί συντακτικός πίνακας Μ, τέτοιος ώστε σε κάθε θέση Μ(x,α) να αντιστοιχεί το πολύ μία παραγωγή. Αυτόματα, η γραμματική ξέρουμε πως δεν είναι ούτε αριστερά αναδρομική ούτε διφορούμενη. Αναλυτές LL(1) 1) Η κατασκευή τους (σύμφωνα με τον αλγόριθμο που δόθηκε) είναι εύκολη. 2) Η στοίβα του συντακτικού αναλυτή LL(1) ουσιαστικά είναι αντίστοιχη της στοίβας που παράγεται από τις αναδρομικές κλήσεις ρουτινών στον προβλέποντα συντακτικό αναλυτή. 3) Ο LL(1) δεν είναι αναδρομικός (αφού δε χρησιμοποιεί αναδρομικές ρουτίνες) 4) Δεν είναι εύκολο ή εφικτό για κάποιες γραμματικές να μετασχηματισθούν σε LL(1).
Προετοιμασία για εξετάσεις Λυμένες ασκήσεις Ασκήσεις Μια πλατφόρμα που περιέχει ασκήσεις εκτός των άλλων και για μεταγλωττιστές: https://proof frontend.herokuapp.com/ Θέματα παλαιοτέρων ετών