Προγραμματισμός

Πώς να φτιάξετε έναν διερμηνέα στην Java, Μέρος 1: Τα βασικά

Όταν είπα σε έναν φίλο ότι είχα γράψει έναν ΒΑΣΙΚΟ διερμηνέα στην Ιάβα, γέλασε τόσο σκληρά που σχεδόν χύθηκε τη σόδα που κρατούσε σε όλα τα ρούχα του. "Γιατί στον κόσμο θα δημιουργούσατε έναν διερμηνέα BASIC στην Java;" ήταν η προβλέψιμη πρώτη ερώτηση από το στόμα του. Η απάντηση είναι τόσο απλή όσο και περίπλοκη. Η απλή απάντηση είναι ότι ήταν διασκεδαστικό να γράψω έναν διερμηνέα στην Java, και αν επρόκειτο να γράψω έναν διερμηνέα, θα μπορούσα επίσης να γράψω ένα για το οποίο έχω αγαπημένες αναμνήσεις από τις πρώτες μέρες της προσωπικής πληροφορικής. Από την περίπλοκη πλευρά, έχω παρατηρήσει ότι πολλοί άνθρωποι που χρησιμοποιούν Java σήμερα έχουν ξεπεράσει το σημείο της δημιουργίας ανατρεπόμενων μικροεφαρμογών Duke και προχωρούν σε σοβαρές εφαρμογές. Συχνά, κατά τη δημιουργία μιας εφαρμογής, θα θέλατε να είναι διαμορφώσιμη. Ο μηχανισμός επιλογής για εκ νέου διαμόρφωση είναι ένα είδος κινητήρα δυναμικής εκτέλεσης.

Γνωστή ως γλώσσες μακροεντολών ή γλώσσες διαμόρφωσης, η δυναμική εκτέλεση είναι η δυνατότητα που επιτρέπει στον χρήστη να "προγραμματιστεί" από τον χρήστη. Το πλεονέκτημα μιας δυναμικής μηχανής εκτέλεσης είναι ότι τα εργαλεία και οι εφαρμογές μπορούν να προσαρμοστούν για την εκτέλεση πολύπλοκων εργασιών χωρίς αντικατάσταση του εργαλείου. Η πλατφόρμα Java προσφέρει μια μεγάλη ποικιλία δυναμικών επιλογών μηχανής εκτέλεσης.

HotJava και άλλες επιλογές

Ας εξερευνήσουμε εν συντομία μερικές από τις δυναμικές επιλογές μηχανών εκτέλεσης που είναι διαθέσιμες και ας δούμε σε βάθος την εφαρμογή του διερμηνέα μου. Μια μηχανή δυναμικής εκτέλεσης είναι ένας ενσωματωμένος διερμηνέας. Ένας διερμηνέας απαιτεί τρεις εγκαταστάσεις για να λειτουργήσει:

  1. Ένα μέσο φόρτωσης με οδηγίες
  2. Μορφή ενότητας, για αποθήκευση οδηγιών προς εκτέλεση
  3. Ένα μοντέλο ή περιβάλλον για αλληλεπίδραση με το πρόγραμμα υποδοχής

HotJava

Ο πιο διάσημος ενσωματωμένος διερμηνέας πρέπει να είναι το περιβάλλον "applet" του HotJava που έχει αναδιαμορφώσει πλήρως τον τρόπο με τον οποίο οι άνθρωποι βλέπουν τα προγράμματα περιήγησης στο Web.

Το μοντέλο "applet" HotJava βασίστηκε στην ιδέα ότι μια εφαρμογή Java θα μπορούσε να δημιουργήσει μια γενική κλάση βάσης με μια γνωστή διεπαφή και, στη συνέχεια, να φορτώσει δυναμικά υποκατηγορίες αυτής της κλάσης και να τις εκτελέσει στο χρόνο εκτέλεσης. Αυτές οι μικροεφαρμογές παρείχαν νέες δυνατότητες και, εντός των ορίων της βασικής κατηγορίας, παρείχαν δυναμική εκτέλεση. Αυτή η δυναμική ικανότητα εκτέλεσης είναι ένα θεμελιώδες μέρος του περιβάλλοντος Java και ένα από τα πράγματα που το καθιστούν τόσο ξεχωριστό. Θα εξετάσουμε σε βάθος αυτό το συγκεκριμένο περιβάλλον σε μια μεταγενέστερη στήλη.

GNU EMACS

Πριν φτάσει το HotJava, ίσως η πιο επιτυχημένη εφαρμογή με δυναμική εκτέλεση ήταν το GNU EMACS. Η γλώσσα μακροεντολών τύπου LISP αυτού του συντάκτη έχει γίνει βασικό στοιχείο για πολλούς προγραμματιστές. Εν συντομία, το περιβάλλον EMACS LISP αποτελείται από έναν διερμηνέα LISP και πολλές λειτουργίες τύπου επεξεργασίας που μπορούν να χρησιμοποιηθούν για τη σύνθεση των πιο σύνθετων μακροεντολών. Δεν πρέπει να θεωρείται εκπληκτικό το γεγονός ότι ο επεξεργαστής EMACS γράφτηκε αρχικά σε μακροεντολές που σχεδιάστηκαν για έναν συντάκτη που ονομάζεται TECO. Έτσι, η διαθεσιμότητα μιας πλούσιας γλώσσας (αν δεν είναι αναγνώσιμη) στο TECO επέτρεψε την κατασκευή ενός εντελώς νέου προγράμματος επεξεργασίας. Σήμερα, το GNU EMACS είναι το βασικό πρόγραμμα επεξεργασίας και ολόκληρα παιχνίδια έχουν γραφτεί σε τίποτα περισσότερο από τον κώδικα EMACS LISP, γνωστός ως el-code. Αυτή η ικανότητα διαμόρφωσης έχει κάνει το GNU EMACS έναν βασικό επεξεργαστή, ενώ τα τερματικά VT-100 που είχε σχεδιαστεί για να λειτουργήσουν έχουν γίνει απλά υποσημειώσεις στη στήλη ενός συγγραφέα.

REXX

Μία από τις αγαπημένες μου γλώσσες, που δεν έφτιαχνε ποτέ τον εαυτό της, ήταν το REXX, που σχεδιάστηκε από τον Mike Cowlishaw της IBM. Η εταιρεία χρειαζόταν μια γλώσσα για τον έλεγχο εφαρμογών σε μεγάλα κεντρικά πλαίσια που εκτελούν το λειτουργικό σύστημα VM. Ανακάλυψα το REXX στο Amiga, όπου συνδέθηκε στενά με μια μεγάλη ποικιλία εφαρμογών μέσω των "θυρών REXX". Αυτές οι θύρες επέτρεψαν την απομακρυσμένη οδήγηση εφαρμογών μέσω του διερμηνέα REXX. Αυτή η σύζευξη διερμηνέα και εφαρμογής δημιούργησε ένα πολύ πιο ισχυρό σύστημα από ό, τι ήταν δυνατό με τα εξαρτήματα του. Ευτυχώς, η γλώσσα ζει στο NETREXX, μια έκδοση που έγραψε ο Mike, η οποία συντάχθηκε σε κώδικα Java.

Καθώς έβλεπα το NETREXX και μια πολύ παλαιότερη γλώσσα (LISP στην Java), με εντυπωσίασε ότι αυτές οι γλώσσες αποτελούσαν σημαντικά μέρη της ιστορίας της εφαρμογής Java. Ποιος καλύτερος τρόπος για να πείτε αυτό το μέρος της ιστορίας από το να κάνετε κάτι διασκεδαστικό εδώ - όπως να αναστήσετε το BASIC-80; Το πιο σημαντικό, θα ήταν χρήσιμο να δείξουμε έναν τρόπο με τον οποίο μπορούν να γραφτούν γλώσσες δέσμης ενεργειών στην Java και, μέσω της ενοποίησής τους με την Java, να δείξουν πώς μπορούν να βελτιώσουν τις δυνατότητες των εφαρμογών σας Java.

ΒΑΣΙΚΕΣ απαιτήσεις για τη βελτίωση των εφαρμογών Java

Το BASIC είναι, απλά, μια βασική γλώσσα. Υπάρχουν δύο σχολές σκέψης για το πώς μπορεί κανείς να γράψει διερμηνέα για αυτό. Μια προσέγγιση είναι να γράψετε έναν βρόχο προγραμματισμού στον οποίο το πρόγραμμα διερμηνέα διαβάζει μια γραμμή κειμένου από το ερμηνευμένο πρόγραμμα, το αναλύει και στη συνέχεια καλεί μια υπορουτίνα για να το εκτελέσει. Η ακολουθία ανάγνωσης, ανάλυσης και εκτέλεσης επαναλαμβάνεται έως ότου μία από τις δηλώσεις του ερμηνευμένου προγράμματος λέει στον διερμηνέα να σταματήσει.

Ο δεύτερος και πολύ πιο ενδιαφέρων τρόπος αντιμετώπισης του έργου είναι στην πραγματικότητα η ανάλυση της γλώσσας σε ένα δέντρο ανάλυσης και στη συνέχεια η εκτέλεση του δέντρου ανάλυσης «στη θέση του». Αυτός είναι ο τρόπος με τον οποίο λειτουργούν οι διερμηνείς συμβολικών και ο τρόπος που επέλεξα να συνεχίσω. Οι διερμηνείς Tokenizing είναι επίσης γρηγορότεροι, καθώς δεν χρειάζεται να σαρώσουν ξανά την είσοδο κάθε φορά που εκτελούν μια δήλωση.

Όπως ανέφερα παραπάνω, τα τρία στοιχεία που είναι απαραίτητα για την επίτευξη δυναμικής εκτέλεσης είναι ένα μέσο φόρτωσης, μια μορφή μονάδας και το περιβάλλον εκτέλεσης.

Το πρώτο συστατικό, ένα μέσο φόρτωσης, θα αντιμετωπιστεί από μια Java InputStream. Καθώς οι ροές εισόδου είναι θεμελιώδεις στην αρχιτεκτονική I / O της Java, το σύστημα έχει σχεδιαστεί για ανάγνωση σε ένα πρόγραμμα από ένα InputStream και μετατρέψτε το σε εκτελέσιμη μορφή. Αυτό αντιπροσωπεύει έναν πολύ ευέλικτο τρόπο τροφοδοσίας κώδικα στο σύστημα. Φυσικά, το πρωτόκολλο για τα δεδομένα που υπερβαίνουν τη ροή εισόδου θα είναι ο βασικός πηγαίος κώδικας. Είναι σημαντικό να σημειωθεί ότι οποιαδήποτε γλώσσα μπορεί να χρησιμοποιηθεί. Μην κάνετε το λάθος να πιστεύετε ότι αυτή η τεχνική δεν μπορεί να εφαρμοστεί στην εφαρμογή σας.

Μετά την εισαγωγή του πηγαίου κώδικα του ερμηνευμένου προγράμματος στο σύστημα, το σύστημα μετατρέπει τον πηγαίο κώδικα σε εσωτερική αναπαράσταση. Επέλεξα να χρησιμοποιήσω το δέντρο ανάλυσης ως τη μορφή εσωτερικής αναπαράστασης για αυτό το έργο. Μόλις δημιουργηθεί το δέντρο ανάλυσης, μπορεί να χρησιμοποιηθεί ή να εκτελεστεί.

Το τρίτο στοιχείο είναι το περιβάλλον εκτέλεσης. Όπως θα δούμε, οι απαιτήσεις για αυτό το στοιχείο είναι αρκετά απλές, αλλά η εφαρμογή έχει μερικές ενδιαφέρουσες ανατροπές.

Μια πολύ γρήγορη περιοδεία BASIC

Για όσους από εσάς δεν έχετε ακούσει ποτέ για το BASIC, θα σας δώσω μια σύντομη ματιά στη γλώσσα, ώστε να μπορείτε να κατανοήσετε τις προκλήσεις ανάλυσης και εκτέλεσης που ακολουθούν. Για περισσότερες πληροφορίες σχετικά με το BASIC, συνιστώ ανεπιφύλακτα τους πόρους στο τέλος αυτής της στήλης.

Το BASIC αντιπροσωπεύει τον αρχικό συμβολικό κώδικα διδασκαλίας για αρχάριους και αναπτύχθηκε στο Πανεπιστήμιο Dartmouth για να διδάξει έννοιες υπολογισμού σε προπτυχιακούς φοιτητές. Από την ανάπτυξή του, το BASIC έχει εξελιχθεί σε μια ποικιλία διαλέκτων. Οι απλούστερες από αυτές τις διαλέκτους χρησιμοποιούνται ως γλώσσες ελέγχου για ελεγκτές βιομηχανικών διεργασιών. Οι πιο σύνθετες διάλεκτοι είναι δομημένες γλώσσες που ενσωματώνουν ορισμένες πτυχές του αντικειμενοστραφούς προγραμματισμού. Για το έργο μου, επέλεξα μια διάλεκτο γνωστή ως BASIC-80 που ήταν δημοφιλής στο λειτουργικό σύστημα CP / M στα τέλη της δεκαετίας του εβδομήντα. Αυτή η διάλεκτος είναι πολύ πιο περίπλοκη από τις απλούστερες διαλέκτους.

Σύνταξη δήλωσης

Όλες οι γραμμές δήλωσης έχουν τη μορφή

[ : [ : ... ] ]

όπου "Γραμμή" είναι ένας αριθμός γραμμής δήλωσης, "Λέξη-κλειδί" είναι μια βασική λέξη-κλειδί δήλωσης και "Παράμετροι" είναι ένα σύνολο παραμέτρων που σχετίζονται με αυτήν τη λέξη-κλειδί.

Ο αριθμός γραμμής έχει δύο σκοπούς: Χρησιμεύει ως ετικέτα για δηλώσεις που ελέγχουν τη ροή εκτέλεσης, όπως a παω σε δήλωση και χρησιμεύει ως ετικέτα διαλογής για δηλώσεις που εισάγονται στο πρόγραμμα. Ως ετικέτα ταξινόμησης, ο αριθμός γραμμής διευκολύνει ένα περιβάλλον επεξεργασίας γραμμής στο οποίο η επεξεργασία και η επεξεργασία εντολών αναμιγνύονται σε μία μόνο διαδραστική συνεδρία. Παρεμπιπτόντως, αυτό ήταν απαραίτητο όταν το μόνο που είχατε ήταν ένας τηλετύπος. :-)

Αν και δεν είναι πολύ κομψοί, οι αριθμοί γραμμής δίνουν στο περιβάλλον του διερμηνέα τη δυνατότητα ενημέρωσης του προγράμματος μία δήλωση κάθε φορά. Αυτή η ικανότητα πηγάζει από το γεγονός ότι μια δήλωση είναι μία οντότητα με ανάλυση και μπορεί να συνδεθεί σε μια δομή δεδομένων με αριθμούς γραμμής. Χωρίς αριθμούς γραμμής, συχνά είναι απαραίτητο να αναλυθεί ολόκληρο το πρόγραμμα όταν αλλάζει μια γραμμή.

Η λέξη-κλειδί προσδιορίζει τη δήλωση BASIC. Στο παράδειγμα, ο διερμηνέας μας θα υποστηρίξει ένα ελαφρώς εκτεταμένο σύνολο λέξεων-κλειδιών BASIC, όπως παω σε, gosub, ΕΠΙΣΤΡΟΦΗ, Τυπώνω, αν, τέλος, δεδομένα, επαναφέρω, ανάγνωση, επί, υπενθύμιση, Για, Επόμενο, αφήνω, εισαγωγή, να σταματήσει, αμυδρός, τυχαιοποιήστε, tron, και troff. Προφανώς, δεν θα τα εξετάσουμε όλα αυτά σε αυτό το άρθρο, αλλά θα υπάρξει κάποια διαδικτυακή τεκμηρίωση στο "Java In Depth" του επόμενου μήνα για να το εξερευνήσετε.

Κάθε λέξη-κλειδί έχει ένα σύνολο παραμέτρων νομικών λέξεων-κλειδιών που μπορούν να την ακολουθήσουν. Για παράδειγμα, το παω σε λέξη-κλειδί πρέπει να ακολουθείται από έναν αριθμό γραμμής, το αν Η δήλωση πρέπει να ακολουθείται από μια υπό όρους έκφραση καθώς και τη λέξη-κλειδί έπειτα -- και ούτω καθεξής. Οι παράμετροι είναι συγκεκριμένες για κάθε λέξη-κλειδί. Θα καλύψω μερικές από αυτές τις λίστες παραμέτρων λεπτομερώς λίγο αργότερα.

Εκφράσεις και τελεστές

Συχνά, μια παράμετρος που καθορίζεται σε μια δήλωση είναι μια έκφραση. Η έκδοση του BASIC που χρησιμοποιώ εδώ υποστηρίζει όλες τις τυπικές μαθηματικές λειτουργίες, τις λογικές λειτουργίες, την εκτόνωση και μια απλή βιβλιοθήκη λειτουργιών. Το πιο σημαντικό συστατικό της γραμματικής έκφρασης είναι η ικανότητα κλήσης συναρτήσεων. Οι ίδιες οι εκφράσεις είναι αρκετά τυπικές και παρόμοιες με αυτές που περιγράφονται στο παράδειγμα της προηγούμενης στήλης StreamTokenizer.

Μεταβλητές και τύποι δεδομένων

Μέρος του λόγου που το BASIC είναι τόσο απλή γλώσσα είναι επειδή έχει μόνο δύο τύπους δεδομένων: αριθμούς και συμβολοσειρές. Ορισμένες γλώσσες δέσμης ενεργειών, όπως REXX και PERL, δεν κάνουν καν αυτή τη διάκριση μεταξύ τύπων δεδομένων έως ότου χρησιμοποιηθούν. Αλλά με το BASIC, χρησιμοποιείται μια απλή σύνταξη για τον προσδιορισμό των τύπων δεδομένων.

Τα μεταβλητά ονόματα σε αυτήν την έκδοση του BASIC είναι σειρές γραμμάτων και αριθμών που ξεκινούν πάντα με ένα γράμμα. Οι μεταβλητές δεν είναι διάκριση πεζών-κεφαλαίων. Έτσι, τα A, B, FOO και FOO2 είναι όλα έγκυρα ονόματα μεταβλητών. Επιπλέον, στο BASIC, η μεταβλητή FOOBAR είναι ισοδύναμη με το FooBar. Για τον εντοπισμό συμβολοσειρών, ένα σύμβολο δολαρίου ($) προσαρτάται στο όνομα της μεταβλητής. Έτσι, η μεταβλητή FOO $ είναι μια μεταβλητή που περιέχει μια συμβολοσειρά.

Τέλος, αυτή η έκδοση της γλώσσας υποστηρίζει πίνακες χρησιμοποιώντας το αμυδρός λέξη-κλειδί και μια μεταβλητή σύνταξη της φόρμας NAME (index1, index2, ...) για έως τέσσερις δείκτες.

Δομή προγράμματος

Τα προγράμματα στο BASIC ξεκινούν από προεπιλογή στη χαμηλότερη αριθμημένη γραμμή και συνεχίζονται μέχρι να μην υπάρχουν άλλες γραμμές προς επεξεργασία ή το να σταματήσει ή τέλος οι λέξεις-κλειδιά εκτελούνται. Ένα πολύ απλό πρόγραμμα BASIC φαίνεται παρακάτω:

100 REM Αυτό είναι πιθανώς το κανονικό BASIC παράδειγμα 110 REM Program. Σημειώστε ότι οι δηλώσεις REM αγνοούνται. 120 ΕΚΤΥΠΩΣΗ "Αυτό είναι ένα πρόγραμμα δοκιμών." 130 ΕΚΤΥΠΩΣΗ "Αθροίζοντας τις τιμές μεταξύ 1 και 100" 140 LET σύνολο = 0 150 FOR I = 1 TO 100 160 LET total = total + i 170 NEXT I 180 PRINT "Το σύνολο όλων των ψηφίων μεταξύ 1 και 100 είναι" σύνολο 190 ΤΕΛΟΣ 

Οι αριθμοί γραμμής παραπάνω δείχνουν τη λεξική σειρά των δηλώσεων. Όταν εκτελούνται, οι γραμμές 120 και 130 εκτυπώνουν μηνύματα στην έξοδο, η γραμμή 140 αρχικοποιεί μια μεταβλητή και ο βρόχος στις γραμμές 150 έως 170 ενημερώνει την τιμή αυτής της μεταβλητής. Τέλος, τα αποτελέσματα εκτυπώνονται. Όπως μπορείτε να δείτε, το BASIC είναι μια πολύ απλή γλώσσα προγραμματισμού και επομένως ιδανικός υποψήφιος για τη διδασκαλία εννοιών υπολογισμού.

Οργάνωση της προσέγγισης

Χαρακτηριστικό των γλωσσών δέσμης ενεργειών, το BASIC περιλαμβάνει ένα πρόγραμμα που αποτελείται από πολλές δηλώσεις που εκτελούνται σε ένα συγκεκριμένο περιβάλλον. Η πρόκληση του σχεδιασμού, λοιπόν, είναι η κατασκευή των αντικειμένων για την εφαρμογή ενός τέτοιου συστήματος με χρήσιμο τρόπο.

Όταν κοίταξα το πρόβλημα, μια απλή δομή δεδομένων μου πήγε αρκετά. Αυτή η δομή έχει ως εξής:

Η δημόσια διεπαφή με τη γλώσσα δέσμης ενεργειών αποτελείται από

  • Μια εργοστασιακή μέθοδος που λαμβάνει τον πηγαίο κώδικα ως είσοδο και επιστρέφει ένα αντικείμενο που αντιπροσωπεύει το πρόγραμμα.
  • Ένα περιβάλλον που παρέχει το πλαίσιο στο οποίο εκτελείται το πρόγραμμα, συμπεριλαμβανομένων συσκευών "I / O" για εισαγωγή κειμένου και έξοδο κειμένου.
  • Ένας τυπικός τρόπος τροποποίησης αυτού του αντικειμένου, ίσως με τη μορφή διεπαφής, που επιτρέπει στο πρόγραμμα και το περιβάλλον να συνδυαστούν για να επιτύχουν χρήσιμα αποτελέσματα.

Εσωτερικά, η δομή του διερμηνέα ήταν λίγο πιο περίπλοκη. Το ερώτημα ήταν πώς να εξεταστούν οι δύο όψεις της γλώσσας σεναρίου, η ανάλυση και η εκτέλεση; Προέκυψαν τρεις ομάδες τάξεων - μία για ανάλυση, μία για το δομικό πλαίσιο αναπαραγωγής αναλυόμενων και εκτελέσιμων προγραμμάτων και μία που διαμόρφωσε την βασική κλάση περιβάλλοντος για εκτέλεση.

Στην ομάδα ανάλυσης, απαιτούνται τα ακόλουθα αντικείμενα:

  • Λεξική ανάλυση για την επεξεργασία του κώδικα ως κείμενο
  • Ανάλυση έκφρασης, για την κατασκευή αναλυτικών δέντρων των εκφράσεων
  • Αναλυτική δήλωση, για την κατασκευή δέντρων ανάλυσης των ίδιων των δηλώσεων
  • Κατηγορίες σφαλμάτων για την αναφορά σφαλμάτων στην ανάλυση

Η ομάδα πλαισίου αποτελείται από αντικείμενα που συγκρατούν τα δέντρα ανάλυσης και τις μεταβλητές. Αυτά περιλαμβάνουν:

  • Ένα αντικείμενο δήλωσης με πολλές εξειδικευμένες υποκατηγορίες που αντιπροσωπεύουν αναλυμένες δηλώσεις
  • Ένα αντικείμενο έκφρασης που αντιπροσωπεύει εκφράσεις για αξιολόγηση
  • Ένα μεταβλητό αντικείμενο με πολλές εξειδικευμένες υποκατηγορίες που αντιπροσωπεύουν ατομικές παρουσίες δεδομένων