Περιεχόμενα Προοίμιο 7 Πρόλογος του συγγραφέα 21 Πρόλογος της ελληνικής έκδοσης 29 Ι ΘΕΜΕΛΙΑ 33 1 Εισαγωγή 35 1.1 Η τέχνη της σχεδίασης γλωσσών 36 1.2 Το φάσμα των γλωσσών προγραμματισμού 40 1.3 Γιατί μελετούμε τις γλώσσες προγραμματισμού; 42 1.4 Μεταγλώττιση και διερμηνεία 44 1.5 Προγραμματιστικά περιβάλλοντα 50 1.6 Γενικά για τη μεταγλώττιση 52 1.6.1 Λεκτική και συντακτική ανάλυση 53 1.6.2 Σημασιολογική ανάλυση και παραγωγή ενδιάμεσου κώδικα 55 1.6.3 Παραγωγή τελικού κώδικα 57 1.6.4 Βελτίωση κώδικα 59 1.7 Περίληψη και συμπεράσματα 60 1.8 Ασκήσεις 60 1.9 Εξερευνήσεις 62 1.10 Βιβλιογραφικές σημειώσεις 63 2 Σύνταξη των γλωσσών προγραμματισμού 65 2.1 Περιγραφή της σύνταξης 66 2.1.1 Λεκτικές μονάδες και κανονικές εκφράσεις 67 2.1.2 Γραμματικές χωρίς συμφραζόμενα 69 2.1.3 Παραγωγές και δένδρα συντακτικής ανάλυσης 70
10 Περιεχόμενα 2.2 Λεκτική ανάλυση 73 2.2.1 Δημιουργία πεπερασμένου αυτομάτου 76 2.2.2 Κώδικας του λεκτικού αναλυτή 79 2.2.3 Λεκτική ανάλυση βάσει πίνακα 83 2.2.4 Λεκτικά σφάλματα 83 2.2.5 Οδηγίες προς το μεταγλωττιστή 84 2.3 Συντακτική ανάλυση 86 2.3.1 Αναδρομική κατάβαση 89 2.3.2 Καθοδική συντακτική ανάλυση βασισμένη σε πίνακα 94 2.3.3 Ανοδική συντακτική ανάλυση 102 2.3.4 Συντακτικά σφάλματα 113 2.4 Θεωρητική θεμελίωση 114 2.5 Περίληψη και συμπεράσματα 115 2.6 Ασκήσεις 116 2.7 Εξερευνήσεις 120 2.8 Βιβλιογραφικές σημειώσεις 120 3 Ονόματα, εμβέλεια, και δέσμευση 123 3.1 Η έννοια του χρόνου δέσμευσης 124 3.2 Διάρκεια ζωής των αντικειμένων και διαχείριση μνήμης 126 3.2.1 Στατική εκχώρηση 127 3.2.2 Εκχώρηση βασισμένη σε στοίβα 128 3.2.3 Εκχώρηση βασισμένη σε σωρό 130 3.2.4 Συλλογή σκουπιδιών 132 3.3 Κανόνες εμβέλειας 133 3.3.1 Στατική εμβέλεια 134 3.3.2 Ένθετες υπορουτίνες 135 3.3.3 Σειρά δηλώσεων 138 3.3.4 Μονάδες κώδικα 142 3.3.5 Τύποι μονάδων κώδικα και κλάσεις 145 3.3.6 Δυναμική εμβέλεια 148 3.4 Υλοποίηση της εμβέλειας 151 3.5 Η δέσμευση του περιβάλλοντος αναφοράς 152 3.5.1 Κλείσιμο υπορουτίνας 153 3.5.2 Υπορουτίνες πρώτης και δεύτερης κατηγορίας 155 3.6 Δεσμεύσεις μέσα σε μια εμβέλεια 156 3.6.1 Ψευδώνυμα 157 3.6.2 Υπερφόρτωση 158 3.6.3 Πολυμορφισμός και σχετικές έννοιες 159 3.7 Ξεχωριστή μεταγλώττιση 163 3.8 Περίληψη και συμπεράσματα 163 3.9 Ασκήσεις 165 3.10 Εξερευνήσεις 170 3.11 Βιβλιογραφικές σημειώσεις 171
Περιεχόμενα 11 4 Σημασιολογική ανάλυση 173 4.1 Ο ρόλος του σημασιολογικού αναλυτή 174 4.2 Γραμματικές χαρακτηριστικών 177 4.3 Υπολογισμός χαρακτηριστικών 179 4.4 Ρουτίνες ενεργειών 188 4.5 Διαχείριση χώρου για τα χαρακτηριστικά 190 4.6 Διακόσμηση του συντακτικού δένδρου 191 4.7 Περίληψη και συμπεράσματα 196 4.8 Ασκήσεις 197 4.9 Εξερευνήσεις 201 4.10 Βιβλιογραφικές σημειώσεις 201 5 Αρχιτεκτονική του τελικού υπολογιστή 203 5.1 Η ιεραρχία της μνήμης 204 5.2 Αναπαράσταση δεδομένων 206 5.2.1 Αριθμητική υπολογιστών 207 5.3 Αρχιτεκτονική συνόλου εντολών 208 5.3.1 Τρόποι διευθυνσιοδότησης 208 5.3.2 Συνθήκες και άλματα 209 5.4 Αρχιτεκτονική και υλοποίηση 211 5.4.1 Μικροπρογραμματισμός 212 5.4.2 Μικροεπεξεργαστές 213 5.4.3 RISC 213 5.4.4 Δυο παραδείγματα αρχιτεκτονικών: x86 και MIPS 214 5.4.5 Ψευδοσυμβολική γλώσσα 215 5.5 Η μεταγλώττιση για σύγχρονους επεξεργαστές 216 5.5.1 Αποδοτική χρήση της διοχέτευσης 217 5.5.2 Δέσμευση καταχωρητών 221 5.6 Περίληψη και συμπεράσματα 226 5.7 Ασκήσεις 227 5.8 Εξερευνήσεις 230 5.9 Βιβλιογραφικές σημειώσεις 231 II ΒΑΣΙΚΑ ΖΗΤΗΜΑΤΑ ΣΧΕΔΙΑΣΗΣ ΓΛΩΣΣΩΝ 233 6 Ροή ελέγχου 235 6.1 Αποτίμηση εκφράσεων 236 6.1.1 Προτεραιότητα και προσεταιριστικότητα 237 6.1.2 Αναθέσεις 239 6.1.3 Απόδοση αρχικών τιμών 246 6.1.4 Σειρά αποτίμησης μέσα σε εκφράσεις 249 6.1.5 Βραχυκυκλωμένη αποτίμηση 251
12 Περιεχόμενα 6.2 Δομημένη και μη δομημένη ροή 253 6.2.1 Δομημένες εναλλακτικές της goto 254 6.2.2 Συνέχειες 257 6.3 Ακολουθιακή εκτέλεση 258 6.4 Επιλογή 259 6.4.1 Βραχυκυκλωμένες συνθήκες 259 6.4.2 Εντολές case/switch 261 6.5 Επανάληψη 266 6.5.1 Βρόχοι ελεγχόμενοι με απαρίθμηση 266 6.5.2 Συνδυασμένοι βρόχοι 272 6.5.3 Επαναλήπτες 273 6.5.4 Γεννήτριες στην Icon 277 6.5.5 Λογικά ελεγχόμενοι βρόχοι 278 6.6 Αναδρομή 280 6.6.1 Επανάληψη και αναδρομή 281 6.6.2 Εφαρμοστική αποτίμηση και αποτίμηση κανονικής σειράς 284 6.7 Μη ντετερμινισμός 287 6.8 Περίληψη και συμπεράσματα 287 6.9 Ασκήσεις 289 6.10 Εξερευνήσεις 294 6.11 Βιβλιογραφικές σημειώσεις 295 7 Τύποι δεδομένων 297 7.1 Συστήματα τύπων 298 7.1.1 Έλεγχος τύπων 298 7.1.2 Πολυμορφισμός 299 7.1.3 Ο ορισμός των τύπων 300 7.1.4 Η ταξινόμηση των τύπων 301 7.1.5 Ορθογωνιότητα 307 7.2 Έλεγχος τύπων 309 7.2.1 Ισοδυναμία τύπων 309 7.2.2 Συμβατότητα τύπων 315 7.2.3 Συναγωγή τύπων 319 7.2.4 Το σύστημα τύπων της ML 321 7.3 Εγγραφές (δομές) και παραλλαγές (ενώσεις) 322 7.3.1 Σύνταξη και πράξεις 323 7.3.2 Η διάταξη στη μνήμη και η σημασία της 324 7.3.3 Εντολές with 326 7.3.4 Εγγραφές με παραλλαγές 327 7.4 Πίνακες 334 7.4.1 Σύνταξη και πράξεις 334 7.4.2 Διαστάσεις, όρια, και εκχώρηση μνήμης 337 7.4.3 Διάταξη μνήμης 341 7.5 Συμβολοσειρές 348 7.6 Σύνολα 350
Περιεχόμενα 13 7.7 Δείκτες και αναδρομικοί τύποι 351 7.7.1 Σύνταξη και πράξεις 352 7.7.2 Αιωρούμενες αναφορές 359 7.7.3 Συλλογή σκουπιδιών 362 7.8 Λίστες 369 7.9 Αρχεία και είσοδος-έξοδος 371 7.10 Έλεγχος ισότητας και ανάθεση 372 7.11 Περίληψη και συμπεράσματα 374 7.12 Ασκήσεις 376 7.13 Εξερευνήσεις 382 7.14 Βιβλιογραφικές σημειώσεις 383 8 Υπορουτίνες και η αφαίρεση του ελέγχου 385 8.1 Ανασκόπηση της διάταξης της στοίβας 386 8.2 Ακολουθίες κλήσεων 387 8.2.1 Πίνακες δεικτών 390 8.2.2 Μελέτες περιπτώσεων: C στο MIPS, Pascal στον x86 391 8.2.3 Παράθυρα καταχωρητών 392 8.2.4 Εμβόλιμη ανάπτυξη 392 8.3 Μεταβίβαση παραμέτρων 394 8.3.1 Τρόποι μεταβίβασης παραμέτρων 395 8.3.2 Κλήση κατ όνομα 402 8.3.3 Παράμετροι ειδικού σκοπού 402 8.3.4 Επιστρεφόμενες τιμές συναρτήσεων 407 8.4 Γενικές υπορουτίνες και μονάδες κώδικα 409 8.4.1 Επιλογές υλοποίησης 410 8.4.2 Περιορισμοί γενικών παραμέτρων 411 8.4.3 Έμμεση δημιουργία παρουσιών 413 8.4.4 Γενικές δομές στη C++, την Java, και τη C# 414 8.5 Χειρισμός εξαιρέσεων 415 8.5.1 Ορισμός εξαιρέσεων 417 8.5.2 Διάδοση εξαιρέσεων 418 8.5.3 Παράδειγμα: Ανάνηψη επιπέδου φράσης σε συντακτικό αναλυτή αναδρομικής κατάβασης 421 8.5.4 Υλοποίηση των εξαιρέσεων 422 8.6 Συρρουτίνες 425 8.6.1 Εκχώρηση στη στοίβα 427 8.6.2 Μεταφορά 429 8.6.3 Υλοποίηση επαναληπτών 430 8.6.4 Προσομοίωση διακριτών συμβάντων 430 8.7 Περίληψη και συμπεράσματα 431 8.8 Ασκήσεις 432 8.9 Εξερευνήσεις 437 8.10 Βιβλιογραφικές σημειώσεις 437
14 Περιεχόμενα 9 Αφαίρεση δεδομένων και αντικειμενοστρεφείς γλώσσες 439 9.1 Αντικειμενοστρεφής προγραμματισμός 440 9.2 Ενθυλάκωση και κληρονομικότητα 449 9.2.1 Μονάδες κώδικα 449 9.2.2 Κλάσεις 452 9.2.3 Επεκτάσεις τύπων 454 9.3 Απόδοση αρχικών τιμών και καταστροφή 456 9.3.1 Επιλογή κατασκευαστή 457 9.3.2 Αναφορές και τιμές 458 9.3.3 Σειρά εκτέλεσης 461 9.3.4 Συλλογή σκουπιδιών 462 9.4 Δυναμική δέσμευση μεθόδων 463 9.4.1 Εικονικές και μη εικονικές μέθοδοι 465 9.4.2 Αφηρημένες κλάσεις 466 9.4.3 Αναζήτηση μελών 467 9.4.4 Πολυμορφισμός 470 9.4.5 Κλεισίματα 472 9.5 Πολλαπλή κληρονομικότητα 475 9.6 Επιστροφή στον αντικειμενοστρεφή προγραμματισμό 476 9.6.1 Το μοντέλο αντικειμένων της Smalltalk 476 9.7 Περίληψη και συμπεράσματα 477 9.8 Ασκήσεις 478 9.9 Εξερευνήσεις 480 9.10 Βιβλιογραφικές σημειώσεις 481 III ΕΝΑΛΛΑΚΤΙΚΑ ΜΟΝΤΕΛΑ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ 483 10 Συναρτησιακές γλώσσες 485 10.1 Ιστορικά στοιχεία 486 10.2 Έννοιες συναρτησιακού προγραμματισμού 487 10.3 Μια ανασκόπηση/σύνοψη της Scheme 489 10.3.1 Δεσμεύσεις 491 10.3.2 Λίστες και αριθμοί 492 10.3.3 Έλεγχος ισότητας και αναζήτηση 492 10.3.4 Ροή ελέγχου και ανάθεση 493 10.3.5 Προγράμματα ως λίστες 495 10.3.6 Εκτενές παράδειγμα: προσομοίωση ΝΠΑ 497 10.4 Επιστροφή στη σειρά αποτίμησης 498 10.4.1 Αυστηρότητα και οκνηρή αποτίμηση 500 10.4.2 Είσοδος-έξοδος: ρεύματα και μονάδες 501 10.5 Συναρτήσεις υψηλής τάξης 504 10.6 Θεωρητικές βάσεις 507
Περιεχόμενα 15 10.7 Απόψεις περί συναρτησιακού προγραμματισμού 507 10.8 Περίληψη και συμπεράσματα 509 10.9 Ασκήσεις 510 10.10 Εξερευνήσεις 514 10.11 Βιβλιογραφικές σημειώσεις 515 11 Γλώσσες λογικού προγραμματισμού 517 11.1 Έννοιες του λογικού προγραμματισμού 518 11.2 Prolog 518 11.2.1 Επίλυση και ενοποίηση 520 11.2.2 Λίστες 522 11.2.3 Αριθμητικές πράξεις 522 11.2.4 Αναζήτηση και σειρά εκτέλεσης 523 11.2.5 Εκτενές παράδειγμα: τρίλιζα 525 11.2.6 Προστακτικός έλεγχος ροής 528 11.2.7 Διαχείριση της βάσης δεδομένων 530 11.3 Θεωρητικές βάσεις 534 11.4 Προοπτικές του λογικού προγραμματισμού 535 11.4.1 Μέρη της λογικής που δεν καλύψαμε 535 11.4.2 Σειρά εκτέλεσης 535 11.4.3 Άρνηση και η υπόθεση του κλειστού κόσμου 536 11.5 Περίληψη και συμπεράσματα 537 11.6 Ασκήσεις 539 11.7 Εξερευνήσεις 540 11.8 Βιβλιογραφικές σημειώσεις 541 12 Ταυτοχρονισμός 543 12.1 Υπόβαθρο και κίνητρα 544 12.1.1 Λίγη ιστορία 544 12.1.2 Η υπόθεση των πολυνηματικών προγραμμάτων 546 12.1.3 Αρχιτεκτονική πολυεπεξεργαστών 550 12.2 Θεμελιώδεις έννοιες του ταυτόχρονου προγραμματισμού 553 12.2.1 Επικοινωνία και συγχρονισμός 554 12.2.2 Γλώσσες και βιβλιοθήκες 555 12.2.3 Σύνταξη δημιουργίας νημάτων 556 12.2.4 Υλοποίηση νημάτων 564 12.3 Κοινόχρηστη μνήμη 569 12.3.1 Συγχρονισμός αναμονής με απασχόληση 570 12.3.2 Υλοποίηση χρονοπρογραμματιστή 573 12.3.3 Σηματοφόροι 576 12.3.4 Μόνιτορ 578 12.3.5 Κρίσιμες περιοχές υπό συνθήκη 582 12.3.6 Έμμεσος συγχρονισμός 586 12.4 Μεταβίβαση μηνυμάτων 589 12.4.1 Ονομασία επικοινωνούντων μερών 589 12.4.2 Αποστολή 593
16 Περιεχόμενα 12.4.3 Παραλαβή 596 12.4.4 Κλήση απομακρυσμένων διαδικασιών 601 12.5 Περίληψη και συμπεράσματα 605 12.6 Ασκήσεις 607 12.7 Εξερευνήσεις 612 12.8 Βιβλιογραφικές σημειώσεις 613 13 Γλώσσες σεναρίων 615 13.1 Τι είναι γλώσσα σεναρίων; 616 13.1.1 Κοινά γνωρίσματα 618 13.2 Πεδία εφαρμογής 621 13.2.1 Γλώσσες (διαταγών) κελύφους 621 13.2.2 Επεξεργασία κειμένου και παραγωγή εκθέσεων 627 13.2.3 Μαθηματικά και στατιστική 631 13.2.4 Γλώσσες συγκόλλησης και σενάρια γενικού σκοπού 632 13.2.5 Γλώσσες επέκτασης 639 13.3 Σενάρια στον Παγκόσμιο Ιστό 642 13.3.1 Σενάρια CGI 643 13.3.2 Ενσωματωμένα σενάρια εξυπηρετητή 644 13.3.3 Σενάρια πελάτη 646 13.3.4 Μικροεφαρμογές Java 648 13.3.5 XSLT 651 13.4 Καινοτομίες 660 13.4.1 Ονόματα και εμβέλειες 661 13.4.2 Επεξεργασία συμβολοσειρών και προτύπων 665 13.4.3 Τύποι δεδομένων 671 13.4.4 Αντικειμενοστρέφεια 676 13.5 Περίληψη και συμπεράσματα 682 13.6 Ασκήσεις 684 13.7 Εξερευνήσεις 688 13.8 Βιβλιογραφικές σημειώσεις 689 IV ΜΙΑ ΠΙΟ ΚΟΝΤΙΝΗ ΜΑΤΙΑ ΣΤΗΝ ΥΛΟΠΟΙΗΣΗ 693 14 Δημιουργία ενός εκτελέσιμου προγράμματος 695 14.1 Δομή του οπίσθιου τμήματος του μεταγλωττιστή 695 14.1.1 Μια εύλογη οργάνωση σε φάσεις 696 14.1.2 Φάσεις και διελεύσεις 698 14.2 Ενδιάμεσος κώδικας 699 14.3 Παραγωγή κώδικα 702 14.3.1 Ένα παράδειγμα γραμματικής χαρακτηριστικών 702 14.3.2 Δέσμευση καταχωρητών 704 14.4 Οργάνωση χώρου διευθύνσεων 706
Περιεχόμενα 17 14.5 Συμβολική γλώσσα 708 14.5.1 Παραγωγή εντολών 709 14.5.2 Ανάθεση διευθύνσεων σε ονόματα 711 14.6 Σύνδεση 712 14.6.1 Επανατοποθέτηση και επίλυση ονομάτων 713 14.6.2 Έλεγχος τύπων 713 14.7 Δυναμική σύνδεση 715 14.8 Περίληψη και συμπεράσματα 716 14.9 Ασκήσεις 718 14.10 Εξερευνήσεις 719 14.11 Βιβλιογραφικές σημειώσεις 720 15 Βελτίωση κώδικα 723 Α Γλώσσες προγραμματισμού που αναφέρθηκαν 725 Β Σχεδίαση και υλοποίηση γλωσσών 735 Γ Αριθμημένα παραδείγματα 739 Βιβλιογραφία 747 Ευρετήριο 761 Περιεχόμενα του CD Στην αγγλική γλώσσα. (Με τελεία σημειώνονται οι αντίστοιχες εισαγωγικές παράγραφοι στο βιβλίο.) 2.3.4 Syntax Errors 113 CD 1 2.4 eoretical Foundations 114 CD 13 2.4.1 Finite Automata CD 13 2.4.2 Push-Down Automata CD 16 2.4.3 Grammar and Language Classes CD 17 3.4 Implementing Scope 151 CD 23 3.4.1 Symbol Tables CD 23 3.4.2 Association Lists and Central Reference Tables CD 27 3.7 Separate Compilation 163 CD 30 3.7.1 Separate Compilation in C CD 30 3.7.2 Packages and Automatic Header Inference CD 33 3.7.3 Module Hierarchies CD 35 4.5 Space Management for Attributes 190 CD 39 4.5.1 Bottom-Up Evaluation CD 39 4.5.2 Top-Down Evaluation CD 44 5.2.1 Computer Arithmetic 207 CD 54 5.4.4 Two Example Architectures: e x86 and MIPS 214 CD 59 6.5.4 Generators in Icon 277 CD 69
18 Περιεχόμενα 6.7 Nondeterminacy 287 CD 72 7.2.4 e ML Type System 321 CD 81 7.3.3 With Statements 326 CD 90 7.9 Files and Input/Output 371 CD 93 7.9.1 Interactive I/O CD 93 7.9.2 File-Based I/O CD 94 7.9.3 Text I/O CD 96 8.2.1 Displays 390 CD 107 8.2.2 Case Studies: C on the MIPS; Pascal on the x86 391 CD 111 8.2.3 Register Windows 392 CD 119 8.3.2 Call by Name 402 CD 122 8.4.4 Generics in C++, Java, and C# 414 CD 125 8.6.3 Implementation of Iterators 430 CD 135 8.6.4 Discrete Event Simulation 430 CD 139 9.5 Multiple Inheritance 475 CD 146 9.5.1 Semantic Ambiguities CD 148 9.5.2 Replicated Inheritance CD 151 9.5.3 Shared Inheritance CD 152 9.5.4 Mix-In Inheritance CD 154 9.6.1 e Object Model of Smalltalk 476 CD 158 10.6 eoretical Foundations 507 CD 166 10.6.1 Lambda Calculus CD 168 10.6.2 Control Flow CD 171 10.6.3 Structures CD 173 11.3 eoretical Foundations 534 CD 180 11.3.1 Clausal Form CD 181 11.3.2 Limitations CD 182 11.3.3 Skolemization CD 183 14.2 Intermediate Forms 699 CD 189 14.2.1 Diana CD 189 14.2.2 GNU RTL CD 192 14.7.1 Position-Independent Code CD 195 14.7.2 Fully Dynamic (Lazy) Linking CD 196 15 Code Improvement 723 CD 202 15.1 Phases of Code Improvement CD 204 15.2 Peephole Optimization CD 206 15.3 Redundancy Elimination in Basic Blocks CD 209 15.3.1 A Running Example CD 210 15.3.2 Value Numbering CD 211 15.4 Global Redundancy and Data Flow Analysis CD 217 15.4.1 SSA Form and Global Value Numbering CD 218 15.4.2 Global Common Subexpression Elimination CD 220 15.5 Loop Improvement I CD 227 15.5.1 Loop Invariants CD 228 15.5.2 Induction Variables CD 229 15.6 Instruction Scheduling CD 232
Περιεχόμενα 19 15.7 Loop Improvement II CD 236 15.7.1 Loop Unrolling and So ware Pipelining CD 237 15.7.2 Loop Reordering CD 241 15.8 Register Allocation CD 248 15.9 Summary and Concluding Remarks CD 252 15.10 Exercises CD 253 15.11 Explorations CD 257 15.12 Bibliographic Notes CD 258
Χρήση αυτού του βιβλίου με το συνοδευτικό CD Η Πραγματολογία των γλωσσών προγραμματισμού περιλαμβάνει ένα συνοδευτικό CD που περιέχει (στην αγγλική γλώσσα) προηγμένα/προαιρετικά θέματα και το πλήρες κείμενο του Κεφαλαίου 15: Βελτίωση κώδικα. Αυτή η ύλη που βρίσκεται μόνο στο CD σημειώνεται και αναφέρεται μέσα στο κείμενο με διάφορους τρόπους: 2 Παράγραφοι Σε περισσότερο βάθος Οι παράγραφοι Σε περισσότερο βάθος συνοψίζουν την προαιρετική ύλη. 2 Πίνακας περιεχομένων Η ύλη που βρίσκεται μόνο στο CD περιλαμβάνεται (ξεχωριστά) στον πίνακα περιεχομένων. Μπορείτε να χρησιμοποιήσετε τον πίνακα περιεχομένων για να βρείτε γρήγορα όλες τις ενότητες και τις υποενότητες που είναι διαθέσιμες στο CD. Για παράδειγμα, στον πίνακα περιεχομένων του βιβλίου θα βρείτε: 2.4 Θεωρητική θεμελίωση 112 και στο τέλος του πίνακα, στα περιεχόμενα του CD: 2.4 eoretical Foundations CD 13 2.4.1 Finite Automata CD 13 2.4.2 Push-Down Automata CD 16 Αυτό σημαίνει ότι η Ενότητα 2.4 εισάγεται στη σελίδα 112 του βιβλίου (στη σελίδα όπου βρίσκεται η εισαγωγική παράγραφος Σε περισσότερο βάθος ) το μεγαλύτερο μέρος του κειμένου αυτής της ενότητας (στην αγγλική γλώσσα) καθώς και οι υποενότητες που περιέχει βρίσκονται στις σελίδες 13 16 του CD. 2 Αναφορές Σε όλο το κείμενο του βιβλίου, το εικονίδιο περισσότερο βάθος στο CD. σημαίνει αναφορές προς ύλη Σε Περιεχόμενα του CD 2 Κεφάλαιο 15, Βελτίωση κώδικα: Όλο το κείμενο του Κεφαλαίου είναι διαθέσιμο στο CD ως ένα αρχείο PDF. Το κείμενο του βιβλίου περιέχει μόνο μια μικρή εισαγωγή. 2 Ενότητες και υποενότητες: Πρόσθετη ύλη που εισάγεται στο βιβλίο και καλύπτεται λεπτομερώς στο CD. 2 Ασκήσεις: Πρόσθετες ασκήσεις για προσωπική μελέτη ή άλλες σε στυλ εργασιών για το σπίτι, που σημειώνονται στο κείμενο του βιβλίου με την ένδειξη Σε περισσότερο βάθος, αλλά και δυσκολότερες ασκήσεις Εξερευνήσεων για τους πιο προχωρημένους και περίεργους αναγνώστες. 2 Εκπαιδευτικά βοηθήματα και εγχειρίδια αναφοράς: Ένας περιεκτικός κατάλογος συνδέσμων προς τα πιο χρήσιμα εκπαιδευτικά βοηθήματα και εγχειρίδια αναφορών γλωσσών προγραμματισμού που υπάρχουν στον Ιστό. 2 Μεταγλωττιστές και διερμηνείς: Σύνδεσμοι προς μεταγλωττιστές και διερμηνείς γλωσσών προγραμματισμού που υπάρχουν στον Ιστό. 2 Μηχανή αναζήτησης: Μια μηχανή αναζήτησης για τις συμπληρωματικές ενότητες αλλά και για το κείμενο του τυπωμένου βιβλίου. (Σημείωση: όλα τα στοιχεία του συνοδευτικού CD είναι στην αγγλική γλώσσα και οι παραπομπές της μηχανής αναζήτησης στο τυπωμένο κείμενο αφορούν την πρωτότυπη αμερικανική έκδοση του βιβλίου.)
Προοίμιο Η επιστήμη των υπολογιστών έχει την εξαιρετική ικανότητα να δημιουργεί διαδοχικά στρώματα αφαιρέσεων. Η ευκολία με την οποία αυτός ο επιστημονικός κλάδος κρύβει τις λεπτομέρειες πίσω από μια απλοποιημένη διασύνδεση είναι και αρετή και αναγκαιότητα. Τα λειτουργικά συστήματα, οι βάσεις δεδομένων, και οι μεταγλωττιστές είναι υπερβολικά πολύπλοκα προγράμματα που έχουν ωριμάσει μετά από σαράντα χρόνια θεωρίας και ανάπτυξης. Στο μεγαλύτερο μέρος, οι προγραμματιστές χρειάζεται να καταλαβαίνουν ελάχιστα ή και καθόλου την εσωτερική λογική ή τη δομή ενός συστήματος λογισμικού, προκειμένου να το χρησιμοποιήσουν παραγωγικά. Τις περισσότερες φορές, η άγνοια είναι ευτυχία. Η αδιαφανής αφαίρεση όμως μπορεί να μετατραπεί σε τοίχο που εμποδίζει την πρόοδο, αντί να αποτελεί ένα ισχυρό θεμέλιο για νέες κατασκευές. Ας πάρουμε το θέμα αυτού του βιβλίου: τα προγράμματα και τις γλώσσες προγραμματισμού. Τι συμβαίνει όταν ένα πρόγραμμα λειτουργεί πολύ αργά, και η ανάλυση της απόδοσής του δεν μπορεί να εντοπίσει κανένα προφανές σημείο συμφόρησης, ή το σημείο συμφόρησης δεν εξηγείται αλγοριθμικά; Μερικά τέτοια προβλήματα οφείλονται στη μετάφραση των δομών της γλώσσας προγραμματισμού σε εντολές γλώσσας μηχανής, ή στον τρόπο που ο παραγόμενος ο κώδικας αλληλεπιδρά με την αρχιτεκτονική του επεξεργαστή. Η διόρθωση αυτών των προβλημάτων απαιτεί μια κατανόηση που γεφυρώνει τα επίπεδα της αφαίρεσης. Η αφαίρεση μπορεί επίσης να εμποδίζει την εκμάθηση. Απλές ερωτήσεις πώς προγράμματα γραμμένα σε ένα μικρό και επιτηδευμένο υποσύνολο της αγγλικής γλώσσας μπορούν να ελέγχουν υπολογιστές που μιλούν μόνο δυαδικό κώδικα, ή γιατί οι γλώσσες προγραμματισμού, παρά το μεγάλο πλήθος και την ποικιλία τους, μοιάζουν όλες τόσο πολύ μεταξύ τους δεν μπορούν να απαντηθούν παρά μόνο αν μπούμε βαθιά στις λεπτομέρειες και προσπαθήσουμε να καταλάβουμε τους υπολογιστές, τους μεταγλωττιστές, και τις γλώσσες. Η εκπαίδευση στην επιστήμη των υπολογιστών, στο σύνολό της, μπορεί να απαντήσει σε αυτές τις ερωτήσεις. Τα περισσότερα προγράμματα προπτυχιακών σπουδών προσφέρουν μαθήματα αρχιτεκτονικής υπολογιστών, λειτουργικών συστημάτων, σχεδίασης γλωσσών προγραμματισμού, και μεταγλωττιστών. Όλα αυτά είναι ενδιαφέροντα μαθήματα που αξίζει τον κόπο να παρακολουθήσουν οι σπουδαστές είναι όμως δύσκολο να χωρέσουν όλα στο πρόγραμμα σπουδών της επιστήμης των υπολογιστών, μαζί με τα πολλά άλλα και πλούσια μαθήματα που πρέπει να υπάρχουν εκεί. Επιπλέον, τα μαθήματα συχνά διδάσκονται ως ανεξάρτητες και αυτοτελείς θεματικές ενότητες και δεν εξηγούν τη σύνδεσή τους με άλλα μαθήματα. Το βιβλίο αυτό απαντά και σε αυτές τις ερωτήσεις, κοιτάζοντας πέρα από τις αφαιρέσεις που διαχωρίζουν αυτά τα θέματα. Ο Michael Scott είναι ένας διακεκριμένος ερευνητής με σημαντική συνεισφορά στην υλοποίηση των γλωσσών, τα συστήματα χρόνου εκτέλεσης, και την αρχιτεκτονική των υπολογιστών. Έχει μια ιδιαίτερη ικανότητα να αντλεί από όλα αυτά τα γνωστικά πεδία και να βοηθάει τους αναγνώστες να καταλάβουν σε βάθος και με συνέπεια τις σύγχρονες γλώσσες προγραμματισμού. Αυτό το βιβλίο όχι μόνο εξηγεί με αξιοθαύμαστη σαφήνεια τις βασικές έννοιες των γλωσσών προγραμματισμού και τις λεπτομέρειες που αφορούν την υλοποίηση, αλλά δείχνει στον αναγνώστη 7
8 Προοίμιο με ποιο τρόπο η αρχιτεκτονική των υπολογιστών και οι μεταγλωττιστές επηρεάζουν τη σχεδίαση και την υλοποίηση των γλωσσών προγραμματισμού. Επιπλέον, παρουσιάζει τον τρόπο χρήσης των διαφορετικών γλωσσών στην πράξη, με ρεαλιστικά παραδείγματα που δείχνουν καθαρά πώς τα πεδία εφαρμογής επίσης επηρεάζουν τις γλώσσες. Για να είμαι ειλικρινής, πρέπει να ομολογήσω ότι αυτό το βιβλίο με προβλημάτισε όταν το διάβασα για πρώτη φορά. Σκέφθηκα τότε ότι η προσέγγιση του συγγραφέα εξασθένιζε τη σημασία των γλωσσών προγραμματισμού και των μεταγλωττιστών στο πρόγραμμα σπουδών και άφηνε τους σπουδαστές με μια επιφανειακή μόνο κατανόηση. Τώρα όμως, έχοντας ξαναδιαβάσει το βιβλίο, αντιλαμβάνομαι ότι στην πραγματικότητα συμβαίνει ακριβώς το αντίθετο. Παρουσιάζοντάς τες στο κατάλληλο περιβάλλον, αυτό το βιβλίο δείχνει ότι οι γλώσσες προγραμματισμού είναι το πραγματικό κέντρο της επιστήμης των υπολογιστών είναι αυτές που γεφυρώνουν το χάσμα ανάμεσα στον προγραμματιστή και τον υπολογιστή. James Larus, Microso Research
Πρόλογος του συγγραφέα Συνήθως, η πρώτη επαφή των σπουδαστών με την επιστήμη των υπολογιστών είναι μέσω ενός μαθήματος προγραμματισμού. Σε ένα τέτοιο μάθημα, οι περισσότεροι σπουδαστές θα έχουν χρησιμοποιήσει τους υπολογιστές όλη τους τη ζωή, για ηλεκτρονικό ταχυδρομείο, περιήγηση στον Παγκόσμιο Ιστό, επεξεργασία κειμένου, αποστολή άμεσων μηνυμάτων, και ένα σωρό άλλες δουλειές, αλλά μόνο όταν γράφουν τα πρώτα τους προγράμματα αρχίζουν να εκτιμούν πώς δουλεύουν οι εφαρμογές. Αφού αποκτήσουν μια σχετική ευχέρεια στον προγραμματισμό (προφανώς με τη βοήθεια και ενός καλού μαθήματος δομών δεδομένων και αλγορίθμων), το επόμενο φυσικό βήμα είναι να αναρωτηθούν πώς δουλεύουν οι γλώσσες προγραμματισμού. Αυτό το βιβλίο δίνει μια εξήγηση. Πολύ απλά, σκοπεύει να αποτελέσει το πιο περιεκτικό και ακριβές βιβλίο για τις γλώσσες προγραμματισμού, με τρόπο ώστε να είναι προσιτό και ευχάριστο στο μέσο προπτυχιακό σπουδαστή. Ο σκοπός αυτός αντανακλά την πεποίθησή μου ότι οι σπουδαστές καταλαβαίνουν περισσότερα και απολαμβάνουν περισσότερο την ύλη αν τους εξηγήσουμε τι συμβαίνει στην πραγματικότητα. Στο συμβατικό πρόγραμμα σπουδών ενός πανεπιστημιακού τμήματος επιστήμης υπολογιστών ή μηχανικών υπολογιστών, η ύλη εκτός των δομών δεδομένων (και πιθανόν της οργάνωσης των υπολογιστών) συνήθως διαιρείται σε ένα σύνολο από ξεχωριστά μαθήματα, όπως γλώσσες προγραμματισμού, κατασκευή μεταγλωττιστών, αρχιτεκτονική υπολογιστών, λειτουργικά συστήματα, δίκτυα υπολογιστών, παράλληλοι και κατανεμημένοι υπολογισμοί, συστήματα διαχείρισης βάσεων δεδομένων, και πιθανόν τεχνολογία λογισμικού, αντικειμενοστρεφής σχεδίαση, γραφικά, ή συστήματα διασύνδεσης ανθρώπουυπολογιστή. Ένα πρόβλημα αυτής της διαίρεσης είναι ότι η λίστα των μαθημάτων διαρκώς μεγαλώνει, ενώ ο αριθμός των εξαμήνων στο προπτυχιακό πρόγραμμα σπουδών παραμένει σταθερός. Πιο σημαντικό ίσως είναι ότι πολλές από τις πιο ενδιαφέρουσες ανακαλύψεις στην επιστήμη των υπολογιστών συμβαίνουν στα όρια μεταξύ των θεματικών περιοχών που ορίζουν τα μαθήματα. Η επανάσταση των επεξεργαστών RISC, για παράδειγμα, οδήγησε σε μια συμμαχία μεταξύ της αρχιτεκτονικής των υπολογιστών και της κατασκευής μεταγλωττιστών που κρατάει εδώ και 20 χρόνια. Πιο πρόσφατα, το ανανεωμένο ενδιαφέρον για τις εικονικές μηχανές έχει θολώσει τη διαχωριστική γραμμή ανάμεσα στον πυρήνα του λειτουργικού συστήματος και το σύστημα χρόνου εκτέλεσης της γλώσσας προγραμματισμού. Παρόμοια, η διάδοση της Java και του.net έχει θολώσει τη διαχωριστική γραμμή μεταξύ μεταγλωττιστή και συστήματος χρόνου εκτέλεσης. Τα προγράμματα σήμερα ενσωματώνονται συχνά σε ιστοσελίδες, λογιστικά φύλλα, και διασυνδέσεις με το χρήστη. Με την πάροδο του χρόνου, τόσο οι εκπαιδευτικοί όσο και αυτοί που ασκούν τον προγραμματισμό ως επάγγελμα αναγνωρίζουν όλο και περισσότερο την ανάγκη να δώσουν έμφαση σε αυτού του είδους τις αλληλεπιδράσεις. Ιδιαίτερα στην ανώτατη εκπαίδευση, υπάρχει μια αυξανόμενη τάση προς ενσωμάτωση στον κορμό του προγράμματος σπουδών. Αντί να δίνουν στο μέσο σπουδαστή εμβάθυνση μόνο σε δύο ή τρία στενά θέματα, αφήνοντας τρύπες σε όλα τα υπόλοιπα, πολλά πανεπιστήμια έχουν αναθεωρήσει τα μαθήματα των γλωσσών προγραμματισμού και των λειτουργικών συστημάτων ώστε να καλύπτουν μια ευρύτερη θεματική περιοχή, σε συνδυασμό με μαθήματα επιλογής δια- 21
22 Πρόλογος του συγγραφέα φόρων ειδικοτήτων. Η τάση αυτή είναι σε μεγάλο βαθμό σύμφωνη με τα ευρήματα της ομάδας εργασίας της ACM/IEEE-CS για τον προσδιορισμό του προγράμματος σπουδών στην επιστήμη των υπολογιστών το 2001 (Computing Curricula 2001), που δίνουν έμφαση στην ανάπτυξη του κλάδου, την αυξανόμενη ανάγκη για διεύρυνση, τη σημασία της ευελιξίας στη σχεδίαση των προγραμμάτων σπουδών, και τον κυρίαρχο στόχο των αποφοίτων που μπορούν να δουν τα πράγματα στο επίπεδο του συστήματος, εκτιμούν την αλληλεπίδραση θεωρίας και πράξης, είναι εξοικειωμένοι με τα ζητήματα που εμφανίζονται κατ επανάληψη, και μπορούν να προσαρμόζονται με το χρόνο, καθώς ο κλάδος εξελίσσεται [CR01, 11.1, μτφρ. και προσαρμοσμένο]. Η πρώτη αμερικανική έκδοση της Πραγματολογίας των γλωσσών προγραμματισμού (ΠΓΠ-1ε) είχε την καλή τύχη να ακολουθεί αυτή την τάση. Η δεύτερη έκδοση συνεχίζει και επιτείνει την έμφαση στην ολοκληρωμένη εκμάθηση, διατηρώντας στο επίκεντρο τη σχεδίαση των γλωσσών προγραμματισμού. Στον πυρήνα της, η ΠΓΠ είναι ένα βιβλίο σχετικά με τον τρόπο που δουλεύουν οι γλώσσες προγραμματισμού. Αντί να απαριθμεί τις λεπτομέρειες πολλών διαφορετικών γλωσσών, εστιάζεται στις έννοιες που αποτελούν τη βάση όλων των γλωσσών τις οποίες είναι πιθανό να συναντήσει ο σπουδαστής, εξηγώντας αυτές τις έννοιες με μια ποικιλία συγκεκριμένων παραδειγμάτων, και εξερευνώντας τους συμβιβασμούς που εξηγούν γιατί διαφορετικές γλώσσες έχουν σχεδιαστεί με διαφορετικό τρόπο. Παρόμοια, αντί να εξηγεί πώς κατασκευάζεται ένας μεταγλωττιστής ή ένας διερμηνέας (εργασία που μόνο λίγοι προγραμματιστές θα αναλάβουν να φέρουν σε πέρας), η ΠΓΠ επικεντρώνεται στο τι κάνει ένας μεταγλωττιστής στο πρόγραμμα εισόδου και γιατί. Η σχεδίαση και η υλοποίηση των γλωσσών αντιμετωπίζεται επομένως από κοινού, με έμφαση στους τρόπους με τους οποίους αυτές οι δύο αλληλεπιδρούν. Αλλαγές στη δεύτερη αμερικανική έκδοση Στη δεύτερη αμερικανική έκδοση υπήρχαν τέσσερις κύριοι στόχοι: 1. Να προστεθεί νέα ύλη, με κυριότερη τις γλώσσες σεναρίων. 2. Να ενημερωθεί το βιβλίο με οτιδήποτε συνέβη τα έξι χρόνια που μεσολάβησαν από την πρώτη έκδοση. 3. Να αντισταθεί στην πίεση αύξησης των τιμών των βιβλίων. 4. Να ενισχυθεί το βιβλίο από παιδαγωγική άποψη, και να γίνει πιο χρήσιμο και προσιτό. Το στοιχείο (1) είναι η σημαντικότερη αλλαγή στο περιεχόμενο του βιβλίου. Με την έκρηξη του Παγκόσμιου Ιστού, γλώσσες όπως οι Perl, PHP, Tcl/Tk, Python, Ruby, JavaScript, και XSLT έχουν γνωρίσει τεράστια άνθιση όχι μόνο ως προς την εμπορική τους σημασία, αλλά και ως προς τις σχεδιαστικές τους καινοτομίες. Πολλοί από τους σημερινούς αποφοίτους θα περάσουν περισσότερο χρόνο δουλεύοντας με γλώσσες σεναρίων, παρά με τις C++, Java, ή C#. Το νέο κεφάλαιο των γλωσσών σεναρίων (Κεφάλαιο 13) είναι οργανωμένο καταρχήν κατά τομέα εφαρμογής (γλώσσες κελύφους, επεξεργασία κειμένου και παραγωγή εκθέσεων, μαθηματικά και στατιστική, γλώσσες συγκόλλησης και σενάρια γενικού σκοπού, γλώσσες επέκτασης, σενάρια για τον Παγκόσμιο Ιστό) και μετά ως προς τις καινοτόμες δυνατότητες (ονόματα και εμβέλεια, χειρισμός συμβολοσειρών και προτύπων, τύποι δεδομένων υψηλού επιπέδου, αντικειμενοστρέφεια). Έχουν επίσης προστεθεί αναφορές σε γλώσσες σεναρίων όπου χρειαζόταν στο υπόλοιπο βιβλίο. Το στοιχείο (2) αντανακλά σημαντικές εξελίξεις, όπως το τελικό πρότυπο της C99 και η εμφάνιση των Java 5 και C# (έκδοση 2.0). Το Κεφάλαιο 6 (Ροή ελέγχου) καλύπτει την πλαισίωση (boxing), την αποπλαισίωση (unboxing), και τις νεότερες δομές ροής ελέγχου. Το Κεφάλαιο 8 (Υπορουτίνες) καλύπτει τη γενικότητα (generics) της Java και της C#. Το Κεφάλαιο 12 (Ταυτοχρονισμός) καλύπτει τη βιβλιοθήκη ταυτοχρονισμού της Java 5 (JSR 166). Όπου χρειάζεται, έχουν προστεθεί αναφορές στη C#. Σύμφωνα με τις αλλαγές στην αγορά των μικροεπεξεργαστών, ο πανταχού παρών x86 των Intel/AMD έχει αντικαταστήσει τον Motorola 68000 στις μελέτες περιπτώσεων των Κεφαλαίων 5 (Αρχιτεκτονική) και 8 (Υπορουτίνες). Η μελέτη περίπτωσης του MIPS στο Κεφάλαιο 8 ενημε-
Πρόλογος του συγγραφέα 23 ρώθηκε για κώδικα 64 bit. Οι αναφορές στις τεχνολογικές σταθερές και στις νέες τάσεις έχουν επίσης ενημερωθεί. Σε αρκετά σημεία, τροποποίησα τα παραδείγματα για γλώσσες με τις οποίες οι σπουδαστές είναι πιθανότερο να είναι εξοικειωμένοι αυτή η διαδικασία οπωσδήποτε θα συνεχιστεί στις επόμενες εκδόσεις. Πολλές ενότητες έχουν ξαναγραφεί σε μεγάλο βαθμό ώστε να γίνουν πιο σαφείς και ακριβείς. Αυτές καλύπτουν την κατασκευή πεπερασμένων αυτομάτων (2.2.1), τη σειρά των δηλώσεων (3.3.3), τις μονάδες κώδικα (3.3.4), τα ψευδώνυμα και την υπερφόρτωση (3.6.1 και 3.6.2), τον πολυμορφισμό και τη γενικότητα (3.6.3, 7.1.2, 8.4, και 9.4.4), την ξεχωριστή μεταγλώττιση (3.7), τις συνέχειες, τις εξαιρέσεις, και τις επιστροφές από πολυεπίπεδες κλήσεις (6.2.1, 6.2.2, και 8.5), τις ακολουθίες κλήσεων (8.2), και το μεγαλύτερο μέρος του Κεφαλαίου 5. Το στοιχείο (3) εκφράζει τη δέσμευση του εκδοτικού οίκου Morgan Kaufmann να εκδίδει βιβλία σε τιμές προσιτές στους σπουδαστές. Η ΠΓΠ-1ε ήταν ένα βιβλίο μεγαλύτερο και περιεκτικότερο από τους ανταγωνιστές του, σε φθηνότερη τιμή. Η δεύτερη αυτή αμερικανική έκδοση διατηρεί χαμηλή τιμή (και επίσης μειώνει το μέγεθος) χρησιμοποιώντας μαλακό εξώφυλλο υψηλής ποιότητας. Τέλος, το στοιχείο (4) περικλείει ένα μεγάλο αριθμό αλλαγών στην παρουσίαση. Μερικές από αυτές είναι σχετικά μικρές. Για παράδειγμα, οι επικεφαλίδες των ενοτήτων είναι πιο συχνές, όπως και τα ιστορικά ανέκδοτα. Πιο σημαντική όμως είναι η οργάνωση του βιβλίου σε τέσσερα κύρια μέρη: Μέρος I: Καλύπτει εισαγωγική ύλη: (1) εισαγωγή στη σχεδίαση και την υλοποίηση των γλωσσών, (2) σύνταξη των γλωσσών προγραμματισμού, (3) ονόματα, εμβέλειες, και δεσμεύσεις, (4) σημασιολογική ανάλυση, και (5) αρχιτεκτονική του τελικού υπολογιστή. Το δεύτερο και το πέμπτο από τα παραπάνω εστιάζονται σε μεγάλο βαθμό σε θέματα υλοποίησης. Το πρώτο και το τρίτο είναι ανάμικτα. Το τρίτο κάνει μια εισαγωγή σε βασικές έννοιες της σχεδίασης γλωσσών. Μέρος II: Συνεχίζει την κάλυψη βασικών εννοιών: (6) ροή ελέγχου, (7) τύποι δεδομένων, (8) υπορουτίνες και αφαίρεση ελέγχου, και (9) αφαίρεση δεδομένων και αντικειμενοστρέφεια. Το τελευταίο από τα παραπάνω μεταφέρθηκε από τη θέση που είχε (αργότερα στην ύλη) στην ΠΓΠ-1ε, προκειμένου να αναδειχθεί ο κεντρικός ρόλος του αντικειμενοστρεφούς προγραμματισμού στο σύγχρονο υπολογισμό. Μέρος III: Στρέφεται σε εναλλακτικά μοντέλα προγραμματισμού: (10) συναρτησιακές γλώσσες, (11) λογικές γλώσσες, (12) ταυτοχρονισμός, και (13) γλώσσες σεναρίων. Οι συναρτησιακές και οι λογικές γλώσσες μοιράζονταν ένα κεφάλαιο της ΠΓΠ-1ε. Μέρος IV: Επιστρέφει στην υλοποίηση των γλωσσών: (14) δημιουργία εκτελέσιμου προγράμματος (παραγωγή κώδικα, συμβολομετάφραση, και σύνδεση), και (15) βελτίωση (βελτιστοποίηση) κώδικα. Συνοδευτικό CD με ύλη στην αγγλική γλώσσα (ΠΓΠ CD) Για να μειωθεί το μέγεθος αυτού του βιβλίου, να χωρέσει νέα ύλη, και να μπορούν οι σπουδαστές να επικεντρώνονται στα βασικά όταν ξεφυλλίζουν το βιβλίο, περίπου 250 σελίδες (στην αγγλική γλώσσα) πιο προηγμένης ύλης ή ύλης περιφερειακής θεματολογίας έχουν μεταφερθεί στο συνοδευτικό CD. Στο μεγαλύτερο μέρος της (όχι όμως αποκλειστικά), αυτή η ύλη αποτελείται από τις ενότητες που είχαν χαρακτηριστεί ως προχωρημένες ή προαιρετικές στην ΠΓΠ-1ε. Η σημαντικότερη μετακίνηση ύλης είναι αυτή ολόκληρου του κεφαλαίου της βελτίωσης κώδικα (15). Το υπόλοιπο της ύλης που μετακινήθηκε αποτελείται από διάσπαρτες, μικρότερες ενότητες. Για κάθε τέτοια ενότητα, υπάρχει στο κείμενο του βιβλίου μια μικρή εισαγωγή και μια παράγραφος Σε περισσότερο βάθος που συνοψίζει την ύλη που περιέχεται στο CD. Σημειώστε ότι η μεταφορά αυτής της ύλης στο CD δεν σημαίνει ότι αυτή έχει μειωμένη τεχνική σημασία. Οφείλεται απλώς στην ύπαρξη περισσότερης ύλης που αξίζει να καλυφθεί, από όση μπορεί να χωρέσει σε έναν τόμο ή σε ένα μόνο προπτυχιακό μάθημα.
24 Πρόλογος του συγγραφέα Πρόθεσή μου είναι να παραμείνει στο έντυπο βιβλίο η ύλη που είναι πιθανό να καλύπτεται από την πλειοψηφία των σχετικών μαθημάτων. Ειδικά πλαίσια Σχεδίαση & υλοποίηση Η ΠΓΠ-1ε έδινε αρκετή έμφαση στους τρόπους με τους οποίους η σχεδίαση των γλωσσών περιορίζει τις επιλογές της υλοποίησης, και στους τρόπους με τους οποίους οι αναμενόμενες υλοποιήσεις έχουν επηρεάσει τη σχεδίαση των γλωσσών. Σε αυτή την έκδοση, υπάρχουν περισσότερα από 120 ειδικά πλαίσια που κάνουν αυτές τις συνδέσεις ακόμη πιο σαφείς. Μπορείτε να βρείτε μια λεπτομερέστερη εισαγωγή σε αυτά τα ειδικά πλαίσια στη σελίδα 39 (Κεφάλαιο 1). Ένας αριθμημένος κατάλογός τους υπάρχει στο Παράρτημα Β. Παραδείγματα με αριθμούς και τίτλους Τα παραδείγματα σε αυτή την έκδοση είναι στενά συνυφασμένα με τη ροή της παρουσίασης. Για να βρίσκει πιο εύκολα ο αναγνώστης τα παραδείγματα, να θυμάται το περιεχόμενό τους, και να μπορεί να τα ξαναβρεί αργότερα, για κάθε παράδειγμα δίνεται ένας αριθμός και ένας τίτλος σε μια σημείωση στο περιθώριο της σελίδας. Υπάρχουν σχεδόν 900 τέτοια παραδείγματα σε όλο το βιβλίο και στο CD. Ένας αναλυτικός κατάλογος υπάρχει στο Παράρτημα Γ. Σχέδιο ασκήσεων Η ΠΓΠ-1ε περιείχε συνολικά 385 ερωτήσεις ελέγχου κατανόησης και 312 ασκήσεις, στο τέλος κάθε κεφαλαίου. Οι ερωτήσεις ελέγχου κατανόησης σε αυτή την έκδοση έχουν μεταφερθεί στο τέλος των αντίστοιχων ενοτήτων, πλησιέστερα στην ύλη που καλύπτουν, ώστε να είναι ευκολότερο να διαπιστώσει ο αναγνώστης αν έχει καταλάβει τις κεντρικές έννοιες. Ο συνολικός αριθμός αυτών των ερωτήσεων έχει σχεδόν διπλασιαστεί. Τα προβλήματα που παραμένουν στο τέλος των κεφαλαίων έχουν τώρα διαιρεθεί σε Ασκήσεις και Εξερευνήσεις. Οι πρώτες είναι λίγο-πολύ απλές, αν και δυσκολότερες από τις ερωτήσεις ελέγχου κατανόησης στο τέλος κάθε ενότητας είναι κατάλληλες για εργασίες στο σπίτι ή για μικρές εργασίες προς παράδοση. Οι ερωτήσεις εξερεύνησης είναι πιο ανοιχτές, απαιτούν έρευνα στον Παγκόσμιο Ιστό ή στη βιβλιοθήκη, σημαντικά περισσότερο χρόνο για την επίλυσή τους, ή απαιτούν να αναπτύξει ο σπουδαστής μια υποκειμενική άποψη. Ο συνολικός αριθμός των ερωτήσεων έχει αυξηθεί, από τις περίπου 300 της ΠΓΠ-1ε, σε περισσότερες από 500 σε αυτή την έκδοση. Οι λύσεις των ασκήσεων (όχι όμως των εξερευνήσεων) είναι διαθέσιμες σε καταχωρισμένους διδάσκοντες σε μια ιστοσελίδα προστατευόμενη με κωδικό πρόσβασης: www.mkp.com/companions/0126339511/. Πώς να χρησιμοποιήσετε το βιβλίο Η Πραγματολογία των γλωσσών προγραμματισμού καλύπτει σχεδόν όλη την ύλη των ενοτήτων γνώσης (knowledge units) που σχετίζονται με τις γλώσσες προγραμματισμού, όπως αυτές ορίζονται στο πρόγραμμα σπουδών της επιστήμης των υπολογιστών του 2001 (Computing Curricula 2001) [CR01]. Το βιβλίο είναι ιδανικό για τη διδασκαλία του μοντέλου μαθήματος CS 341 Σχεδίαση γλωσσών προγραμματισμού (Programming Language Design), και μπορεί επίσης να χρησιμοποιηθεί για το μάθημα CS 340 Κατασκευή μεταγλωττιστών (Compiler Construction) ή CS 343 Υποδείγματα προγραμματισμού (Programming Paradigms). Περιέχει ένα αρκετά μεγάλο μέρος της ύλης των μαθημάτων CS 344 Συναρτησιακός προγραμματισμός (Functional Programming) και CS 346 Γλώσσες σεναρίων (Scripting Languages). Η Εικόνα 1 δείχνει διάφορες πιθανές διαδρομές για την ανάγνωση αυτού του βιβλίου.
Πρόλογος του συγγραφέα 25 Εικόνα 1 Διαδρομές ανάγνωσης του βιβλίου. Οι πιο σκούρες περιοχές δείχνουν τις πρόσθετες ενότητες με την ένδειξη Σε περισσότερο βάθος στο ΠΓΠ CD. Σε κάθε διαδρομή ανάγνωσης αναγράφονται οι αριθμοί των ενοτήτων του έντυπου βιβλίου (όχι στο ΠΓΠ CD) που λείπουν. Για προσωπική μελέτη ή για ένα μάθημα που εκτείνεται σε ολόκληρο το ακαδημαϊκό έτος, (διαδρομή F στην Εικόνα 1), σας συνιστώ να διαβάσετε ολόκληρο το βιβλίο, από την αρχή μέχρι το τέλος, ανατρέχοντας και στο ΠΓΠ CD όταν συναντάτε μια ενότητα Σε περισσότερο βάθος. Το εξαμηνιαίο μάθημα που διδάσκεται στο Πανεπιστήμιο του Rochester (διαδρομή R), για το οποίο αρχικά γράφηκε αυτό το βιβλίο, καλύπτει επίσης το μεγαλύτερο μέρος του βιβλίου αλλά αφήνει έξω τις περισσότερες από τις ενότητες του CD, όπως επίσης και την ανοδική συντακτική ανάλυση (2.3.3), τη μεταβίβαση μηνυμάτων (12.4), τα σενάρια Παγκόσμιου Ιστού (13.3), και το μεγαλύτερο μέρος του Κεφαλαίου 14 (Δημιουργία εκτελέσιμου προγράμματος). Μερικά κεφάλαια (2, 4, 5, 14, 15) δίνουν περισσότερη έμφαση από άλλα σε θέματα υλοποίησης. Αυτά μπορούν να αλλάξουν σειρά μέχρι κάποιο βαθμό με τα κεφάλαια που σχετίζονται περισσότερο με τη σχεδίαση γλωσσών, είναι όμως σημαντικό το Κεφάλαιο 5 ή το ισοδύναμό του να καλυφθεί πριν από τα Κεφάλαια 6 έως 9. Πολλοί σπουδαστές θα είναι ήδη εξοικειωμένοι με κάποια από την ύλη του Κεφαλαίου 5, επειδή πιθανόν έχουν παρακολουθήσει κάποιο μάθημα οργάνωσης υπολογιστών. Στην περίπτωση αυτή, μπορούν να διαβάσουν αυτό το κεφάλαιο γρήγορα για επανάληψη. Μερικοί σπουδαστές μπορεί επίσης να είναι εξοικειωμένοι με την ύλη του Κεφαλαίου 2, επειδή πιθανόν έχουν παρακολουθήσει κάποιο μάθημα θεωρίας αυτομάτων. Σε αυτή την περίπτωση, μπορούν να διαβάσουν το μεγαλύτερο μέρος αυτού του κεφαλαίου γρήγορα, μένοντας μόνο σε πρακτικά ζητήματα όπως η ανάνηψη από συντακτικά σφάλματα, ή οι διαφορές ανάμεσα σε ένα λεκτικό αναλυτή και σε ένα κλασικό πεπερασμένο αυτόματο. Ένα παραδοσιακό μάθημα γλωσσών προγραμματισμού (διαδρομή P στην Εικόνα 1) μπορεί να αφήσει εκτός όλη τη λεκτική και τη συντακτική ανάλυση, καθώς και όλη την ύλη των Κεφαλαίων 4 και 5. Επίσης, θα μειώσει την έμφαση στα ζητήματα υλοποίησης σε όλο το κείμενο του βιβλίου. Αντί γι αυτά, θα μπορούσε να προσθέσει ενότητες σχετικές με τη σχεδίαση των γλωσσών από το CD, όπως το σύστημα τύπων της ML (7.2.4), η πολλαπλή κληρονομικότητα (9.5), η Smalltalk (9.6.1), ο λογισμός λάμβδα (10.6), και ο κατηγορηματικός λογισμός (11.3).
26 Πρόλογος του συγγραφέα Η ΠΓΠ έχει επίσης χρησιμοποιηθεί σε ορισμένα πανεπιστημιακά ιδρύματα για εισαγωγικά μαθήματα στην κατασκευή μεταγλωττιστών (διαδρομή C στην Εικόνα 1). Η συνηθισμένη ύλη ενός τέτοιου μαθήματος αφήνει εκτός το περισσότερο από το Μέρος III (Κεφάλαια 10 έως 13), και δε δίνει έμφαση στα ζητήματα που σχετίζονται με τη σχεδίαση γλωσσών στο υπόλοιπο βιβλίο. Στη θέση τους, περιέχει όλη την ύλη που αφορά τη λεκτική και συντακτική ανάλυση, τα Κεφάλαια 14 και 15, και ένα διαφορετικό μίγμα πρόσθετων ενοτήτων από το CD. Για ένα πανεπιστημιακό ίδρυμα που υιοθετεί το σύστημα των τριμήνων, μια ελκυστική προσέγγιση είναι να παρέχει ένα εισαγωγικό μάθημα ενός τριμήνου και δύο προαιρετικά επόμενα μαθήματα (διαδρομή Q στην Εικόνα 1). Το εισαγωγικό τρίμηνο μπορεί να καλύπτει τις κύριες ενότητες (όχι του CD) των Κεφαλαίων 1, 3, 6, και 7, συν τα πρώτα μισά των Κεφαλαίων 2 και 8. Ένα επόμενο τριμηνιαίο μάθημα με προσανατολισμό στις γλώσσες θα μπορούσε να καλύπτει το υπόλοιπο του Κεφαλαίου 8, όλο το Μέρος III, τις ενότητες στο CD των Κεφαλαίων 6 έως 8, και πιθανόν πρόσθετο υλικό για τυπική σημασιολογία, συστήματα τύπων, ή άλλα σχετικά αντικείμενα. Ένα επόμενο τριμηνιαίο μάθημα με προσανατολισμό στους μεταγλωττιστές θα μπορούσε να καλύπτει το υπόλοιπο του Κεφαλαίου 2, τα Κεφάλαια 4 5, και 14 15, τις ενότητες στο CD των Κεφαλαίων 3 και 8 9, και ίσως πρόσθετο υλικό για αυτόματη παραγωγή κώδικα, σύγχρονες επιθετικές μεθόδους βελτίωσης κώδικα, προγραμματιστικά εργαλεία, κ.ο.κ. Οποιαδήποτε διαδρομή και αν ακολουθήσει για την ανάγνωση του βιβλίου, υποτίθεται ότι ο μέσος αναγνώστης έχει ήδη σημαντική πείρα σε τουλάχιστον μία προστακτική γλώσσα. Ποια είναι αυτή δεν έχει ιδιαίτερη σημασία. Τα παραδείγματα προέρχονται από μια ποικιλία γλωσσών, αλλά περιέχουν πάντα αρκετά σχόλια και συζήτηση ώστε οι αναγνώστες που δεν έχουν προηγούμενη πείρα να μπορούν εύκολα να τα καταλάβουν. Εισαγωγές της μίας παραγράφου για περίπου 50 διαφορετικές γλώσσες παρέχονται στο Παράρτημα Α. Οι αλγόριθμοι, όποτε χρειάζεται, παρουσιάζονται σε μορφή άτυπου ψευδοκώδικα που είναι γενικά αυτο-επεξηγηματικός. 1 Για τον κώδικα σε πραγματικές γλώσσες προγραμματισμού χρησιμοποιείται μη αναλογική γραμματοσειρά γραφομηχανής (typewriter). Ο ψευδοκώδικας γράφεται με γραμματοσειρά sans-serif. Πρόσθετο υλικό Εκτός από τις πρόσθετες ενότητες του κειμένου, το ΠΓΠ CD περιέχει επίσης μια ποικιλία άλλων πόρων (στην αγγλική γλώσσα): Συνδέσμους προς εγχειρίδια αναφοράς (reference manuals) γλωσσών προγραμματισμού και εκπαιδευτικά βοηθήματα (tutorials), στον Παγκόσμιο Ιστό Συνδέσμους προς μεταγλωττιστές και διερμηνείς ανοιχτού πηγαίου κώδικα Τον πλήρη κώδικα όλων των μη τετριμμένων παραδειγμάτων αυτού του βιβλίου (περισσότερα από 300 αρχεία κώδικα) Μια μηχανή αναζήτησης τόσο για το κυρίως κείμενο όσο και για το κείμενο στο CD Περισσότεροι πόροι (στην αγγλική γλώσσα) είναι διαθέσιμοι στη διεύθυνση www.mkp. com/companions/0126339511/ (την οποία καλό είναι να επισκέπτεστε περιοδικά). Για τους διδάσκοντες που χρησιμοποιούν αυτό το βιβλίο, μια ιστοσελίδα προστατευμένη με κωδικό πρόσβασης παρέχει πρόσβαση σε Επεξεργάσιμο πηγαίο κώδικα PDF όλων των εικόνων του βιβλίου Επεξεργάσιμες διαφάνειες του PowerPoint Λύσεις των περισσότερων ασκήσεων Προτάσεις για μεγαλύτερες εργασίες 1 Σ.τ.Μ.: Ο ψευδοκώδικας έχει διατηρηθεί στην αγγλική γλώσσα.
2 Σύνταξη των γλωσσών προγραμματισμού ΠΑΡΑΔΕΙΓΜΑ 2.1 Σύνταξη των αραβικών αριθμών Σε αντίθεση με τις φυσικές γλώσσες όπως τα Ελληνικά ή τα Αγγλικά, οι γλώσσες των υπολογιστών πρέπει να είναι ακριβείς. Τόσο η μορφή τους (σύνταξη) όσο και το νόημά τους (σημασιολογία) πρέπει να καθορίζονται χωρίς αμφισημίες, ώστε οι προγραμματιστές και οι υπολογιστές να μπορούν να καταλαβαίνουν τι πρέπει να κάνει ένα πρόγραμμα. Για να εξασφαλίζεται ο απαιτούμενος βαθμός ακρίβειας, οι σχεδιαστές και οι υλοποιητές των γλωσσών χρησιμοποιούν τυπικές συντακτικές και σημασιολογικές σημειογραφίες. Για να διευκολύνουμε τη μελέτη των δυνατοτήτων των γλωσσών στα επόμενα κεφάλαια, θα ασχοληθούμε πρώτα με αυτές τις σημειογραφίες: τη σύνταξη σε αυτό το κεφάλαιο και τη σημασιολογία στο Κεφάλαιο 4. Θα χρησιμοποιήσουμε ως πρώτο παράδειγμα τη σύνταξη των Αραβικών αριθμών. Οι αριθμοί αυτοί αποτελούνται από ψηφία, που μπορούμε να απαριθμήσουμε ως εξής ( σημαίνει ή ): digit 0 1 2 3 4 5 6 7 8 9 Τα ψηφία (digits) είναι τα συντακτικά δομικά υλικά των αριθμών. Σύμφωνα με τη συνήθη σημειογραφία, λέμε ότι ένας φυσικός αριθμός αναπαρίσταται από μια (μη κενή) ακολουθία ψηφίων οποιουδήποτε μήκους, που αρχίζει με μη μηδενικό ψηφίο: non zero digit 1 2 3 4 5 6 7 8 9 natural number non zero digit digit * Εδώ το μετασύμβολο *, που ονομάζεται άστρο του Kleene, 1 δείχνει καμία ή περισσότερες επαναλήψεις του συμβόλου που βρίσκεται στα αριστερά του. Φυσικά, τα ψηφία είναι μόνο σύμβολα: κηλίδες από μελάνι στο χαρτί ή πίξελ στην οθόνη. Από μόνα τους δε μεταφέρουν κανένα νόημα. Προσθέτουμε σημασιολογία στα ψηφία όταν λέμε ότι αναπαριστούν τους φυσικούς αριθμούς από το μηδέν έως το εννέα, όπως αυτοί ορίζονται από τους μαθηματικούς. Εναλλακτικά, θα μπορούσαμε να πούμε ότι αναπαριστούν χρώματα, ή τις ημέρες της εβδομάδας σε ένα δεκαδικό ημερολόγιο. Αυτές θα ήταν διαφορετικές σημασιολογίες για την ίδια σύνταξη. Με παρόμοιο τρόπο, ορίζουμε τη σημασιολογία των φυσικών αριθμών αποδίδοντας στις ακολουθίες ψηφίων τη συνηθισμένη ερμηνεία του δεκαδικού συστήματος αρίθμησης. Παρόμοιοι συντακτικοί κανόνες και σημασιολογικές ερμηνείες μπορούν να οριστούν για τους ρητούς αριθμούς, τους πραγματικούς αριθμούς (περιορισμένης ακρίβειας), την αριθμητική, τις αναθέσεις τιμών, τον έλεγχο ροής, τις δηλώσεις, και γενικά για όλα τα στοιχεία που απαρτίζουν τις γλώσσες προγραμματισμού. Η διάκριση ανάμεσα στη σύνταξη και τη σημασιολογία είναι χρήσιμη τουλάχιστον για δύο λόγους. Πρώτον, διαφορετικές γλώσσες προγραμματισμού συχνά παρέχουν δυνατότητες με πολύ παρόμοια σημασιολογία αλλά πολύ διαφορετική σύνταξη. Γενικά, είναι 1 Ο Stephen Kleene (1909 1994), μαθηματικός στο Πανεπιστήμιο του Wisconsin, συνέβαλε στη θεμελίωση της θεωρίας υπολογισμού και για μεγάλο μέρος της ύλης που παρουσιάζεται στην Ενότητα 2.4. 65
66 Κεφάλαιο 2 Σύνταξη των γλωσσών προγραμματισμού πολύ ευκολότερο να μάθει κανείς μια καινούργια γλώσσα αν μπορεί να ξεχωρίσει τις κοινές (και μάλλον οικείες σε αυτόν) ιδέες που βρίσκονται κάτω από την άγνωστη σύνταξη. Δεύτερον, υπάρχουν πολύ αποδοτικοί και κομψοί αλγόριθμοι με τους οποίους ένας μεταγλωττιστής ή ένας διερμηνέας μπορεί να ανακαλύψει τη συντακτική δομή (αλλά όχι τη σημασιολογία!) ενός προγράμματος. Αυτοί οι αλγόριθμοι μπορούν να χρησιμοποιηθούν για να καθοδηγήσουν το υπόλοιπο της διαδικασίας μεταγλώττισης ή διερμηνείας. Σε αυτό το κεφάλαιο εστιάζουμε στη σύνταξη: πώς καθορίζουμε τους δομικούς κανόνες μιας γλώσσας προγραμματισμού και πώς ο μεταγλωττιστής καταλαβαίνει τη δομή ενός δεδομένου αρχικού προγράμματος. Αυτές οι δύο εργασίες ο καθορισμός των συντακτικών κανόνων και η διαπίστωση πώς (και κατά πόσο) η δομή ενός προγράμματος είναι σύμφωνη με αυτούς τους κανόνες είναι διακριτές. Η πρώτη ενδιαφέρει κυρίως τους προγραμματιστές, που θέλουν να γράφουν έγκυρα προγράμματα. Η δεύτερη ενδιαφέρει κυρίως τους μεταγλωττιστές, που χρειάζεται να αναλύουν αυτά τα προγράμματα. Η πρώτη εργασία βασίζεται στις κανονικές εκφράσεις (regular expressions) και τις γραμματικές χωρίς συμφραζόμενα (context-free grammars) που καθορίζουν πώς σχηματίζονται έγκυρα προγράμματα. Η δεύτερη εργασία βασίζεται σε λεκτικούς αναλυτές (scanners), ή λεξικογραφικούς αναλυτές, και σε συντακτικούς αναλυτές (parsers), που αναγνωρίζουν τη συντακτική δομή των προγραμμάτων. Εξετάζουμε την πρώτη εργασία στην Ενότητα 2.1 και τη δεύτερη στις Ενότητες 2.2 και 2.3. Στην Ενότητα 2.4 (κατά το μεγαλύτερο μέρος στο ΠΓΠ CD) εξετάζουμε σε μεγαλύτερο βάθος την τυπική θεωρία πίσω από τη λεκτική και τη συντακτική ανάλυση. Στη θεωρητική ορολογία, ο λεκτικός αναλυτής είναι ένα ντετερμινιστικό πεπερασμένο αυτόματο (ΝΠΑ) (deterministic finite automaton, DFA) που αναγνωρίζει τις λεκτικές μονάδες μιας γλώσσας προγραμματισμού. Ο συντακτικός αναλυτής είναι ένα ντετερμινιστικό αυτόματο στοίβας (ΑΣ) (push-down automaton, PDA) που αναγνωρίζει τη χωρίς συμφραζόμενα σύνταξη της γλώσσας. Αποδεικνύεται ότι λεκτικοί και συντακτικοί αναλυτές μπορούν να κατασκευαστούν αυτόματα από κανονικές εκφράσεις και γραμματικές χωρίς συμφραζόμενα, με τη χρήση εργαλείων όπως τα lex και yacc του Unix. 2 Ίσως πουθενά αλλού στην επιστήμη των υπολογιστών δεν είναι η σύνδεση μεταξύ θεωρίας και πράξης τόσο ορατή και συναρπαστική. 2.1 Περιγραφή της σύνταξης: κανονικές εκφράσεις και γραμματικές χωρίς συμφραζόμενα Η τυπική περιγραφή της σύνταξης χρειάζεται ένα σύνολο από κανόνες. Η πολυπλοκότητα (η εκφραστικότητα) της σύνταξης εξαρτάται από το είδος των κανόνων που χρησιμοποιούνται για την περιγραφή της. Αποδεικνύεται πως όλες οι λεκτικές μονάδες που μπορεί κανείς να φανταστεί είναι δυνατόν να κατασκευαστούν από μεμονωμένους χαρακτήρες με μόνο τρία είδη τυπικών κανόνων: τη συνένωση (concatenation) την εναλλαγή (alternation), δηλαδή την επιλογή από ένα πεπερασμένο σύνολο εναλλακτικών μορφών, και το επονομαζόμενο κλείσιμο του Kleene, δηλαδή την επανάληψη κατά αυθαίρετα πολλές φορές. Για να περιγράψουμε σχεδόν ό,τι άλλο μπορούμε να φανταστούμε σχετικά με τη σύνταξη, απαιτείται μόνο ένας ακόμη κανόνας: η αναδρομή, δηλαδή η δημιουργία μιας συντακτικής δομής από απλούστερες εμφανίσεις της ίδιας συντακτικής δομής. Οποιοδήποτε σύνολο συμβολοσειρών που μπορεί να οριστεί με τη χρήση των τριών πρώτων κανόνων ονομάζεται κανονικό σύνολο (regular set). Τα κανονικά σύνολα παράγονται από κανονικές εκφράσεις και αναγνωρίζονται από λεκτικούς αναλυτές. Κάθε σύνολο συμβολοσειρών που μπορεί να οριστεί αν προσθέσουμε και την αναδρομή ονομάζεται γλώσσα χωρίς συμφραζόμενα (context-free language, CFL). Οι γλώσσες χωρίς συμφραζόμενα παράγονται από γραμματικές χωρίς συμφραζόμενα και αναγνωρίζονται από συντα- 2 Σε πολλούς υπολογιστές, τα lex και yacc έχουν αντικατασταθεί από τα εργαλεία GNU flex και bison. Αυτά τα εργαλεία δεν είναι εμπορικά, έχουν αναπτυχθεί ανεξάρτητα, και είναι διαθέσιμα δωρεάν από το Ίδρυμα Ελεύθερου Λογισμικού (Free So ware Foundation) στη διεύθυνση www.gnu.org/software. Παρέχουν ένα υπερσύνολο των λειτουργιών των lex και yacc.
2.1 Περιγραφή της σύνταξης 67 κτικούς αναλυτές. (Η ορολογία μπορεί μερικές φορές να προκαλέσει σύγχυση. Το νόημα της λέξης γλώσσα ποικίλει σημαντικά, ανάλογα με το αν μιλούμε για τυπικές γλώσσες [π.χ., κανονικές ή χωρίς συμφραζόμενα] ή για γλώσσες προγραμματισμού. Μια τυπική γλώσσα είναι απλώς ένα σύνολο συμβολοσειρών, χωρίς συνοδευτική σημασιολογία.) 2.1.1 Λεκτικές μονάδες και κανονικές εκφράσεις Οι λεκτικές μονάδες είναι τα βασικά δομικά υλικά των προγραμμάτων. Περιλαμβάνουν τις λέξεις-κλειδιά, τα αναγνωριστικά, τους αριθμούς, και πολλά είδη συμβόλων. Η Pascal, που είναι μια σχετικά απλή γλώσσα, έχει 64 είδη λεκτικών μονάδων: 21 σύμβολα (+, -, ;, :=,.., κλπ.), 35 λέξεις κλειδιά (begin, end, div, record, while, κλπ.), ακέραιες σταθερές (π.χ., 137), πραγματικές σταθερές κινητής υποδιαστολής (π.χ., 6.022e23), σταθερούς χαρακτήρες και συμβολοσειρές σε εισαγωγικά (π.χ., snerk ), αναγνωριστικά (MyVariable, YourType, maxint, readln, κλπ., 39 από τα οποία είναι προκαθορισμένα), και δύο διαφορετικά είδη σχολίων. Τα πεζά και τα κεφαλαία γράμματα στα αναγνωριστικά και τις λέξεις-κλειδιά θεωρούνται διαφορετικά σε κάποιες γλώσσες (π.χ., στις Modula-2/3 και στη C και τους απογόνους της), και ταυτόσημα σε άλλες (π.χ., στην Ada, την Common Lisp, τη Fortran 90, και την Pascal). Έτσι, foo, Foo και FOO αναπαριστούν το ίδιο αναγνωριστικό στην Ada αλλά διαφορετικά αναγνωριστικά στη C. Η Modula-2 και η Modula-3 απαιτούν οι λέξειςκλειδιά και τα προκαθορισμένα αναγνωριστικά να γράφονται με κεφαλαία γράμματα, ενώ η C και οι απόγονοί της με πεζά. Μερικές γλώσσες (με πιο αξιοσημείωτες τη Modula- 3 και τη Standard Pascal) επιτρέπουν μόνο γράμματα και ψηφία στα αναγνωριστικά. Οι περισσότερες (μεταξύ των οποίων και πολλές υλοποιήσεις της Pascal) επιτρέπουν το χαρακτήρα υπογράμμισης (underscore). Μερικές (με πιο αξιοσημείωτη τη Lisp) επιτρέπουν μια ποικιλία πρόσθετων χαρακτήρων. Σε κάποιες γλώσσες (π.χ., στην Java, τη C, και τη Modula-3) υπάρχουν καθιερωμένες συμβάσεις για τη χρήση των πεζών και των κεφαλαίων γραμμάτων στα ονόματα. 3 Με την παγκοσμιοποίηση των υπολογιστών, τα σύνολα χαρακτήρων που δε βασίζονται στο λατινικό αλφάβητο αποκτούν ολοένα αυξανόμενη σημασία. Πολλές σύγχρονες γλώσσες, όπως η C99, η C++, η Ada 95, η Java, η C#, και η Fortran 2003, διαθέτουν ρητή υποστήριξη για σύνολα χαρακτήρων πολλών byte (multibyte character sets), που γενικά βασίζονται στα διεθνή πρότυπα Unicode και ISO/IEC 10646. Οι περισσότερες σύγχρονες γλώσσες προγραμματισμού επιτρέπουν τη χρήση μη λατινικών χαρακτήρων μέσα σε σχόλια και συμβολοσειρές, ενώ ο αριθμός των γλωσσών που επιτρέπουν τέτοιους χαρακτήρες και στα αναγνωριστικά αυξάνεται συνεχώς. Οι συμβάσεις για τη φορητότητα μεταξύ διαφορετικών συνόλων χαρακτήρων και για την τοπική προσαρμογή (localization) σε ένα συγκεκριμένο σύνολο χαρακτήρων μπορούν να είναι εξαιρετικά πολύπλοκες, ιδιαίτερα όταν απαιτούνται διάφορες μορφές αναδρομικής συμβατότητας με προγενέστερο κώδικα (το συνοδευτικό έγγραφο C99 Rationale αφιερώνει πέντε σελίδες σε αυτό το θέμα [Int99, σελίδες 19 23]). Σε αυτό το βιβλίο γενικά αγνοούμε τέτοιου είδους ζητήματα. Ορισμένες υλοποιήσεις γλωσσών θέτουν περιορισμούς στο μέγιστο μήκος των αναγνωριστικών, αλλά οι περισσότερες αποφεύγουν τέτοιους περιττούς περιορισμούς. Οι περισσότερες σύγχρονες γλώσσες έχουν επίσης λίγο-πολύ ελεύθερη μορφή, δηλαδή τα προγράμματα είναι απλώς ακολουθίες λεκτικών μονάδων: αυτό που έχει σημασία είναι η σειρά με την οποία εμφανίζονται οι λεκτικές μονάδες, όχι η φυσική τους θέση μέσα σε μια τυπωμένη γραμμή ή σελίδα. Τα λευκά διαστήματα (white space) κενά διαστήματα, στηλοθέτες, χαρακτήρες επαναφοράς κεφαλής και αλλαγής γραμμής ή σελίδας μεταξύ λεκτικών μονάδων συνήθως αγνοούνται, με εξαίρεση όταν διαχωρίζουν μια λεκτική μονάδα από την επόμενη. Σε αυτούς τους κανόνες υπάρχουν μερικές εξαιρέσεις. Μερικές υλοποιήσεις γλωσσών περιορίζουν το μέγιστο μήκος μιας γραμμής, ώστε ο μεταγλωττιστής να μπορεί να αποθηκεύσει την τρέχουσα γραμμή σε μια προσωρινή μνήμη (buffer) 3 Για λόγους συνέπειας, σε αυτό το βιβλίο δε χρησιμοποιούμε πάντα αυτές τις συμβάσεις. Τα περισσότερα παραδείγματα ακολουθούν τη συνηθισμένη πρακτική των προγραμματιστών της C, σύμφωνα με την οποία, αν ένα όνομα αποτελείται από περισσότερες υπολέξεις, αυτές χωρίζονται με χαρακτήρες υπογράμμισης.
68 Κεφάλαιο 2 Σύνταξη των γλωσσών προγραμματισμού σταθερού μήκους. Οι διάλεκτοι της Fortran πριν από τη Fortran 90 χρησιμοποιούν σταθερή μορφή με 72 χαρακτήρες ανά γραμμή (το πλάτος μιας χάρτινης διάτρητης κάρτας στην οποία κάποτε αποθηκεύονταν τα προγράμματα), και διαφορετικές στήλες σε αυτή τη γραμμή δεσμευμένες για διαφορετικούς σκοπούς. Οι αλλαγές γραμμών χρησιμοποιούνται για το διαχωρισμό εντολών σε μερικές άλλες γλώσσες, όπως οι Haskell, Occam, SR, Tcl, και Python. Οι Haskell, Occam, και Python επίσης δίνουν ειδική σημασία στη στοίχιση (indentation). Για παράδειγμα, το σώμα ενός βρόχου αποτελείται ακριβώς από τις επόμενες εντολές που είναι στοιχημένες πιο μέσα από την επικεφαλίδα του βρόχου. Για να καθορίσουμε τις λεκτικές μονάδες, χρησιμοποιούμε σημειογραφία κανονικών εκφράσεων. Μια κανονική έκφραση είναι ένα από τα εξής: ΠΑΡΑΔΕΙΓΜΑ 2.2 Σύνταξη των αριθμών στην Pascal 1. Ένας χαρακτήρας 2. Η κενή συμβολοσειρά, που συμβολίζεται με є 3. Δύο κανονικές εκφράσεις η μία δίπλα στην άλλη, που αναπαριστούν οποιαδήποτε συμβολοσειρά μπορεί να παραχθεί από την πρώτη ακολουθούμενη από (συνενωμένη με) οποιαδήποτε συμβολοσειρά μπορεί να παραχθεί από τη δεύτερη 4. Δύο κανονικές εκφράσεις διαχωρισμένες με μια κατακόρυφη γραμμή ( ), που σημαίνουν οποιαδήποτε συμβολοσειρά μπορεί να παραχθεί από την πρώτη ή οποιαδήποτε συμβολοσειρά μπορεί να παραχθεί από τη δεύτερη 5. Μια κανονική έκφραση ακολουθούμενη από το άστρο του Kleene, που σημαίνει τη συνένωση καμίας ή περισσότερων συμβολοσειρών που έχουν παραχθεί από την έκφραση πριν από το άστρο Οι παρενθέσεις χρησιμοποιούνται για να αποφεύγονται οι αμφισημίες, όταν δεν είναι σαφές πού αρχίζουν και πού τελειώνουν οι διάφορες υποεκφράσεις. 4 Επιστρέφοντας στο παράδειγμα της Pascal, οι αριθμητικές σταθερές μπορούν να παραχθούν από τις εξής κανονικές εκφράσεις. 5 digit 0 1 2 3 4 5 6 7 8 9 unsigned integer digit digit * unsigned number unsigned integer ( (. unsigned integer ) є ) ( ( ( e E ) ( + - є ) unsigned integer ) є ) Για να παραγάγουμε μια έγκυρη συμβολοσειρά, διατρέχουμε την κανονική έκφραση από αριστερά προς τα δεξιά, επιλέγοντας μεταξύ εναλλακτικών σε κάθε κατακόρυφη γραμμή, και επιλέγοντας πλήθος επαναλήψεων σε κάθε άστρο του Kleene. Σε κάθε επανάληψη μπορούμε να κάνουμε διαφορετικές επιλογές στις κατακόρυφες γραμμές, παράγοντας έτσι διαφορετικές υποσυμβολοσειρές. Σημειώστε ότι, παρόλο που επιτρέπουμε σε ΣΧΕΔΙΑΣΗ & ΥΛΟΠΟΙΗΣΗ Περιορισμοί μορφοποίησης Οι περιορισμοί της μορφοποίησης που οφείλονται σε θέματα υλοποίησης όπως στην περίπτωση των κανόνων της Fortran 77 και των προγόνων της, που γράφηκαν με βάση τη μορφή των διάτρητων καρτών τείνουν να γίνουν ανεπιθύμητοι και αναχρονιστικοί καθώς βελτιώνονται οι τεχνικές υλοποίησης. Με δεδομένη την τάση ορισμένων επεξεργαστών κειμένου να συμπληρώνουν ή να μορφοποιούν αυτόματα το κείμενο, οι κανόνες στοίχισης και αλλαγής γραμμής γλωσσών όπως οι Haskell, Occam, και Python είναι κάπως αμφιλεγόμενοι. 4 Κάποιοι συγγραφείς αναπαριστούν την κενή συμβολοσειρά με λ. Κάποιοι άλλοι χρησιμοποιούν μια τελεία (.), αντί για την παράθεση, για να συμβολίσουν τη συνένωση. Άλλοι χρησιμοποιούν το σύμβολο συν (+), αντί για την κατακόρυφη γραμμή, για να συμβολίσουν την εναλλαγή. 5 Οι αριθμητικές σταθερές σε πολλές γλώσσες είναι σημαντικά πιο πολύπλοκες. Η Java, για παράδειγμα, υποστηρίζει ακέραιες σταθερές των 32 και των 64 bit στο δεκαδικό, το οκταδικό, και το δεκαεξαδικό σύστημα αρίθμησης.
2.1 Περιγραφή της σύνταξης 69 μεταγενέστερους ορισμούς να χρησιμοποιούν άλλους προγενέστερους, κανένας ορισμός δε χρησιμοποιεί τον εαυτό του. Τέτοιοι αναδρομικοί ορισμοί είναι το διακριτικό γνώρισμα των γραμματικών χωρίς συμφραζόμενα, που περιγράφονται στην Ενότητα 2.1.2. Πολλοί αναγνώστες θα είναι εξοικειωμένοι με τις κανονικές εκφράσεις από την οικογένεια εργαλείων grep του Unix, από τις λειτουργίες αναζήτησης διαφόρων διορθωτών κειμένου (π.χ., emacs), ή από γλώσσες σεναρίων και εργαλεία όπως η Perl, η Python, η Ruby, η awk, και η sed. Τα περισσότερα από αυτά τα εργαλεία παρέχουν ένα πλούσιο σύνολο επεκτάσεων στη σημειογραφία των κανονικών εκφράσεων. Κάποιες επεκτάσεις, όπως η συντομογραφία για καμία ή μία επαναλήψεις ή οτιδήποτε εκτός από κενό διάστημα, δεν αλλάζουν την εκφραστική ισχύ της σημειογραφίας. Άλλες, όπως η δυνατότητα απαίτησης μιας δεύτερης εμφάνισης αργότερα στη συμβολοσειρά εισόδου, της ίδιας ακολουθίας χαρακτήρων που εμφανίζεται σε προηγούμενο σημείο, αυξάνουν την εκφραστική ισχύ της σημειογραφίας ώστε να μην περιορίζεται στην παραγωγή κανονικών συνόλων. Άλλες επεκτάσεις έχουν σχεδιαστεί όχι για να αυξήσουν την εκφραστικότητα της σημειογραφίας αλλά μάλλον για να τη συνδέσουν με άλλες ευκολίες των γλωσσών. Για παράδειγμα, σε πολλά εργαλεία μπορεί κάποιος να σημειώσει μια κανονική έκφραση με τέτοιο τρόπο ώστε, όταν μια συμβολοσειρά ταυτίζεται με αυτή, τα περιεχόμενα αυτής της συμβολοσειράς να ανατίθενται σε κάποια επώνυμη τοπική μεταβλητή. Θα επιστρέψουμε σε αυτά τα ζητήματα στην Ενότητα 13.4.2, στο πλαίσιο των γλωσσών σεναρίων. ΠΑΡΑΔΕΙΓΜΑ 2.3 2.1.2 Γραμματικές χωρίς συμφραζόμενα Οι κανονικές εκφράσεις επαρκούν για τον ορισμό λεκτικών μονάδων. Είναι όμως ανίσχυρες να καθορίσουν ένθετες (nested) δομές, που έχουν κεντρική σημασία για τις γλώσσες προγραμματισμού. Θεωρήστε για παράδειγμα τη δομή μιας αριθμητικής έκφρασης: Συντακτική ένθεση στις εκφράσεις expr id number - expr ( expr ) expr op expr op + - * / Εδώ, η δυνατότητα να οριστεί μια δομή συναρτήσει του εαυτού της έχει μεγάλη σημασία. Μεταξύ άλλων, μας επιτρέπει να εξασφαλίζουμε ότι οι αριστερές και οι δεξιές παρενθέσεις ταιριάζουν, κάτι που δεν μπορεί να εξασφαλιστεί με κανονικές εκφράσεις (δείτε την Ενότητα 2.4.3 για περισσότερες λεπτομέρειες). Οι κανόνες σε μια γραμματική χωρίς συμφραζόμενα ονομάζονται συντακτικοί κανόνες, ή απλώς κανόνες. 6 Τα σύμβολα στο αριστερό μέλος των κανόνων ονομάζονται μεταβλητές ή μη τερματικά σύμβολα (nonterminals). Μπορούν να υπάρχουν περισσότεροι κανόνες με το ίδιο σύμβολο στο αριστερό μέλος. Τα σύμβολα από τα οποία αποτελούνται οι συμβολοσειρές που παράγονται από τη γραμματική ονομάζονται τερματικά σύμβολα (terminals) και εμφανίζονται εδώ με γραμματοσειρά γραφομηχανής. Δεν επιτρέπεται να υπάρχουν στο αριστερό μέλος κανενός κανόνα. Σε μια γλώσσα προγραμματισμού, τα τερματικά σύμβολα της γραμματικής χωρίς συμφραζόμενα είναι οι λεκτικές μονάδες της γλώσσας. Ένα από τα μη τερματικά σύμβολα, συνήθως αυτό που βρίσκεται στο αριστερό μέλος του πρώτου κανόνα, ονομάζεται αρχικό σύμβολο (start symbol). Αυτό ονομάζει τη συντακτική δομή που ορίζεται από ολόκληρη τη γραμματική. Η σημειογραφία των γραμματικών χωρίς συμφραζόμενα ονομάζεται συχνά μορφή Backus-Naur (Backus-Naur Form, BNF), προς τιμή του John Backus και του Peter Naur, που την επινόησαν για τον ορισμό της γλώσσας προγραμματισμού Algol 60 [NBB + 63]. 7 Αυστηρά, το άστρο του Kleene και η χρήση παρενθέσεων στις κανονικές εκφράσεις δεν 6 Σ.τ.Μ.: Στο πρωτότυπο κείμενο χρησιμοποιείται η λέξη production για τους κανόνες μιας γραμματικής, που μεταφράζεται στα ελληνικά ως παραγωγή. Στην ελληνική μετάφραση προτιμήθηκε ο όρος συντακτικός κανόνας για την αποφυγή σύγχυσης, καθώς η λέξη παραγωγή χρησιμοποιήθηκε για τη μετάφραση της αγγλικής derivation. 7 Ο John Backus (1924 ), ήταν επίσης ο εφευρέτης της Fortran. Το μεγαλύτερο μέρος της επαγγελματικής του σταδιοδρομίας συνδέεται με την εταιρεία IBM, και ονομάστηκε IBM Fellow το 1987. Έλαβε το βραβείο Turing της ACM το 1977.
70 Κεφάλαιο 2 Σύνταξη των γλωσσών προγραμματισμού ΠΑΡΑΔΕΙΓΜΑ 2.4 Επεκτεταμένη BNF (EBNF) επιτρέπονται στην BNF, αλλά δεν αλλάζουν την εκφραστική ισχύ της σημειογραφίας και συχνά χρησιμοποιούνται για λόγους ευκολίας. Μερικές φορές, χρησιμοποιείται επίσης το συν του Kleene ( + ), που αναπαριστά μία ή περισσότερες εμφανίσεις του συμβόλου ή της ομάδας συμβόλων που βρίσκεται πριν από αυτό. 8 Όταν επεκτείνεται με αυτούς τους επιπλέον τελεστές, η σημειογραφία ονομάζεται συχνά επεκτεταμένη BNF (EBNF). Η δομή id list id (, id )* είναι συντομογραφία του id list id id list id list, id Κάτι ανάλογο μπορεί να γραφεί για το συν του Kleene. Η κατακόρυφη γραμμή είναι επίσης περιττή κατά μία έννοια, παρόλο που υποστηρίζεται στην αρχική BNF. Η δομή op + - * / μπορεί να θεωρηθεί ως συντομογραφία για την op + op - op * op / που γράφεται και ως εξής: op + - * / Πολλές λεκτικές μονάδες, όπως οι id και number παραπάνω, έχουν πολλές διαφορετικές γραφές (δηλαδή μπορούν να αναπαρασταθούν από πολλές δυνατές ακολουθίες χαρακτήρων). Αυτό είναι αδιάφορο για το συντακτικό αναλυτή, ο οποίος δε διακρίνει ένα αναγνωριστικό από ένα άλλο. Ο σημασιολογικός αναλυτής όμως τα διακρίνει, οπότε ο λεκτικός αναλυτής πρέπει να αποθηκεύει την ακριβή γραφή κάθε ενδιαφέρουσας λεκτικής μονάδας για μεταγενέστερη χρήση. 2.1.3 Παραγωγές και δένδρα συντακτικής ανάλυσης ΠΑΡΑΔΕΙΓΜΑ 2.5 Παραγωγή του slope * x + intercept Μια γραμματική χωρίς συμφραζόμενα μάς δείχνει πώς να δημιουργήσουμε μια συντακτικά έγκυρη ακολουθία τερματικών συμβόλων: Ξεκινούμε από το αρχικό σύμβολο. Επιλέγουμε έναν κανόνα με το αρχικό σύμβολο στο αριστερό μέλος. Αντικαθιστούμε το αρχικό σύμβολο με το δεξιό μέλος αυτού του κανόνα. Τώρα επιλέγουμε ένα οποιοδήποτε μη τερματικό σύμβολο A στη συμβολοσειρά που προέκυψε, επιλέγουμε έναν κανόνα P με το A στο αριστερό του μέλος, και αντικαθιστούμε το A με το δεξιό μέλος του P. Επαναλαμβάνουμε τη διαδικασία μέχρι να μην απομένει κανένα μη τερματικό σύμβολο. Ως παράδειγμα, μπορούμε να χρησιμοποιήσουμε τη γραμματική μας των εκφράσεων για να παραγάγουμε τη συμβολοσειρά slope * x + intercept : expr expr op expr expr op id 8 Ορισμένοι συγγραφείς χρησιμοποιούν άγκιστρα ({ }) για να αναπαραστήσουν καμία ή περισσότερες επαναλήψεις των συμβόλων ανάμεσά τους. Κάποιοι χρησιμοποιούν αγκύλες ([ ]) για να αναπαραστήσουν καμία ή μία εμφάνιση των συμβόλων ανάμεσά τους δηλαδή για να δείξουν ότι αυτά τα σύμβολα είναι προαιρετικά.
2.1 Περιγραφή της σύνταξης 71 Εικόνα 2.1 Δένδρο συντακτικής ανάλυσης για την έκφραση slope * x + intercept (γραμματική του Παραδείγματος 2.3). expr + id expr op expr + id expr op id + id expr * id + id id (slope) * id (x) + id (intercept) ΠΑΡΑΔΕΙΓΜΑ 2.6 Δένδρα συντακτικής ανάλυσης για την έκφραση slope * x + intercept Το μετασύμβολο σημαίνει ότι το δεξιό μέλος προέκυψε με τη χρήση ενός κανόνα για την αντικατάσταση κάποιου μη τερματικού συμβόλου στο αριστερό μέλος. Σε κάθε γραμμή έχουμε υπογραμμίσει το σύμβολο A που αντικαθίσταται στην επόμενη γραμμή. Μια σειρά αντικαταστάσεων που δείχνει πώς μπορεί να παραχθεί μια ακολουθία τερματικών συμβόλων ξεκινώντας από το αρχικό σύμβολο ονομάζεται παραγωγή (derivation). Κάθε ενδιάμεση συμβολοσειρά ονομάζεται προτασιακή μορφή (sentential form). Η τελική προτασιακή μορφή, που αποτελείται μόνο από τερματικά σύμβολα, ονομάζεται αποτέλεσμα (yield) της παραγωγής. Μερικές φορές παραλείπουμε τα ενδιάμεσα βήματα και γράφουμε expr * slope * x + intercept, όπου το μετασύμβολο * σημαίνει δίνει ως αποτέλεσμα μετά από καμία ή περισσότερες αντικαταστάσεις. Σε αυτήν τη συγκεκριμένη παραγωγή, επιλέξαμε σε κάθε βήμα να αντικαταστήσουμε το δεξιότερο μη τερματικό σύμβολο με το δεξιό μέλος κάποιου συντακτικού κανόνα. Αυτή η στρατηγική αντικατάστασης οδηγεί στη δεξιότερη παραγωγή, που συχνά ονομάζεται και κανονική παραγωγή. Υπάρχουν και πολλές άλλες δυνατές παραγωγές, όπως η αριστερότερη και άλλες ενδιάμεσες επιλογές. Οι περισσότεροι συντακτικοί αναλυτές έχουν κατασκευαστεί έτσι ώστε να βρίσκουν μια συγκεκριμένη παραγωγή (συνήθως την αριστερότερη ή τη δεξιότερη). Είδαμε στο Κεφάλαιο 1 ότι μπορούμε να αναπαραστήσουμε σχηματικά μια παραγωγή ως ένα δένδρο συντακτικής ανάλυσης. Η ρίζα του δένδρου είναι το αρχικό σύμβολο της γραμματικής. Τα φύλλα του δένδρου είναι το αποτέλεσμα της παραγωγής. Κάθε εσωτερικός κόμβος, μαζί με τα παιδιά του, αντιπροσωπεύει τη χρήση ενός συντακτικού κανόνα. Ένα δένδρο συντακτικής ανάλυσης για την έκφραση του παραδείγματός μας φαίνεται στην Εικόνα 2.1. Το δένδρο αυτό δεν είναι μοναδικό. Στο δεύτερο επίπεδο του δένδρου, θα μπορούσαμε να είχαμε επιλέξει να αντικαταστήσουμε τον τελεστή με *, αντί για +, και να αναπτύξουμε στη συνέχεια την έκφραση στα δεξιά, αντί γι αυτή στα αριστερά (δείτε την Εικόνα 2.2). Το γεγονός ότι κάποιες συμβολοσειρές είναι αποτέλεσμα περισσότερων από ένα δένδρων συντακτικής ανάλυσης μάς λέει ότι η γραμματική μας είναι διφορούμενη ή αμφίσημη (ambiguous). Οι αμφισημίες δημιουργούν προβλήματα όταν προσπαθούμε να κατασκευάσουμε συντακτικούς αναλυτές: χρειάζεται κάποιος πρόσθετος μηχανισμός που να καθοδηγεί την επιλογή μεταξύ εξίσου αποδεκτών εναλλακτικών. Μετά από λίγη σκέψη, καταλαβαίνουμε ότι υπάρχουν άπειρες γραμματικές χωρίς συμφραζόμενα για κάθε γλώσσα χωρίς συμφραζόμενα. Κάποιες από αυτές τις γραμματικές είναι πολύ πιο χρήσιμες από άλλες. Σε αυτό το βιβλίο, θα αποφύγουμε τη χρήση διφορούμενων γραμματικών, παρότι οι περισσότερες γεννήτριες συντακτικών αναλυτών τις επιτρέπουν, με τη χρήση κανόνων αποφυγής αμφισημίας (disambiguating rules). Θα αποφύγουμε επίσης τη χρήση των αποκαλούμενων άχρηστων συμβόλων: μη τερματικών συμβόλων που δεν μπορούν να παραγάγουν καμία ακολουθία τερματικών συμβόλων, ή τερματικών συμβόλων που δεν εμφανίζονται στο αποτέλεσμα καμίας παραγωγής.
72 Κεφάλαιο 2 Σύνταξη των γλωσσών προγραμματισμού Εικόνα 2.2 Εναλλακτικό (λιγότερο επιθυμητό) δένδρο συντακτικής ανάλυσης για την έκφραση slope * x + intercept (γραμματική του Παραδείγματος 2.3). Το γεγονός ότι υπάρχουν περισσότερα από ένα δένδρα σημαίνει ότι η γραμματική μας είναι διφορούμενη. Εικόνα 2.3 Δένδρο συντακτικής ανάλυσης για την έκφραση 3 + 4 * 5, με προτεραιότητα (γραμματική του Παραδείγματος 2.7). ΠΑΡΑΔΕΙΓΜΑ 2.7 Γραμματική εκφράσεων με προτεραιότητα και προσεταιριστικότητα Όταν σχεδιάζουμε τη γραμματική μιας γλώσσας προγραμματισμού, γενικά προσπαθούμε να βρούμε μια γραμματική που αντανακλά την εσωτερική δομή των προγραμμάτων με τρόπο χρήσιμο για το υπόλοιπο του μεταγλωττιστή. (Στην Ενότητα 2.3.2 θα δούμε ότι προσπαθούμε επίσης να βρούμε μια γραμματική που μπορεί να αναλυθεί αποδοτικά, κάτι το οποίο μπορεί να αποδειχθεί πρόκληση.) Η δομή είναι ιδιαίτερα σημαντική στις αριθμητικές εκφράσεις, όπου με συντακτικούς κανόνες μπορούμε να αποτυπώσουμε την προσεταιριστικότητα και την προτεραιότητα των διαφόρων τελεστών. Η προσεταιριστικότητα μας λέει ότι οι τελεστές στις περισσότερες γλώσσες ομαδοποιούν από αριστερά προς τα δεξιά, άρα 10-4 - 3 σημαίνει (10-4) - 3 και όχι 10 - (4-3). Η προτεραιότητα μας λέει ότι ο πολλαπλασιασμός και η διαίρεση στις περισσότερες γλώσσες ομαδοποιούν ισχυρότερα από την πρόσθεση και την αφαίρεση, άρα 3 + 4 * 5 σημαίνει 3 + (4 * 5) και όχι (3 + 4) * 5. (Οι κανόνες αυτοί δεν ισχύουν παντού θα τους εξετάσουμε ξανά στην Ενότητα 6.1.1.) Ακολουθεί μια καλύτερη έκδοση της γραμματικής μας για τις εκφράσεις. 1. expr term expr add op term 2. term factor term mult op factor 3. factor id number - factor ( expr ) 4. add op + - 5. mult op * / Η γραμματική αυτή δεν είναι διφορούμενη. Αποδίδει την προτεραιότητα των τελεστών με τον τρόπο που ορίζονται τα σύμβολα factor, term, και expr, με διαφορετικούς τελεστές να εμφανίζονται σε κάθε επίπεδο. Αποδίδει την προσεταιριστικότητα στο δεύτερο μισό των γραμμών 1 και 2, οι οποίες δημιουργούν τις εκφράσεις (expr) και τους όρους (term) στα αριστερά των τελεστών και όχι στα δεξιά. Στην Εικόνα 2.3, μπορούμε να δούμε πώς η ενσωμάτωση της έννοιας της προτεραιότητας στη γραμματική αποσαφηνίζει ότι ο πολλαπλασιασμός ομαδοποιεί ισχυρότερα από την πρόσθεση στην έκφραση 3 + 4 * 5, ακόμη και χωρίς παρενθέσεις. Στην Εικόνα 2.4, βλέπουμε ότι η αφαίρεση ομαδοποιεί ισχυρότερα προς τα αριστερά, οπότε η έκφραση 10-4 - 3 θα αποτιμηθεί σε 3 και όχι σε 9.
2.2 Λεκτική ανάλυση 73 Εικόνα 2.4 Δένδρο συντακτικής ανάλυσης για την έκφραση 10-4 - 3, με αριστερή προσεταιριστικότητα (γραμματική του Παραδείγματος 2.7). ΕΛΕΓΧΟΣ ΚΑΤΑΝΟΗΣΗΣ 1. Ποια είναι η διαφορά μεταξύ σύνταξης και σημασιολογίας; 2. Ποιες είναι οι τρεις βασικές πράξεις που μπορεί κανείς να χρησιμοποιήσει για να δημιουργήσει πολύπλοκες κανονικές εκφράσεις από άλλες απλούστερες; 3. Ποια επιπλέον πράξη (εκτός των τριών των κανονικών εκφράσεων) υποστηρίζουν οι γραμματικές χωρίς συμφραζόμενα; 4. Τι είναι η μορφή Backus-Naur; Πότε και γιατί επινοήθηκε; 5. Κατονομάστε μια γλώσσα στην οποία η στοίχιση επηρεάζει τη σύνταξη των προγραμμάτων. 6. Όταν μιλούμε για γλώσσες χωρίς συμφραζόμενα, τι είναι η παραγωγή; Τι είναι προτασιακή μορφή; 7. Ποια είναι η διαφορά ανάμεσα σε μια δεξιότερη παραγωγή και σε μια αριστερότερη παραγωγή; Ποια από τις δύο λέγεται και κανονική; 8. Τι σημαίνει ότι μια γραμματική χωρίς συμφραζόμενα είναι διφορούμενη; 9. Τι είναι η προσεταιριστικότητα και η προτεραιότητα; Γιατί είναι σημαντικές στα δένδρα συντακτικής ανάλυσης; 2.2 Λεκτική ανάλυση Ο λεκτικός και ο συντακτικός αναλυτής μιας γλώσσας προγραμματισμού αναλαμβάνουν από κοινού να ανακαλύψουν τη συντακτική δομή του προγράμματος. Αυτή η διαδικασία ανάλυσης της σύνταξης είναι ένα αναγκαίο πρώτο βήμα για τη μετάφραση του αρχικού προγράμματος σε ένα ισοδύναμο τελικό πρόγραμμα. (Είναι επίσης το πρώτο βήμα για την άμεση διερμηνεία του προγράμματος. Γενικά, στη συνέχεια του βιβλίου θα εστιάσουμε στη μεταγλώττιση και όχι στη διερμηνεία. Τα περισσότερα από αυτά που θα εξετάσουμε είτε έχουν προφανή εφαρμογή και στη διερμηνεία, είτε είναι προφανώς άσχετα με αυτή.) Ομαδοποιώντας τους χαρακτήρες εισόδου σε λεκτικές μονάδες, ο λεκτικός αναλυτής μειώνει δραστικά τον αριθμό των ανεξάρτητων τερματικών συμβόλων που πρέπει να εξετάσει ο συντακτικός αναλυτής, ο οποίος είναι υπολογιστικά πιο πολύπλοκος. Επιπλέον, ο λεκτικός αναλυτής συνήθως αφαιρεί τα σχόλια (ώστε ο συντακτικός αναλυτής να μη χρειάζεται να ασχοληθεί με το ότι αυτά μπορούν να εμφανίζονται οπουδήποτε στη γραμματική χωρίς συμφραζόμενα), αποθηκεύει το κείμενο των ενδιαφερουσών λεκτικών μονάδων όπως τα αναγνωριστικά, οι συμβολοσειρές και οι αριθμητικές σταθερές, και προσθέτει στις λεκτικές μονάδες μια ετικέτα με τον αριθμό γραμμής και στήλης, ώστε να διευκολύνεται η παραγωγή μηνυμάτων σφάλματος υψηλής ποιότητας σε μεταγενέστερες φάσεις.