Professional Documents
Culture Documents
PLH24_4ERG_ΕΝΔΕΙΚΤΙΚΗ ΑΠΑΝΤΗΣΗ.v.1.0
PLH24_4ERG_ΕΝΔΕΙΚΤΙΚΗ ΑΠΑΝΤΗΣΗ.v.1.0
PLH24_4ERG_ΕΝΔΕΙΚΤΙΚΗ ΑΠΑΝΤΗΣΗ.v.1.0
Αναλυτική Αξιολόγηση
Σας δίνεται μια απλή γλώσσα προγραμματισμού που θα την ονομάσουμε SPL (Simple Programming
Language) με λεκτικές και συντακτικές προδιαγραφές όπως παρουσιάζονται παρακάτω.
Λεκτικές Προδιαγραφές
Για τη γλώσσα που μας ενδιαφέρει οι λεκτικές προδιαγραφές συνοψίζονται στον παρακάτω πίνακα:
ΛΕΚΤΙΚΕΣ ΠΡΟΔΙΑΓΡΑΦΕΣ της γλώσσας SPL
Μπορεί να είναι ο κενός χαρακτήρας (blank), o χαρακτήρας νέα γραμμή (newline) και o
Whitespaces
χαρακτήρας tab
Ένα σχόλιο ανοίγει με το σύμβολο { και κλείνει με το σύμβολο } και περιορίζεται σε μια
Comments
μόνο γραμμή. ΔΕΝ υποστηρίζονται εμφωλιασμένα σχόλια (σχόλιο μέσα σε σχόλιο).
PROGRAM, INTEGER, BOOLEAN, STRING, ARRAY, OF, READ, WRITE,
Keywords
IF, THEN, ELSE, WHILE, DO, EXIT, VAR, BODY, BEGIN, END, AND,
OR, NOT, TRUE, FALSE
Τα αναγνωριστικά είναι ονόματα που αρχίζουν με γράμμα ή το χαρακτήρα underscore (_)
Identifiers
και στη συνέχεια περιέχουν γράμματα, αριθμούς ή το χαρακτήρα underscore.
Οι σταθερές μπορεί να είναι ακέραιοι αριθμοί (με πιθανό αρνητικό πρόσημο), οι λογικές
τιμές TRUE και FALSE και αλφαριθμητικές σταθερές που περικλείονται σε ζεύγος διπλών
εισαγωγικών ″…″. Διπλά εισαγωγικά ″ μπορούν να περιέχονται εντός μια
Constants
αλφαριθμητικής σταθεράς αρκεί να προηγείται ένα backslash (\″). Το ίδιο ισχύει και για
τη συμμετοχή ενός backslash (\\). Μια αλφαριθμητική σταθερά δεν μπορεί να περιέχει το
χαρακτήρα newline.
Unary-minus1 ‘–’ (π.χ. – 7)
Multiplicative ‘*’, ‘/’, ‘%’ (% = modulo)
Additive ‘+’, ‘–’
Relational ‘=’, ‘<>’, ‘<’, ‘>’, ‘<=’, ‘>=’
Operators
Logical AND, OR, NOT (NOT is a unary operator)
String ‘|’
Concatenation
Assignment ‘:=’
‘(’, ‘)’ Ομαδοποιούν expressions
Χρησιμοποιούνται στον ορισμό ενός πίνακα ή στην αναφορά σ’ ένα
‘[’, ‘]’
στοιχείο του πίνακα
Διαχωρίζει είτε δηλώσεις μεταβλητών στο τμήμα δηλώσεων του
Punctuators ή ‘;’
προγράμματος, είτε εντολές στο σώμα του προγράμματος
Separators
‘:’ Εισαγάγει τον τύπο δεδομένων ενός identifier
Διαχωρίζει τα ονόματα μιας λίστας από identifiers κατά τη δήλωση
‘,’ τύπου δεδομένων ή διαχωρίζει τα μέλη μιας λίστας εκφράσεων στις
εντολές READ και WRITE.
Συντακτικές Προδιαγραφές
1
Ο τελεστής unary-minus θα εφαρμόζεται μόνο σε αριθμητικές σταθερές.
Οι συντακτικές προδιαγραφές προσδιορίζουν τους νόμιμους τρόπους κατά τους οποίους τα tokens που
προκύπτουν από τις λεκτικές προδιαγραφές μπορούν να συνδυαστούν ώστε να ορίσουν τα συστατικά
στοιχεία ενός προγράμματος της SPL (δηλώσεις, εντολές, εκφράσεις, κ.ά.). Ένα πρόγραμμα SPL
αποτελείται από τρία βασικά τμήματα:
Την επικεφαλίδα του προγράμματος (υποχρεωτικό)
Το τμήμα δηλώσεων του προγράμματος (προαιρετικό)
Το σώμα του προγράμματος (υποχρεωτικό)
Η επικεφαλίδα του προγράμματος αποτελείται από τη δεσμευμένη λέξη PROGRAM κι ένα όνομα που
ορίζουμε για το πρόγραμμα.
Το τμήμα δηλώσεων του προγράμματος, όταν υπάρχει, εισάγεται με τη δεσμευμένη λέξη VAR και
περιλαμβάνει μια ή περισσότερες εντολές δήλωσης μεταβλητών. Μια εντολή δήλωσης μπορεί να
περιλαμβάνει τα ονόματα πολλών μεταβλητών. Οι εντολές δήλωσης (εκτός της τελευταίας)
διαχωρίζονται με τον διαχωριστή ‘;’. Για κάθε μεταβλητή που δηλώνουμε ορίζουμε το όνομα και τον
τύπο δεδομένων της. Οι βασικοί τύποι δεδομένων είναι INTEGER, BOOLEAN και STRING, ενώ
υποστηρίζεται και η δομή δεδομένων πίνακα. Οι πίνακες είναι μονοδιάστατοι και o τύπος εισάγεται με
τη δεσμευμένη λέξη ARRAY. Στη δήλωση ενός πίνακα ο τύπος του δείκτη πρέπει να είναι αριθμητικός
ενώ ο τύπος των περιεχομένων (ακολουθεί τη δεσμευμένη λέξη OF) είναι κάποιος από τους βασικούς
τύπους δεδομένων. Ένα παράδειγμα τμήματος δήλωσης ακολουθεί:
VAR
i, j: INTEGER;
s1, s2: STRING;
buffer: ARRAY[10] OF INTEGER;
flag: BOOLEAN
Το σώμα του προγράμματος εισάγεται με τη δεσμευμένη λέξη BODY και περιλαμβάνει μια ή
περισσότερες εντολές. Εάν το σώμα περιλαμβάνει περισσότερες της μιας εντολές, τότε αυτές
περικλείονται εντός block που οριοθετείται από τις δεσμευμένες λέξεις BEGIN … END. Τα block
εντολών μπορεί να είναι εμφωλιασμένα. Οι εντολές σ’ ένα block εντολών (εκτός της τελευταίας)
διαχωρίζονται με τον διαχωριστή ‘;’. Ένα παράδειγμα σώματος του προγράμματος ακολουθεί:
BODY
BEGIN
Statement-1;
Statement-2;
BEGIN
Statement-3
END;
Statement-4
END
*/%
+ –|
AND
OR
Ο λεκτικός αναλυτής (ΛΑ) είναι ένα τμήμα ενός μεταγλωττιστή, το οποίο αποσυνθέτει ένα πρόγραμμα
στις λεκτικές μονάδες από τις οποίες αποτελείται. Στην περίπτωση που αναγνωρίσει κάποιο σφάλμα
(για παράδειγμα έναν χαρακτήρα που δεν ανήκει στο αλφάβητο της γλώσσας) βγάζει ένα κατάλληλο
μήνυμα λάθους και τερματίζει τη λειτουργία του μεταγλωττιστή.
Για να υλοποιήσουμε τον ΛΑ θα χρησιμοποιήσουμε κανονικές εκφράσεις, ώστε να περιγράψουμε τις
λεκτικές μονάδες που επιθυμούμε να αναγνωρίζονται. Οι λεκτικές μονάδες έχουν αυστηρή σύνταξη,
χρησιμοποιούνται για να περιγράψουν συμβολοσειρές ή σύνολα από συμβολοσειρές και είναι πολύ
χρήσιμες στην λεκτική ανάλυση. Πιο συγκεκριμένα, η υλοποίηση του ΛΑ βασίζεται στο πακέτο
java.util.regex της Java.
Η υλοποίηση του ΛΑ σας δίνεται κατά το μεγαλύτερο μέρος έτοιμη στις κλάσεις Lex, Token και
TokenType. Χρησιμοποιούμε την κλάση Token η οποία υλοποιεί μία δομή στην οποία αποθηκεύουμε
τις λεκτικές μονάδες που αναγνωρίζουμε. Η κλάση αυτή έχει τρία πεδία. Το πρώτο πεδίο είναι το type
το οποίο είναι τύπου TokenType και αποθηκεύει τον τύπο της λεκτικής μονάδας. Το δεύτερο πεδίο το
ονομάζουμε data, είναι τύπου String και αποθηκεύει τη λεκτική μονάδα (π.χ. το όνομα μιας
μεταβλητής) και το τρίτο πεδίο, το line, είναι ακέραιος και αποθηκεύει τη γραμμή στην οποία
εμφανίστηκε η λεκτική μονάδα.
Για τη σύνταξη των κανονικών εκφράσεων, αλλά και τη βιβλιοθήκη γενικότερα java.util.regex,
μπορείτε να συμβουλευτείτε τον ακόλουθο σύνδεσμο:
https://www.tutorialspoint.com/java/java_regular_expressions.htm
Επίσης, μπορείτε να συμβουλευτείτε την Ενότητα 4.2 του Εγχειριδίου «Μεταγλωττιστές: Επιλεγμένες
Λύσεις Θεμάτων και Αναφορά στη Σχετική Θεωρία» που είναι διαθέσιμο στην ενότητα
«Συμπληρωματικό Υλικό» στο Study.
Στην κλάση TokenType της εργασίας χρειάζεται να συμπληρώσετε τις Κανονικές Εκφράσεις για τις
ακόλουθες περιπτώσεις:
- commentTK()
- numericTK()
- stringConstTK()
- identifierTK()
Οι λεκτικές μονάδες αξιοποιούνται από τον Συντακτικό Αναλυτή όπως περιγράφεται στην επόμενη
ενότητα. Η λεκτική μονάδα που αναπαριστά ένα σχόλιο που θα χρησιμοποιηθεί;
Μαθησιακά Αποτελέσματα
Απάντηση
Να συμπληρώσετε τις κανονικές εκφράσεις που λείπουν.
Εάν δεν έχετε δώσει απάντηση, γράψτε με κεφαλαία γράμματα: ΔΕΝ ΑΠΑΝΤΗΘΗΚΕ.
Εάν εν γνώση σας δίνετε ελλιπή απάντηση, γράψτε με κεφαλαία γράμματα: ΕΛΛΙΠΗΣ ΑΠΑΝΤΗΣΗ. Εξηγείστε σε
ποιο σημείο θεωρείτε την απάντησή σας ελλιπή και γιατί.
commentTK("\\{[^{}\\n]*\\}")
Ανάλυση ΚΕ:
\\{
Αντιστοιχεί στο άνοιγμα του σχολίου με το σύμβολο {. Οι δύο χαρακτήρες διαφυγής (\\)2
χρησιμοποιούνται για την αποφυγή της ειδικής ερμηνείας του χαρακτήρα { στις κανονικές εκφράσεις.
[^{}\\n]*
Προσδιορίζει ένα σετ χαρακτήρων που αποκλείει τους χαρακτήρες { } και \n (νέα γραμμή). Το
[^...] χρησιμοποιείται για να δηλώσει μια ομάδα χαρακτήρων που ΔΕΝ πρέπει να εμφανίζονται. Το
* σημαίνει ότι μπορεί να υπάρχουν μηδέν ή περισσότεροι χαρακτήρες.
\\}
Αυτό το μέρος αντιστοιχεί στο κλείσιμο του σχολίου με το σύμβολο }. Όπως και με το άνοιγμα, οι δύο
χαρακτήρες διαφυγής χρησιμοποιούνται για την αποφυγή της ειδικής ερμηνείας του χαρακτήρα }.
numericTK("(0|[1-9]\\d*)")
Ανάλυση ΚΕ:
2
Στην Java, όταν χρησιμοποιούμε κανονικές εκφράσεις μέσα σε συμβολοσειρές, πρέπει να "διαφύγουμε"
(escape) δύο φορές τους ειδικούς χαρακτήρες: μία για την Java, λόγω συμβολοσειράς, και μία για την κανονική
έκφραση.
0
Αντιστοιχεί ακριβώς στον αριθμό 0.
[1-9]\\d*
Αντιστοιχεί σε οποιονδήποτε ακέραιο που ξεκινά με έναν από τους αριθμούς 1-9 και ακολουθείται από
οποιονδήποτε αριθμό (ή καθόλου) ψηφίων (0-9).
Οι αρνητικοί ακέραιοι αναγνωρίζονται σε επίπεδο ΣΑ χρησιμοποιώντας τον τελεστή unary minus της
γλώσσας.
stringConstTK("\"(?:\\\\[\"\\\\]|[^\"\\\n])*\"")
Ανάλυση ΚΕ:
\"
Η κανονική έκφραση αρχίζει και τελειώνει με διπλά εισαγωγικά.
(?: ... )
Αυτή είναι μια non-capturing ομάδα. Χρησιμοποιείται για να ομαδοποιήσει τμήματα της κανονικής
έκφρασης χωρίς να αποθηκεύει τα αποτελέσματα για μετέπειτα χρήση.
\\\\[\"\\\\]
Αυτό το τμήμα καλύπτει τις περιπτώσεις εμφάνισης διπλών εισαγωγικών ή backslashes των οποίων
προηγείται ένα backslash. Τα διπλά backslashes (\\\\) αναπαριστούν ένα κυριολεκτικό backslash στη
Java, επειδή το πρώτο backslash διαφεύγει το δεύτερο. Το [\"\\\\] δηλώνει ότι μπορεί να
ακολουθεί είτε ένα διπλό εισαγωγικό είτε ένα backslash.
[^\"\\\n]
Αυτό το τμήμα δέχεται οποιοδήποτε χαρακτήρα εκτός από διπλό εισαγωγικό (\"), backslash (\\) και
τον χαρακτήρα νέας γραμμής (\n).
Το διπλό εισαγωγικό (\") εξαιρείται γιατί ένα απλό διπλό εισαγωγικό θα ολοκλήρωνε την
αλφαριθμητική σταθερά. Μόνο όταν προηγείται ένα backslash (\\") μπορεί να αποτελέσει μέρος της
σταθεράς ως escaped διπλό εισαγωγικό.
Τo backslash (\\) εξαιρείται επίσης, εκτός εάν ακολουθείται από ένα άλλο backslash ή διπλό
εισαγωγικό. Ένα μοναχικό backslash δεν είναι επιτρεπτό γιατί θα σήμαινε μια ελλιπή διαφυγή.
Ο χαρακτήρας νέας γραμμής (\n) εξαιρείται αυτόματα από την αλφαριθμητική σταθερά για να
διασφαλιστεί ότι το αλφαριθμητικό περιορίζεται σε μία μόνο γραμμή, όπως απαιτούν οι
προδιαγραφές.
Ο αστερίσκος που ακολουθεί τη non-capturing ομάδα επιτρέπει την επανάληψη του περιεχομένου της
ομάδας 0 ή περισσότερες φορές. Αυτό σημαίνει ότι η αλφαριθμητική σταθερά μπορεί να είναι κενή ή
να περιέχει πολλαπλά εισαγωγικά ή backslashes, καθώς και άλλους χαρακτήρες εκτός από το διπλό
εισαγωγικό και τον χαρακτήρα νέας γραμμής.
identifierTK("[a-zA-Z_][a-zA-Z_0-9]*")
Ανάλυση ΚΕ:
[a-zA-Z_]
Αυτό το τμήμα δηλώνει ότι ο πρώτος χαρακτήρας ενός αναγνωριστικού πρέπει να είναι είτε γράμμα
(από το 'a' μέχρι το 'z' ή από το 'A' μέχρι το 'Z') είτε ο χαρακτήρας underscore (_).
[a-zA-Z_0-9]*
Αυτό το τμήμα χρησιμοποιεί τον τελεστή *, που δηλώνει επανάληψη του προηγούμενου στοιχείου
μηδέν ή περισσότερες φορές. Αυτό σημαίνει ότι στα επόμενα στοιχεία του αναγνωριστικού μπορούν να
συμπεριληφθούν γράμματα (ανεξαρτήτως πεζά ή κεφαλαία), αριθμοί (0-9), ή underscore.
Σημείωση 1:
Στην ανάπτυξη ΛΑ, είναι σημαντική η σειρά των κανονικών εκφράσεων, κυρίως λόγω της αρχής της
«μεγαλύτερης δυνατής λεκτικής μονάδας». Αυτή η αρχή διασφαλίζει ότι ο ΛΑ επιλέγει το μεγαλύτερο
δυνατό τμήμα του κώδικα πηγής που ταιριάζει με μια δεδομένη κανονική έκφραση, πριν προχωρήσει
σε επόμενες αναζητήσεις.
Για παράδειγμα, ο ΛΑ για τη γλώσσα SPL, η οποία περιλαμβάνει τόσο σύνθετους τελεστές (όπως <=,
>=) όσο και απλούς τελεστές (όπως <, >), θα πρέπει πρώτα να χειριστεί τις κανονικές εκφράσεις για
τους σύνθετους τελεστές και μετά αυτές των απλών τελεστών. Αυτό σημαίνει ότι στην TokenType θα
πρέπει να τοποθετήσουμε τα regular expressions των σύνθετων τελεστών (<=, >=) πάνω από τους
απλούς τελεστές (<, >). Αυτό εξασφαλίζει ότι συμβολοσειρές όπως <= και >= δεν θα ερμηνευτούν
λανθασμένα ως δύο διακριτά σύμβολα.
Σημείωση 2:
Η λεκτική μονάδα που αναπαριστά ένα σχόλιο δεν θα χρησιμοποιηθεί από τον ΣΑ και ως εκ τούτου στη
μέθοδο Lex() που παράγει τη λίστα με τις λεκτικές μονάδες και τις αποθηκεύει στη δομή τύπου
ArrayList με όνομα tokens, θα πρέπει να αγνοηθεί, όπως συμβαίνει και με τους λευκούς
χαρακτήρες.
…
case "whitespaceTK":
case "commentTK":
;
break;
Ο συντακτικός αναλυτής (ΣΑ) υλοποιείται με βάση τη γραμματική της γλώσσας. H γραμματική δίνεται
στην κλάση που υλοποιεί τον ΣΑ (Parser_ex4) στον κώδικα που σας έχει δοθεί. Η γραμματική είναι
μορφής LL(1). Αυτό σημαίνει ότι αναγνωρίζει από αριστερά προς στα δεξιά, την αριστερότερη δυνατή
παραγωγή και όταν βρίσκεται σε δίλλημα ως προς το ποιον κανόνα να ακολουθήσει, της αρκεί να
κοιτάξει το αμέσως επόμενο σύμβολο στην συμβολοσειρά εισόδου (lookahead token). Αυτό το
τελευταίο στοιχείο μας ενδιαφέρει ιδιαίτερα στην κατασκευή του ΣΑ.
Ο ΣΑ φτιάχνεται από την γραμματική με συστηματικό τρόπο, σχεδόν αυτόματο. Για κάθε έναν από τους
κανόνες της γραμματικής, ορίζουμε και ένα αντίστοιχο υποπρόγραμμα (τυπικά μια μέθοδο στην
υλοποίηση της ΓΕ4). Όταν συναντάμε στον κανόνα ένα μη-τερματικό σύμβολο, καλούμε το αντίστοιχο
υποπρόγραμμα. Όταν συναντάμε ένα τερματικό σύμβολο, τότε εάν και ο ΛΑ επιστρέφει λεκτική
μονάδα που αντιστοιχεί στο τερματικό αυτό σύμβολο, τότε έχουμε αναγνωρίσει επιτυχώς τη λεκτική
μονάδα και ο ΣΑ προχωρά στο επόμενο σύμβολο του κανόνα, αν υπάρχει. Αντίθετα εάν ο ΛΑ δεν
επιστρέψει τη λεκτική μονάδα που περιμένει ο ΣΑ, τότε έχουμε λάθος και καλείται ο διαχειριστής
σφαλμάτων (χρήση της μεθόδου error() στον κώδικα που δόθηκε). Όταν αναγνωριστεί και η
τελευταία λέξη του πηγαίου προγράμματος, τότε η συντακτική ανάλυση έχει στεφτεί με επιτυχία.
Παρακάτω δίνεται, ως παράδειγμα, ο κώδικας της μεθόδου που αναφέρεται στην υλοποίηση ενός
κανόνα. Στον κώδικα αυτό χρησιμοποιείται και η μέθοδος next(), η οποία διαχειρίζεται τη δομή που
αποθηκεύει τις λεκτικές μονάδες και κάθε φορά που καλείται, αφαιρείται μία λεκτική μονάδα από τη
δομή και επιστρέφεται σαν αποτέλεσμα. Ο κανόνας της γραμματικής δίνεται ως σχόλιο στην αρχή της
αντίστοιχης μεθόδου.
//-------------------------------------------------------------------
// decl = idList COLON type
//-------------------------------------------------------------------
public static void decl() {
idList();
if (token.type.name().equals("colonTK")) {
token = next();
type();
} else {
error("COLON seperator expected in variable type declaration");
}
Ας παρακολουθήσουμε τον τρόπο υλοποίησης. Ξεκινώντας από τον κανόνα decl, ορίζουμε σαν void
την μέθοδο decl(). Στη αρχή του δεξιού μέρους του κανόνα συναντάμε το μη-τερματικό σύμβολο
idlist. Άρα στον κώδικα καλούμε την μέθοδο idList(). Μετά αναμένουμε το τερματικό σύμβολο
COLON. Ελέγχουμε αν το COLON αποτελεί το επόμενο σύμβολο εισόδου. Αν όχι, βγάζουμε μήνυμα
λάθους. Αν ναι, καλούμε την next() για να γίνει διαθέσιμη στον ΣΑ η επόμενη λεκτική μονάδα. Στη
συνέχεια στον κανόνα συναντάμε το μη-τερματικό σύμβολο type. Άρα καλούμε την αντίστοιχη μέθοδο
type().
Αντίστοιχα, δίνεται ως ένα επιπλέον παράδειγμα, ο κώδικας για τον κανόνα exprList:
//-------------------------------------------------------------------
// exprList = expr (COMMA expr)*
//-------------------------------------------------------------------
public static void exprList() {
expr();
while (token.type.name().equals("commaTK")) {
token = next();
expr();
}
}
Εδώ θα πρέπει να σημειωθεί ότι στους κανόνες της γραμματικής χρησιμοποιούνται και οι μετα-
χαρακτήρες (…)* και (…)? που χρησιμεύουν στην εκφραστική διατύπωση των κανόνων. Η πρώτη
ακολουθία επαναλαμβάνει τα σύμβολα που περικλείονται εντός των παρενθέσεων 0 ή περισσότερες
φορές, ενώ η δεύτερη ακολουθία επαναλαμβάνει τα σύμβολα 1 ή καμία φορά. Αυτοί οι μετα-
χαρακτήρες παρέχουν ευελιξία στην οριοθέτηση των κανόνων αλλά δεν αποτελούν μέρος της
γραμματικής της γλώσσας προγραμματισμού που αναλύεται.
Στην exprList, ενδιαφέρον έχει ότι δεν γνωρίζουμε πόσες φορές θα χρειαστεί να περάσουμε από την
δεύτερη expr. Μπορεί να περάσουμε καμία, μία ή περισσότερες φορές. Αφού καλέσουμε για πρώτη
φορά την expr, ο οδηγός μας είναι το τερματικό σύμβολο COMMA. Το σημείο αυτό αποτελεί ένα καλό
παράδειγμα για το τι εννοούμε ότι όταν έχουμε δίλημμα για το τι δρόμο θα ακολουθήσουμε μέσα στη
γραμματική, αυτό θα μας το δείξει το επόμενο σύμβολο στην είσοδο (το lookahead token). Αν λοιπόν,
και για όσο, το επόμενο σύμβολο στην είσοδο είναι το COMMA, τότε θα το αναγνωρίζουμε και θα
απαιτούμε μετά από αυτό να υπάρχει ένα expr. Αυτό υλοποιείται με την while, όπως φαίνεται στον
κώδικα παραπάνω.
Μέρος του κώδικα του ΣΑ σας δίνεται έτοιμο στην κλάση Parser_ex4, την οποία θα πρέπει να
μελετήσετε για να κατανοήσετε και άλλες τεχνικές υλοποίησης των κανόνων μιας γραμματικής, όπως
για παράδειγμα την περίπτωση κανόνα με εναλλακτικό δεξιό μέρος την κενή συμβολοσειρά (ε) ή
κανόνα με πολλά εναλλακτικά δεξιά μέρη. Για την υλοποίηση του ΣΑ θα πρέπει να συμπληρώσετε τον
κώδικα των μεθόδων για τους κανόνες της γραμματικής που είναι κενός. Οι κανόνες της γραμματικής
σας δίνονται ως σχόλιο πριν από κάθε μέθοδο. Ωστόσο συγκεκριμένους κανόνες της γραμματικής θα
πρέπει να τους ορίσετε εσείς κατάλληλα. Πιο συγκεκριμένα, θα πρέπει να ορίσετε τη δόμηση της
γραμματικής για τη συντακτική δομή expr (περιλαμβάνει τους κανόνες expr, logicAND,
relationExpr, additiveExpr, factor, term) έτσι ώστε να τηρούνται στον υπολογισμό των
εκφράσεων οι προτεραιότητες των τελεστών σύμφωνα με τις δοθείσες προδιαγραφές (δείτε τον
σχετικό πίνακα με τις προτεραιότητες των τελεστών στην εισαγωγή). Χωρίς αυτή τη δόμηση η
γραμματική θα είναι διφορούμενη ως προς τον υπολογισμό των εκφράσεων.
Για την υλοποίηση του ΣΑ με την παραπάνω μεθοδολογία μπορείτε επίσης να συμβουλευτείτε την
Ενότητα 4.3 του Εγχειριδίου «Μεταγλωττιστές: Επιλεγμένες Λύσεις Θεμάτων και Αναφορά στη Σχετική
Θεωρία» που είναι διαθέσιμο στην ενότητα «Συμπληρωματικό Υλικό» στο Study.
Για τον έλεγχο του ΣΑ που θα υλοποιήσετε σας δίνονται εντός του NetBeans project ενδεικτικά
προγράμματα που είναι σύμφωνα με τη γραμματική της γλώσσας SPL.
Μαθησιακά Αποτελέσματα
Στην άσκηση 1.Β. θα σας δοθεί η δυνατότητα να κατανοήσετε:
την περιγραφή μίας γλώσσας μέσα από μία γραμματική
την έννοια της συντακτικής ανάλυσης
την κύρια ιδιότητα μιας γραμματικής LL(1)
την υλοποίηση ενός συντακτικού αναλυτή με τη μέθοδο της αναδρομικής κατάβασης
τη λειτουργία ενός συντακτικού αναλυτή
τα σφάλματα μεταγλώττισης που μπορούν να προκύψουν κατά τη φάση της συντακτικής ανάλυσης
και τη διαχείρισή τους
Απάντηση
1) Να δώσετε τον κώδικα που υλοποιεί τον συντακτικό αναλυτή, επαρκώς σχολιασμένο. Αυτό
σημαίνει ότι πρέπει να συμπληρώσετε τον κώδικα που λείπει, τις μεθόδους που σημειώνονται με
τρεις τελείες. Να εμφανίζονται τα κατάλληλα μηνύματα λάθους στα οποία να φαίνεται και ο
αριθμός γραμμής του προγράμματος στην οποία έγινε το λάθος.
2) Εάν δεν έχετε δώσει απάντηση στο ερώτημα αυτό, γράψτε με κεφαλαία γράμματα: ΔΕΝ
ΑΠΑΝΤΗΘΗΚΕ.
3) Εάν εν γνώση σας δίνετε ελλιπή απάντηση, γράψτε με κεφαλαία γράμματα: ΕΛΛΙΠΗΣ
ΑΠΑΝΤΗΣΗ. Εξηγήστε σε ποιο σημείο θεωρείτε την απάντησή σας ελλιπή και γιατί.
Η υλοποίηση του ΣΑ ακολουθεί τη μεθοδολογία που περιγράφει η εκφώνηση η οποία βασίζεται στην
αντιστοίχιση κάθε κανόνα της γραμματικής με μια μέθοδο της κλάσης Parser_ex4. Αυτή η
προσέγγιση είναι σύμφωνη με το πλαίσιο υλοποίησης ΣΑ με προβλέπουσα αναδρομική κατάβαση
(recursive descent).
Αρχικά, όμως, θα πρέπει να συμπληρωθεί το μέρος τη γραμματικής για τη συντακτική δομή expr
(περιλαμβάνει τους κανόνες expr, logicAND, relationExpr, additiveExpr, factor,
term) έτσι ώστε να τηρούνται στον υπολογισμό των εκφράσεων οι προτεραιότητες των τελεστών
σύμφωνα με τις δοθείσες προδιαγραφές. H γραμματική πρέπει να λαμβάνει υπόψη τις σχετικές
προτεραιότητες των τελεστών, αλλά και την προσεταιριστικότητα του κάθε τελεστή, όπως δόθηκαν
στην εκφώνηση και συνοψίζονται στον ακόλουθο πίνακα.
Για να διαχειριστούμε την προτεραιότητα των τελεστών σε επίπεδο γραμματικής χρειαζόμαστε ένα μη
τερματικό σύμβολο για κάθε επίπεδο προτεραιότητας. Η διαμόρφωση της γραμματικής θα πρέπει να
είναι τέτοια που να αναγκάζει τον ΣΑ να κτίζει το δένδρο ανίχνευσης βάζοντας τους τελεστές
μικρότερης προτεραιότητας πιο ψηλά και αντίστοιχα τους τελεστές μεγαλύτερης προτεραιότητας πιο
χαμηλά στο δένδρο. Κάτι τέτοιο επιτυγχάνεται με τη δόμηση των κανόνων όπως φαίνεται στο
ακόλουθο απόσπασμα της γραμματικής. Παρατηρήστε ότι ο τελεστής με την χαμηλότερη
προτεραιότητα συνδέεται απευθείας με το μη-τερματικό σύμβολο αφετηρία των εκφράσεων (expr),
ενώ όσο ανεβαίνουμε επίπεδο προτεραιότητας με την εισαγωγή νέου μη-τερματικού συμβόλου και την
εισαγωγή στη γραμματική των σχετικών τελεστών, απομακρυνόμαστε από το σύμβολο αφετηρία.
Ωστόσο η παραπάνω γραμματική δεν είναι κατάλληλη για top-down ανίχνευση, όπως η υλοποίηση που
ζητείται, και γι’ αυτό απαιτείται απαλοιφή αριστερής αναδρομής στους κανόνες: expr, logicAND,
relationExpr, additiveExpr, factor. Η γραμματική μετά την εφαρμογή του μετασχηματισμού
απαλοιφής της αριστερής αναδρομής διαμορφώνεται ως ακολούθως:
| ε
logicAND = relationExpr logicAND’
logicAND’ = AND relationExpr logicAND’
| ε
relationExpr = additiveExpr relationExpr’
relationExpr’ = relationOperator additiveExpr relationExpr’
| ε
additiveExpr = factor additiveExpr’
additiveExpr’ = addingOperator factor dditiveExpr’
| ε
factor = term factor’
factor’ = multiplyOperator term factor’
| ε
term = lvalue
| constant
| LPAREN expr RPAREN
| NOT term
H μεθοδολογία υλοποίησης που έχει δοθεί διευκολύνεται όταν οι κανόνες της γραμματικής
εκφράζονται σε EBNF μορφή. Έτσι, μια επανάληψη σε μορφή BNF:
A=aNC
N = B1 B2 … Bn N | ε
Μπορεί να μετατραπεί στην ακόλουθη μορφή EBNF (με κόκκινο χρώμα δείχνουμε τους μετα-
χαρακτήρες της γραμματικής):
Α = a ( B1 B2 … Bn )* C
Με την ίδια λογική το παραπάνω απόσπασμα της γραμματικής για την αναγνώριση των εκφράσεων με
ενσωματωμένη την προτεραιότητα και προσεταιριστικότητα των τελεστών γίνεται:
| NOT term
Η συνολική γραμματική που περιγράφει το συντακτικό της γλώσσας SPL σε EBNF μορφή και θα πρέπει
να υλοποιηθεί με προβλέπουσα αναδρομική κατάβαση ακολουθεί:
| MINUS
| CONCAT
multiplyOperator = TIMES
| DIVISION
| MODULO
Οι μέθοδοι και οι αντίστοιχοι κανόνες των οποίων η υλοποίηση ζητείται στην κλάση Parser_ex4
είναι οι παρακάτω:
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// declarations = VAR decl (SEMICOLON decl)* | ε
//-------------------------------------------------------------------
public static void declarations() {
if (token.type.name().equals("varTK")) {
token = next();
decl();
while (token.type.name().equals("semicolonTK")) {
token = next();
decl();
}
}
}
//-------------------------------------------------------------------
// idList = ID (COMMA ID)*
//-------------------------------------------------------------------
public static void idList() {
if (token.type.name().equals("identifierTK")) {
token = next();
while (token.type.name().equals("commaTK")) {
token = next();
if (token.type.name().equals("identifierTK")) {
token = next();
} else {
error("name of variable expected after COMMA separator");
}
}
} else {
error("name of variable expected");
}
}
//-------------------------------------------------------------------
// type = basicType | arrayType
//-------------------------------------------------------------------
public static void type() {
if (token.type.name().equals("arrayTK")) {
arrayType();
} else {
basicType();
}
}
//-------------------------------------------------------------------
// statement = BEGIN block END
// | lvalue ASSIGN expr
// | READ LPAREN idList RPAREN
// | WRITE LPAREN exprList RPAREN
// | IF expr THEN statement (ELSE statement)?
// | WHILE expr DO statement
// | EXIT
//-------------------------------------------------------------------
public static void statement() {
switch (token.type.name()) {
case "beginTK": {
token = next();
block();
if (token.type.name().equals("endTK")) {
token = next();
} else {
error("END keyword expected to mark a block of state-
ments");
}
break;
}
case "readTK": {
token = next();
if (token.type.name().equals("lparenTK")) {
token = next();
idList();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in READ statement");
}
} else {
error("LPAREN expected in READ statement");
}
break;
}
case "writeTK": {
token = next();
if (token.type.name().equals("lparenTK")) {
token = next();
exprList();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in WRITE statement");
}
} else {
error("LPAREN expected in WRITE statement");
}
break;
}
case "ifTK": {
token = next();
expr();
if (token.type.name().equals("thenTK")) {
token = next();
statement();
if (token.type.name().equals("elseTK")) {
token = next();
statement();
}
} else {
error("THEN keyword expected in IF statement");
}
break;
}
case "whileTK": {
token = next();
expr();
if (token.type.name().equals("doTK")) {
token = next();
statement();
} else {
//-------------------------------------------------------------------
// block = statement (SEMICOLON statement)* | ε
//-------------------------------------------------------------------
public static void block() {
switch (token.type.name()) {
case "beginTK":
case "readTK":
case "writeTK":
case "ifTK":
case "whileTK":
case "exitTK":
case "identifierTK": {
statement();
while (token.type.name().equals("semicolonTK")) {
token = next();
statement();
}
break;
}
default: {
break; // ε
}
}
}
//-------------------------------------------------------------------
// index = ID | NUMERIC
//-------------------------------------------------------------------
public static void index() {
switch (token.type.name()) {
case "identifierTK": {
token = next();
break;
}
case "numericTK": {
token = next();
break;
}
default: {
error("Unknown index");
break;
}
}
}
//-------------------------------------------------------------------
// expr = logicAND (OR logicAND)*
//-------------------------------------------------------------------
public static void expr() {
logicAND();
while (token.type.name().equals("orTK")) {
token = next();
logicAND();
}
}
//-------------------------------------------------------------------
// logicAND = relationExpr (AND relationExpr)*
//-------------------------------------------------------------------
public static void logicAND() {
relationExpr();
while (token.type.name().equals("andTK")) {
token = next();
relationExpr();
}
}
//-------------------------------------------------------------------
// relationExpr = additiveExpr (relationOperator additiveExpr)*
//-------------------------------------------------------------------
public static void relationExpr() {
additiveExpr();
while (token.type.name().equals("equalTK")
|| token.type.name().equals("notEqualTK")
|| token.type.name().equals("ltTK")
|| token.type.name().equals("gtTK")
|| token.type.name().equals("lteTK")
|| token.type.name().equals("gteTK")) {
relationOperator();
additiveExpr();
}
}
//-------------------------------------------------------------------
// additiveExpr = factor (addingOperator factor)*
//-------------------------------------------------------------------
public static void additiveExpr() {
factor();
while (token.type.name().equals("plusTK")
|| token.type.name().equals("minusTK")
|| token.type.name().equals("concatTK")) {
addingOperator();
factor();
}
}
//-------------------------------------------------------------------
// factor = term (multiplyOperator term)*
//-------------------------------------------------------------------
public static void factor() {
term();
while (token.type.name().equals("timesTK")
|| token.type.name().equals("divisionTK")
|| token.type.name().equals("moduloTK")) {
multiplyOperator();
term();
}
}
//-------------------------------------------------------------------
// term = lvalue
// | constant
// | LPAREN expr RPAREN
// | NOT term
//-------------------------------------------------------------------
public static void term() {
switch (token.type.name()) {
case "numericTK":
case "stringConstTK":
case "trueTK":
case "falseTK":
case "minusTK": {
constant();
break;
}
case "lparenTK": {
token = next();
expr();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in expression");
}
break;
}
case "notTK": {
token = next();
term();
break;
}
default: {
lvalue();
break;
}
}
}
//-------------------------------------------------------------------
// constant = NUMERIC
// | STRING
// | TRUE
// | FALSE
// | MINUS NUMERIC
//-------------------------------------------------------------------
public static void constant() {
switch (token.type.name()) {
case "numericTK": {
token = next();
break;
}
case "stringConstTK": {
token = next();
break;
}
case "trueTK": {
token = next();
break;
}
case "falseTK": {
token = next();
break;
}
case "minusTK": {
token = next();
if (token.type.name().equals("numericTK")) {
token = next();
} else {
error("NUMERIC constant expected");
}
break;
}
default: {
error("Invalid constant");
break;
}
}
}
//-------------------------------------------------------------------
// relationOperator = EQUAL
// | NOT_EQUAL
// | LT
// | GT
// | LTE
// | GTE
//-------------------------------------------------------------------
public static void relationOperator() {
switch (token.type.name()) {
case "equalTK": {
token = next();
break;
}
case "notEqualTK": {
token = next();
break;
}
case "ltTK": {
token = next();
break;
}
case "gtTK": {
token = next();
break;
}
case "lteTK": {
token = next();
break;
}
case "gteTK": {
token = next();
break;
}
default: {
error("Invalid relational operator");
break;
}
}
}
//-------------------------------------------------------------------
// addingOperator = PLUS
// | MINUS
// | CONCAT
//-------------------------------------------------------------------
public static void addingOperator() {
switch (token.type.name()) {
case "plusTK": {
token = next();
break;
}
case "minusTK": {
token = next();
break;
}
case "concatTK": {
token = next();
break;
}
default: {
error("Invalid additive operator");
break;
}
}
}
//-------------------------------------------------------------------
// multiplyOperator = TIMES
// | DIVISION
// | MODULO
//-------------------------------------------------------------------
public static void multiplyOperator() {
switch (token.type.name()) {
case "timesTK": {
token = next();
break;
}
case "divisionTK": {
token = next();
break;
}
case "moduloTK": {
token = next();
break;
}
default: {
error("Invalid additive operator");
break;
}
}
}
Προσθέστε επιπλέον κώδικα στον ΣΑ, ώστε στο τέλος της συντακτικής ανάλυσης να εμφανίζονται στην
οθόνη οι εξής πληροφορίες για το πρόγραμμα που αναλύθηκε:
1. Ο αριθμός των μεταβλητών που έχουν δηλωθεί.
2. Ο αριθμός των μεταβλητών για κάθε τύπο δεδομένων (INTEGER, STRING, BOOLEAN).
3. Ποιες μεταβλητές (αν υπάρχουν) έχουν χρησιμοποιηθεί στο πρόγραμμα και σε ποια γραμμή
του, χωρίς όμως να έχουν δηλωθεί στο τμήμα δηλώσεων του προγράμματος.
4. Ο αριθμός των εντολών (statements) που έχουν ορισθεί.
5. Ο αριθμός των εντολών ανά κατηγορία, για τις παρακάτω κατηγορίες:
- ASSIGNMENT
- BEGIN-END
- EXIT
- IF-THEN
- IF-THEN-ELSE
- READ
- WHILE-DO
- WRITE
6. Ο αριθμός των εκφράσεων που έχουν ορισθεί.
7. Ο αριθμός των εκφράσεων ανά κατηγορία, για τις παρακάτω κατηγορίες:
- ADDITIVE
- CONSTANT
- LOGICAL
- MULTIPLICATIVE
- RELATIONAL
Υποδείξεις:
- Μπορείτε να ορίσετε έναν απλό πίνακα συμβόλων για να αποθηκεύσετε πληροφορία
σχετική με τις μεταβλητές του προγράμματος και τον τύπο τους.
- Μπορείτε να τροποποιήσετε μεθόδους που υλοποιούν κανόνες της γραμματικής ώστε
να επιστρέφουν πληροφορία στις καλούσες μεθόδους ή να δέχονται ορίσματα κατά
την κλήση τους, για τις ανάγκες της ανάλυσης του κώδικα.
- Μπορείτε να ορίσετε μια ξεχωριστή κλάση Statistics στην οποία θα χειριστείτε την
συλλογή και επεξεργασία της πληροφορίας που ζητείται.
- Για τα στατιστικά των εκφράσεων ζητείται το πλήθος ανά είδος έκφρασης και όχι ανά
τύπο δεδομένων μιας έκφρασης
Μαθησιακά Αποτελέσματα
Στην άσκηση 1.Γ. θα σας δοθεί η δυνατότητα να κατανοήσετε:
το πώς μπορούμε να αναλύσουμε πηγαίο κώδικα
πως μπορούμε να εντοπίσουμε σφάλματα πέρα από αυτά που εντοπίζονται μέσα από τη γραμματική
της γλώσσας.
στοιχεία από τη διαχείριση των μεταβλητών που γίνεται από έναν μεταγλωττιστή
πως θα ελέγξουμε αν ο πηγαίος κώδικας ικανοποιεί κάποιες δεδομένες απαιτήσεις
Απάντηση
Η λύση που δίνεται για να καλυφθούν οι απαιτήσεις του ερωτήματος βασίζεται στα ακόλουθα βασικά
στοιχεία:
Διαχείριση μεταβλητών και τύπων δεδομένων: Χρησιμοποιείται ένας πίνακας συμβόλων (sym-
bolTable ως δομή τύπου HashMap), όπου κάθε μεταβλητή καταχωρείται με τον αντίστοιχο τύπο
δεδομένων της. Αυτό επιτρέπει την εύκολη πρόσβαση και επεξεργασία των τύπων δεδομένων κατά
τη διάρκεια της ανάλυσης και την εκτύπωση των στατιστικών.
Συλλογή στατιστικών στοιχείων: Η κλάση ProgramStatistics αποθηκεύει τα στατιστικά
στοιχεία όπως το πλήθος των μεταβλητών ανά τύπο, το πλήθος των εντολών και των εκφράσεων
ανά κατηγορία, καθώς και άλλες πληροφορίες όπως οι μεταβλητές που χρησιμοποιήθηκαν χωρίς
να έχουν δηλωθεί.
Χειρισμός μη δηλωμένων μεταβλητών: Προστίθεται λειτουργικότητα (μέθοδος
checkVariableUsage(String variable, int line)) για τον εντοπισμό και καταγραφή μη
δηλωμένων μεταβλητών που χρησιμοποιούνται στο πρόγραμμα, με αναφορά στη γραμμή όπου
χρησιμοποιήθηκαν.
Ειδικότερα, η ενημέρωση του πίνακα συμβόλων ακολουθεί την οδηγία της εκφώνησης για
τροποποίηση μεθόδων του ΣΑ που υλοποιούν κανόνες της γραμματικής ώστε να επιστρέφουν
πληροφορία στις καλούσες μεθόδους ή να δέχονται ορίσματα κατά την κλήση τους, για τις ανάγκες της
ανάλυσης του κώδικα. Οι μέθοδοι που εμπλέκονται είναι οι εξής:
Η μέθοδος decl() χρησιμοποιεί τις μεθόδους idList() και type() για να λάβει τη λίστα των
μεταβλητών και τον τύπο τους αντίστοιχα. Στη συνέχεια, αναθέτει τον παραληφθέντα τύπο σε κάθε
μεταβλητή από τη λίστα και ενημερώνει το symbolTable κατάλληλα.
Η μέθοδος idList() διαβάζει και συλλέγει έναν κατάλογο αναγνωριστικών (ids). Αυτό γίνεται
επαναληπτικά μέχρι να μην βρεθούν άλλα ID που διαχωρίζονται με κόμμα. Η μέθοδος επιστρέφει μια
λίστα από συμβολοσειρές, που αντιστοιχούν στα αναγνωριστικά των μεταβλητών που έχουν διαβαστεί.
Η μέθοδος type() καθορίζει τον τύπο μιας μεταβλητής. Αναγνωρίζει αν η δήλωση αφορά απλό τύπο
(με τη βοήθεια της basicType()) ή πίνακα (με τη βοήθεια της arrayType()). Επιστρέφει μια
συμβολοσειρά που περιγράφει τον τύπο της μεταβλητής.
Η μέθοδος basicType() επιστρέφει τον τύπο δεδομένων αν αναφέρεται σε βασικό τύπο: INTEGER,
BOOLEAN, STRING.
Η μέθοδος arrayType() επιστρέφει μια συμβολοσειρά που περιγράφει τον τύπο ενός πίνακα,
μορφοποιημένη ως "ARRAY OF" ακολουθούμενο από τον βασικό τύπο των στοιχείων του πίνακα.
/**
* This class collects and displays statistics about SPL program structures
such as
* variables, statements, and expressions.
*/
/**
* Updates the set of undeclared variables that have been used, noting the
line number.
* @param variable the variable name used
* @param line the line number where the variable is used
*/
/**
* Increments the count of statements of a specific type.
* @param type the type of the statement (e.g., IF-THEN, WHILE-DO)
*/
/**
* Increments the count of expressions of a specific type.
* @param type the type of the expression (e.g., ADDITIVE, LOGICAL)
*/
/**
* Calculates the total number of statements defined in the program.
* @return the total number of statements
*/
/**
* Calculates the total number of expressions defined in the program.
* @return the total number of expressions
*/
/**
* Displays the collected statistics about the analyzed program, including
details of
* variables, statements, and expressions.
*
* @param symbolTable the symbol table containing variables and their
types
*/
----------------------------------------------------------------------------------------------------------------------------------------------
/*
* This class implements the recursive descent parser for the SPL grammar,
* systematically analyzing the syntax structure of SPL programs.
* It integrates with the ProgramStatistics class to track and quantify vari-
ous
* aspects of the code being analyzed. This includes counting the types and
instances
* of variables, categorizing statements and expressions, and identifying un-
declared
* variables used within the code. This parser not only parses the SPL code
but also
* provides detailed analysis and insights into the code's structure for edu-
cational purposes.
*/
//-------------------------------------------------------------------
// program = PROGRAM ID declarations BODY statement EOF
//-------------------------------------------------------------------
public static void program() {
if (token.type.name().equals("programTK")) {
token = next();
if (token.type.name().equals("identifierTK")) {
token = next();
declarations();
if (token.type.name().equals("bodyTK")) {
token = next();
statement();
if (token.type.name().equals("eofTK")) {
System.out.println("syntactically correct program");
} else {
error("EOF expected at the end of the program");
}
} else {
//-------------------------------------------------------------------
// declarations = VAR decl (SEMICOLON decl)* | ε
//-------------------------------------------------------------------
public static void declarations() {
if (token.type.name().equals("varTK")) {
token = next();
decl();
while (token.type.name().equals("semicolonTK")) {
token = next();
decl();
}
}
}
//-------------------------------------------------------------------
// decl = idList COLON type
//-------------------------------------------------------------------
public static void decl() {
List<String> ids = idList();
if (token.type.name().equals("colonTK")) {
token = next();
String type = type();
for (String id : ids) {
symbolTable.put(id, type);
}
} else {
error("COLON seperator expected in variable type declaration");
}
}
//-------------------------------------------------------------------
// idList = ID (COMMA ID)*
//-------------------------------------------------------------------
public static List<String> idList() {
List<String> ids = new ArrayList<>();
if (token.type.name().equals("identifierTK")) {
ids.add(token.data);
token = next();
while (token.type.name().equals("commaTK")) {
token = next();
if (token.type.name().equals("identifierTK")) {
ids.add(token.data);
token = next();
} else {
error("name of variable expected after COMMA separator");
}
}
} else {
error("name of variable expected");
}
return ids;
}
//-------------------------------------------------------------------
// type = basicType | arrayType
//-------------------------------------------------------------------
public static String type() {
if (token.type.name().equals("arrayTK")) {
return arrayType();
} else {
return basicType();
}
}
//-------------------------------------------------------------------
// basicType = INTEGER | BOOLEAN |STRING
//-------------------------------------------------------------------
public static String basicType() {
switch (token.type.name()) {
case "integerTK": {
token = next();
return "INTEGER";
}
case "booleanTK": {
token = next();
return "BOOLEAN";
}
case "stringTK": {
token = next();
return "STRING";
}
default: {
error("Unknown data type");
return "UNKNOWN";
}
}
}
//-------------------------------------------------------------------
// arrayType = ARRAY LBRACK NUMERIC RBRACK OF basicType
//-------------------------------------------------------------------
public static String arrayType() {
if (token.type.name().equals("arrayTK")) {
token = next();
if (token.type.name().equals("lbrackTK")) {
token = next();
if (token.type.name().equals("numericTK")) {
token = next();
if (token.type.name().equals("rbrackTK")) {
token = next();
if (token.type.name().equals("ofTK")) {
token = next();
return "ARRAY OF " + basicType();
} else {
error("OF keyword expected in array variable dec-
laration");
}
} else {
error("RBRACK expected in array variable declara-
tion");
}
} else {
error("Numeric constant expected in array variable decla-
ration");
}
} else {
error("LBRACK expected in array variable declaration");
}
} else {
error("ARRAY keyword expected in array variable declaration");
}
return "UNKNOWN";
}
//-------------------------------------------------------------------
// statement = BEGIN block END
// | lvalue ASSIGN expr
// | READ LPAREN idList RPAREN
// | WRITE LPAREN exprList RPAREN
// | IF expr THEN statement (ELSE statement)?
// | WHILE expr DO statement
// | EXIT
//-------------------------------------------------------------------
public static void statement() {
switch (token.type.name()) {
case "beginTK": {
stats.incrementStatementDefined("BEGIN-END");
token = next();
block();
if (token.type.name().equals("endTK")) {
token = next();
} else {
error("END keyword expected to mark a block of state-
ments");
}
break;
}
case "readTK": {
stats.incrementStatementDefined("READ");
token = next();
if (token.type.name().equals("lparenTK")) {
token = next();
idList();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in READ statement");
}
} else {
error("LPAREN expected in READ statement");
}
break;
}
case "writeTK": {
stats.incrementStatementDefined("WRITE");
token = next();
if (token.type.name().equals("lparenTK")) {
token = next();
exprList();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in WRITE statement");
}
} else {
error("LPAREN expected in WRITE statement");
}
break;
}
case "ifTK": {
token = next();
expr();
if (token.type.name().equals("thenTK")) {
token = next();
statement();
if (token.type.name().equals("elseTK")) {
stats.incrementStatementDefined("IF-THEN-ELSE");
token = next();
statement();
} else {
stats.incrementStatementDefined("IF-THEN");
}
} else {
error("THEN keyword expected in IF statement");
}
break;
}
case "whileTK": {
stats.incrementStatementDefined("WHILE-DO");
token = next();
expr();
if (token.type.name().equals("doTK")) {
token = next();
statement();
} else {
error("DO keyword expected in WHILE statement");
}
break;
}
case "exitTK": {
stats.incrementStatementDefined("EXIT");
token = next();
break;
}
default: {
lvalue();
if (token.type.name().equals("assignTK")) {
stats.incrementStatementDefined("ASSIGNMENT");
token = next();
expr();
} else {
error("ASSIGN operator expected in assignment statement");
}
break;
}
}
}
//-------------------------------------------------------------------
// block = statement (SEMICOLON statement)* | ε
//-------------------------------------------------------------------
public static void block() {
switch (token.type.name()) {
case "beginTK":
case "readTK":
case "writeTK":
case "ifTK":
case "whileTK":
case "exitTK":
case "identifierTK": {
statement();
while (token.type.name().equals("semicolonTK")) {
token = next();
statement();
}
break;
}
default: {
break; // ε
}
}
}
//-------------------------------------------------------------------
// lvalue = ID args
//-------------------------------------------------------------------
public static void lvalue() {
if (token.type.name().equals("identifierTK")) {
checkVariableUsage(token.data, token.line);
token = next();
args();
} else {
error("variable name expected");
}
}
//-------------------------------------------------------------------
// args = LBRACK index RBRACK | ε
//-------------------------------------------------------------------
public static void args() {
if (token.type.name().equals("lbrackTK")) {
token = next();
index();
if (token.type.name().equals("rbrackTK")) {
token = next();
} else {
error("RBRACK expected in array variable reference");
}
}
}
//-------------------------------------------------------------------
// index = ID | NUMERIC
//-------------------------------------------------------------------
public static void index() {
switch (token.type.name()) {
case "identifierTK": {
checkVariableUsage(token.data, token.line);
token = next();
break;
}
case "numericTK": {
token = next();
break;
}
default: {
error("Unknown index");
break;
}
}
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// expr = logicAND (OR logicAND)*
//-------------------------------------------------------------------
public static void expr() {
logicAND();
while (token.type.name().equals("orTK")) {
stats.incrementExprDefined("LOGICAL");
token = next();
logicAND();
}
}
//-------------------------------------------------------------------
// logicAND = relationExpr (AND relationExpr)*
//-------------------------------------------------------------------
public static void logicAND() {
relationExpr();
while (token.type.name().equals("andTK")) {
stats.incrementExprDefined("LOGICAL");
token = next();
relationExpr();
}
}
//-------------------------------------------------------------------
// relationExpr = additiveExpr (relationOperator additiveExpr)*
//-------------------------------------------------------------------
public static void relationExpr() {
additiveExpr();
while (token.type.name().equals("equalTK")
|| token.type.name().equals("notEqualTK")
|| token.type.name().equals("ltTK")
|| token.type.name().equals("gtTK")
|| token.type.name().equals("lteTK")
|| token.type.name().equals("gteTK")) {
relationOperator();
additiveExpr();
}
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// factor = term (multiplyOperator term)*
//-------------------------------------------------------------------
public static void factor() {
term();
while (token.type.name().equals("timesTK")
|| token.type.name().equals("divisionTK")
|| token.type.name().equals("moduloTK")) {
multiplyOperator();
term();
}
}
//-------------------------------------------------------------------
// term = lvalue
// | constant
// | LPAREN expr RPAREN
// | NOT term
//-------------------------------------------------------------------
public static void term() {
switch (token.type.name()) {
case "numericTK":
case "stringConstTK":
case "trueTK":
case "falseTK":
case "minusTK": {
constant();
break;
}
case "lparenTK": {
token = next();
expr();
if (token.type.name().equals("rparenTK")) {
token = next();
} else {
error("RPAREN expected in expression");
}
break;
}
case "notTK": {
stats.incrementExprDefined("LOGICAL");
token = next();
term();
break;
}
default: {
lvalue();
break;
}
}
}
//-------------------------------------------------------------------
// constant = NUMERIC
// | STRING
// | TRUE
// | FALSE
// | MINUS NUMERIC
//-------------------------------------------------------------------
public static void constant() {
switch (token.type.name()) {
case "numericTK": {
stats.incrementExprDefined("CONSTANT");
token = next();
break;
}
case "stringConstTK": {
stats.incrementExprDefined("CONSTANT");
token = next();
break;
}
case "trueTK": {
stats.incrementExprDefined("CONSTANT");
token = next();
break;
}
case "falseTK": {
stats.incrementExprDefined("CONSTANT");
token = next();
break;
}
case "minusTK": {
stats.incrementExprDefined("CONSTANT");
token = next();
if (token.type.name().equals("numericTK")) {
token = next();
} else {
error("NUMERIC constant expected");
}
break;
}
default: {
error("Invalid constant");
break;
}
}
}
//-------------------------------------------------------------------
// relationOperator = EQUAL
// | NOT_EQUAL
// | LT
// | GT
// | LTE
// | GTE
//-------------------------------------------------------------------
public static void relationOperator() {
switch (token.type.name()) {
case "equalTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
case "notEqualTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
case "ltTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
case "gtTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
case "lteTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
case "gteTK": {
stats.incrementExprDefined("RELATIONAL");
token = next();
break;
}
default: {
error("Invalid relational operator");
break;
}
}
}
//-------------------------------------------------------------------
// addingOperator = PLUS
// | MINUS
// | CONCAT
//-------------------------------------------------------------------
public static void addingOperator() {
switch (token.type.name()) {
case "plusTK": {
stats.incrementExprDefined("ADDITIVE");
token = next();
break;
}
case "minusTK": {
stats.incrementExprDefined("ADDITIVE");
token = next();
break;
}
case "concatTK": {
stats.incrementExprDefined("ADDITIVE");
token = next();
break;
}
default: {
error("Invalid additive operator");
break;
}
}
}
//-------------------------------------------------------------------
// multiplyOperator = TIMES
// | DIVISION
// | MODULO
//-------------------------------------------------------------------
public static void multiplyOperator() {
switch (token.type.name()) {
case "timesTK": {
stats.incrementExprDefined("MULTIPLICATIVE");
token = next();
break;
}
case "divisionTK": {
stats.incrementExprDefined("MULTIPLICATIVE");
token = next();
break;
}
case "moduloTK": {
stats.incrementExprDefined("MULTIPLICATIVE");
token = next();
break;
}
default: {
error("Invalid additive operator");
break;
}
}
}
token = next();
program();
stats.displayStatistics(symbolTable);
}
}
Να ελέγξετε την ορθή λειτουργία του τελικού μεταγλωττιστή με τα προγράμματα που σας δόθηκαν.
Μια ενδεικτική έξοδος του μεταγλωττιστή για το PROGRAM Sample5 είναι η παρακάτω:
Απάντηση
Να ελέγξετε την ορθή λειτουργία της ανάλυσης κώδικα χρησιμοποιώντας τα προγράμματα που σας
δόθηκαν.
Τα πέντε αυτά προγράμματα βρίσκονται στο φάκελο test του project
Παραδώστε τον κώδικα και screenshots των αποτελεσμάτων.
Εάν δεν έχετε δώσει απάντηση, γράψτε με κεφαλαία γράμματα: ΔΕΝ ΑΠΑΝΤΗΘΗΚΕ.
Εάν εν γνώση σας δίνετε ελλιπή απάντηση, γράψτε με κεφαλαία γράμματα: ΕΛΛΙΠΗΣ ΑΠΑΝΤΗΣΗ.
Εξηγείστε σε ποιο σημείο θεωρείτε την απάντησή σας ελλιπή και γιατί.
sample1.spl
syntactically correct program
Program Statistics:
Variables by Type: {}
sample2.spl
syntactically correct program
Program Statistics:
sample3.spl
syntactically correct program
Program Statistics:
sample4.spl
syntactically correct program
Program Statistics:
sample5.spl
syntactically correct program
Program Statistics:
Undeclared Variables Used: [b at line 10, b at line 9, n at line 35, n at line 37, n at line 39, s1 at line 11, a at line 39,
s2 at line 11, n at line 40]
(1) S S x A
(2) | A
(3) Α B y z
(4) | w A
(5) | w A t A
(6) | r z q A
(7) | p
(8) Β u
(9) | u [ C ]
(10) C k B
Να μετασχηματίσετε την γραμματική ώστε να είναι κατάλληλη για top-down ανίχνευση με πρόβλεψη.
Να εξηγήσετε τους μετασχηματισμούς και να κάνετε τις κατάλληλες αντικαταστάσεις ώστε να προκύψει
ως τελική γραμματική αυτή με τους λιγότερους κανόνες.
Μαθησιακά Αποτελέσματα
Στην άσκηση 3. θα σας δοθεί η δυνατότητα να κατανοήσετε:
τις γραμματικές που χρησιμοποιούμε για την υλοποίηση συντακτικών αναλυτών αναδρομικής κατάβασης
τον τρόπο που μετασχηματίζουμε γραμματικές ώστε να είναι κατάλληλες για αναδρομική κατάβαση
Απάντηση
Εάν δεν έχετε δώσει απάντηση, γράψτε με κεφαλαία γράμματα: ΔΕΝ ΑΠΑΝΤΗΘΗΚΕ.
Εάν εν γνώση σας δίνετε ελλιπή απάντηση, γράψτε με κεφαλαία γράμματα: ΕΛΛΙΠΗΣ ΑΠΑΝΤΗΣΗ. Εξηγείστε σε
ποιο σημείο θεωρείτε την απάντησή σας ελλιπή και γιατί.
Για να είναι κατάλληλη η γραμματική για recursive-descent ανίχνευση θα πρέπει να εξαλείψουμε την
αριστερή αναδρομή στον κανόνα S και τις παραγωγές που έχουν κοινά προθέματα, όπως συμβαίνει με
τα εναλλακτικά μέρη του κανόνα A (σε ότι αφορά το πρόθεμα w A) και του κανόνα B (σε ότι αφορά το
πρόθεμα u).
Η νέα γραμματική διαμορφώνεται ως εξής:
(1) S A S’
(2) S’ x A S’
(3) |
(4) A B y z
(5) | w A A’
(6) | r z q A
(7) | p
(8) A’ t A
(9) |
(10) B u B’
(11) B’ [ C ]
(12) |
(13) C k B
O κανόνας (13) μπορεί να απαλειφθεί αντικαθιστώντας το σύμβολο C στον κανόνα (11). Η τελική
γραμματική είναι:
(1) S A S’
(2) S’ x A S’
(3) |
(4) A B y z
(5) | w A A’
(6) | r z q A
(7) | p
(8) A’ t A
(9) |
(10) B u B’
(11) B’ [ k B ]
(12) |