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

Δημιουργήστε έναν διερμηνέα σε Java - Εφαρμόστε τη μηχανή εκτέλεσης

Προηγούμενο 1 2 3 Σελίδα 2 Επόμενο Σελίδα 2 από 3

Άλλες πτυχές: Χορδές και πίνακες

Δύο άλλα μέρη της γλώσσας BASIC εφαρμόζονται από τον διερμηνέα COCOA: χορδές και πίνακες. Ας δούμε πρώτα την εφαρμογή των χορδών.

Για την εφαρμογή συμβολοσειρών ως μεταβλητών, το Εκφραση η τάξη τροποποιήθηκε για να συμπεριλάβει την έννοια των "συμβολοσειρών" εκφράσεων. Αυτή η τροποποίηση έλαβε τη μορφή δύο προσθηκών: είναι συμβολοσειρά και stringValue. Η πηγή για αυτές τις δύο νέες μεθόδους φαίνεται παρακάτω.

 String stringValue (Program pgm) ρίχνει το BASICRuntimeError {ρίξτε νέο BASICRuntimeError ("Δεν υπάρχει παράσταση συμβολοσειράς για αυτό."); } boolean isString () {return false; } 

Είναι σαφές ότι δεν είναι πολύ χρήσιμο σε ένα πρόγραμμα BASIC να λάβετε την τιμή συμβολοσειράς μιας βασικής έκφρασης (η οποία είναι πάντα είτε αριθμητική είτε boolean έκφραση). Μπορεί να συμπεράνετε από την έλλειψη χρησιμότητας ότι αυτές οι μέθοδοι δεν ανήκαν τότε Εκφραση και ανήκε σε μια υποκατηγορία του Εκφραση αντι αυτου. Ωστόσο, βάζοντας αυτές τις δύο μεθόδους στην βασική τάξη, όλες Εκφραση τα αντικείμενα μπορούν να ελεγχθούν για να δουν αν, στην πραγματικότητα, είναι χορδές.

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

 String stringValue (Program pgm) ρίχνει το BASICRuntimeError {StringBuffer sb = νέο StringBuffer (); sb.append (this.value (pgm)); επιστροφή sb.toString (); } 

Και εάν χρησιμοποιείται ο παραπάνω κωδικός, μπορείτε να εξαλείψετε τη χρήση του είναι συμβολοσειρά γιατί κάθε παράσταση μπορεί να επιστρέψει μια τιμή συμβολοσειράς. Επιπλέον, μπορείτε να τροποποιήσετε το αξία μέθοδος για να προσπαθήσετε να επιστρέψετε έναν αριθμό εάν η έκφραση αξιολογηθεί σε μια συμβολοσειρά εκτελώντας τον μέσω του αξία του μέθοδος για java.lang. Διπλό. Σε πολλές γλώσσες, όπως Perl, TCL και REXX, αυτό το είδος άμορφης πληκτρολόγησης χρησιμοποιείται με μεγάλο πλεονέκτημα. Και οι δύο προσεγγίσεις είναι έγκυρες και θα πρέπει να κάνετε την επιλογή σας βάσει του σχεδιασμού του διερμηνέα σας. Στο BASIC, ο διερμηνέας πρέπει να επιστρέψει ένα σφάλμα όταν μια συμβολοσειρά έχει αντιστοιχιστεί σε μια αριθμητική μεταβλητή, οπότε επέλεξα την πρώτη προσέγγιση (επιστρέφοντας ένα σφάλμα).

Όσον αφορά τις συστοιχίες, υπάρχουν διαφορετικοί τρόποι με τους οποίους μπορείτε να σχεδιάσετε τη γλώσσα σας για να τις ερμηνεύσετε. Το C χρησιμοποιεί τις αγκύλες γύρω από τα στοιχεία του πίνακα για να διακρίνει τις αναφορές ευρετηρίου του πίνακα από αναφορές συναρτήσεων που έχουν παρενθέσεις γύρω από τα ορίσματά τους. Ωστόσο, οι σχεδιαστές γλωσσών για το BASIC επέλεξαν να χρησιμοποιήσουν παρενθέσεις τόσο για τις λειτουργίες όσο και για τους πίνακες, έτσι ώστε όταν το κείμενο ΟΝΟΜΑ (V1, V2) φαίνεται από τον αναλυτή, θα μπορούσε να είναι είτε μια συνάρτηση κλήσης είτε μια αναφορά πίνακα.

Ο λεξικός αναλυτής κάνει διακρίσεις μεταξύ των διακριτικών που ακολουθούνται από παρενθέσεις υποθέτοντας πρώτα ότι είναι συναρτήσεις και δοκιμές για αυτό. Στη συνέχεια, συνεχίζει να βλέπει αν είναι λέξεις-κλειδιά ή μεταβλητές. Αυτή η απόφαση εμποδίζει το πρόγραμμά σας να ορίσει μια μεταβλητή με το όνομα "SIN". Οποιαδήποτε μεταβλητή του οποίου το όνομα ταιριάζει με ένα όνομα συνάρτησης θα επιστρέφεται από τον λεξικό αναλυτή ως διακριτικό συνάρτησης. Το δεύτερο κόλπο που χρησιμοποιεί ο λεξικός αναλυτής είναι να ελέγξει αν το όνομα της μεταβλητής ακολουθείται αμέσως από το "(". Εάν είναι, ο αναλυτής υποθέτει ότι είναι μια συστοιχία αναφοράς. Αν το αναλύσουμε στον λεξικό αναλυτή, εξαλείφουμε τη συμβολοσειρά "MYARRAY (2)«να ερμηνευθεί ως έγκυρος πίνακας (σημειώστε το διάστημα μεταξύ του ονόματος της μεταβλητής και της ανοιχτής παρένθεσης).

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

class Variable επεκτείνει Token {// Νομικοί τύποι μεταβλητών final static int NUMBER = 0; τελικό στατικό int STRING = 1; τελικό στατικό int NUMBER_ARRAY = 2; τελικό στατικό int STRING_ARRAY = 4; Όνομα συμβολοσειράς; int υποτύπος; / * * Εάν η μεταβλητή βρίσκεται στον πίνακα συμβόλων, αυτές οι τιμές * αρχικοποιούνται. * / int ndx []; // δείκτες πίνακα. int πολυ []; // πολλαπλασιαστές συστοιχιών double nArrayValues ​​[]; String sArrayValues ​​[]; 

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

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

Για παράδειγμα, ένας πίνακας BASIC με τρεις διαστάσεις 10, 10 και 8, θα έχει τις τιμές 10, 10 και 8 αποθηκευμένες σε ndx. Αυτό επιτρέπει στον αξιολογητή έκφρασης να δοκιμάσει μια κατάσταση "ευρετηρίου εκτός ορίων" συγκρίνοντας τον αριθμό που χρησιμοποιείται στο πρόγραμμα BASIC με τον μέγιστο νόμιμο αριθμό που είναι τώρα αποθηκευμένος σε ndx. Ο πίνακας πολλαπλασιαστή στο παράδειγμά μας θα περιέχει τις τιμές 1, 10 και 100. Αυτές οι σταθερές αντιπροσωπεύουν τους αριθμούς που χρησιμοποιεί ένας για να χαρτογραφήσει από μια πολυδιάστατη προδιαγραφή ευρετηρίου συστοιχιών σε μια προδιαγραφή ευρετηρίου γραμμικού πίνακα. Η πραγματική εξίσωση είναι:

Java Index = Index1 + Index2 * Max Size of Index1 + Index3 * (MaxSize of Index1 * MaxSizeIndex 2)

Ο επόμενος πίνακας Java στο Μεταβλητός η τάξη φαίνεται παρακάτω.

 Η έκφραση επεκτείνεται []; 

ο επεκτείνει Ο πίνακας χρησιμοποιείται για την αντιμετώπιση συστοιχιών που γράφονται ως "A (10 * B, i)"Σε αυτήν την περίπτωση, οι δείκτες είναι στην πραγματικότητα εκφράσεις και όχι σταθερές, οπότε η αναφορά πρέπει να περιέχει δείκτες σε εκείνες τις εκφράσεις που αξιολογούνται κατά το χρόνο εκτέλεσης. Τέλος, υπάρχει ένα αρκετά άσχημο κομμάτι κώδικα που υπολογίζει το ευρετήριο ανάλογα με το τι πέρασε στο πρόγραμμα. Αυτή η ιδιωτική μέθοδος φαίνεται παρακάτω.

 private int computeIndex (int ii []) ρίχνει το BASICRuntimeError {int offset = 0; εάν ((ndx == null) || (ii.length! = ndx.length)) ρίξτε νέο BASICRuntimeError ("Λάθος αριθμός δεικτών."); για (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) ρίξτε νέο BASICRuntimeError ("Ευρετήριο εκτός εύρους."); offset = offset + (ii [i] -1) * mult [i]; } αντιστάθμιση επιστροφής; } 

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

 double numValue (int ii []) ρίχνει το BASICRuntimeError {return nArrayValues ​​[computeIndex (ii)]; } Το string stringValue (int ii []) ρίχνει το BASICRuntimeError {if (subType == NUMBER_ARRAY) return "" + nArrayValues ​​[computeIndex (ii)]; επιστροφή sArrayValues ​​[computeIndex (ii)]; } 

Υπάρχουν πρόσθετες μέθοδοι για τον καθορισμό της τιμής μιας μεταβλητής που δεν εμφανίζονται εδώ.

Κρύβοντας μεγάλο μέρος της πολυπλοκότητας του τρόπου υλοποίησης κάθε κομματιού, όταν έρθει επιτέλους η ώρα να εκτελέσετε το πρόγραμμα BASIC, ο κώδικας Java είναι αρκετά απλός.

Εκτέλεση του κώδικα

Ο κωδικός για την ερμηνεία των βασικών δηλώσεων και την εκτέλεση τους περιέχεται στο

τρέξιμο

μέθοδος του

Πρόγραμμα

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

 1 δημόσια άκυρη εκτέλεση (InputStream in, OutputStream out) ρίχνει το BASICRuntimeError {2 PrintStream σύκο; 3 απαρίθμηση e = stmts.elements (); 4 stmtStack = νέα στοίβα (); // υποθέστε ότι δεν υπάρχουν στοιβαγμένες δηλώσεις ... 5 dataStore = new Vector (); // ... και δεν υπάρχουν δεδομένα για ανάγνωση. 6 δεδομέναPtr = 0; 7 Δήλωση; 8 9 vars = νέο RedBlackTree (); 10 11 // εάν το πρόγραμμα δεν είναι ακόμη έγκυρο. 12 εάν (! E.hasMoreElements ()) 13 επιστροφή; 14 15 εάν (έξω από το PrintStream) {16 pout = (PrintStream) έξω; 17} αλλιώς {18 pout = νέο PrintStream (έξω); 19} 

Ο παραπάνω κώδικας δείχνει ότι το τρέξιμο η μέθοδος παίρνει ένα InputStream και ένα Έξοδος ροής για χρήση ως "κονσόλα" για το πρόγραμμα εκτέλεσης. Στη γραμμή 3, το αντικείμενο απαρίθμησης μι ορίζεται στο σύνολο των δηλώσεων από τη συλλογή που ονομάζεται stmts. Για αυτήν τη συλλογή χρησιμοποίησα μια παραλλαγή σε ένα δέντρο δυαδικής αναζήτησης που ονομάζεται "κόκκινο-μαύρο" δέντρο. (Για περισσότερες πληροφορίες σχετικά με δυαδικά δέντρα αναζήτησης, ανατρέξτε στην προηγούμενη στήλη μου σχετικά με τη δημιουργία γενικών συλλογών.) Μετά από αυτό, δημιουργούνται δύο επιπλέον συλλογές - μία χρησιμοποιώντας Σωρός και ένα χρησιμοποιώντας ένα Διάνυσμα. Η στοίβα χρησιμοποιείται όπως η στοίβα σε οποιονδήποτε υπολογιστή, αλλά το διάνυσμα χρησιμοποιείται ρητά για τις δηλώσεις DATA στο πρόγραμμα BASIC. Η τελική συλλογή είναι ένα άλλο κόκκινο-μαύρο δέντρο που κρατά τις αναφορές για τις μεταβλητές που ορίζονται από το πρόγραμμα BASIC. Αυτό το δέντρο είναι ο πίνακας συμβόλων που χρησιμοποιείται από το πρόγραμμα ενώ εκτελείται.

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

 / * Πρώτα φορτώνουμε όλες τις δηλώσεις δεδομένων * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); εάν (s.keyword == Statement.DATA) {s.execute (αυτό, σε, σύκο); }} 

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

 e = stmts.elements (); s = (Δήλωση) e.nextElement (); κάντε {int yyy; / * Κατά την εκτέλεση παραλείπουμε τις δηλώσεις δεδομένων. * / δοκιμάστε το {yyy = in.available (); } catch (IOException ez) {εεε = 0; } if (yyy! = 0) {pout.println ("Διακοπή στις:" + s); ώθηση (ες); Διακοπή; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (this, (traceFile! = null)? traceFile: pout); } s = s.execute (αυτό, σε, σύκο); } other s = nextStatement (s); } ενώ (s! = null); } 

Όπως μπορείτε να δείτε στον παραπάνω κώδικα, το πρώτο βήμα είναι η εκ νέου προετοιμασία μι. Το επόμενο βήμα είναι να φέρετε την πρώτη δήλωση στη μεταβλητή μικρό και μετά για να μπείτε στον βρόχο εκτέλεσης. Υπάρχει κάποιος κώδικας για έλεγχο της εισόδου σε εκκρεμότητα στη ροή εισόδου για να επιτρέπεται η διακοπή της προόδου του προγράμματος πληκτρολογώντας το πρόγραμμα και στη συνέχεια ο βρόχος ελέγχει για να δει αν η δήλωση που θα εκτελεστεί θα ήταν δήλωση DATA. Εάν είναι, ο βρόχος παραλείπει τη δήλωση καθώς είχε ήδη εκτελεστεί. Η μάλλον περίπλοκη τεχνική εκτέλεσης όλων των δηλώσεων δεδομένων πρώτα απαιτείται επειδή το BASIC επιτρέπει στις δηλώσεις DATA που ικανοποιούν μια δήλωση READ να εμφανίζονται οπουδήποτε στον πηγαίο κώδικα. Τέλος, εάν είναι ενεργοποιημένη η ανίχνευση, εκτυπώνεται μια εγγραφή ιχνών και η πολύ μη εντυπωσιακή δήλωση s = s.execute (αυτό, σε, σύκο); καλείται. Η ομορφιά είναι ότι όλη η προσπάθεια ενσωμάτωσης των βασικών εννοιών σε εύληπτα μαθήματα καθιστά τον τελικό κώδικα ασήμαντο. Εάν δεν είναι ασήμαντο, τότε ίσως έχετε ιδέα ότι μπορεί να υπάρχει άλλος τρόπος να χωρίσετε το σχέδιό σας.

Συμπλήρωση και περαιτέρω σκέψεις

Ο διερμηνέας σχεδιάστηκε έτσι ώστε να μπορεί να λειτουργεί ως νήμα, επομένως μπορεί να υπάρχουν πολλά νήματα διερμηνέα COCOA που εκτελούνται ταυτόχρονα στον χώρο του προγράμματος σας ταυτόχρονα. Επιπλέον, με τη χρήση επέκτασης λειτουργίας μπορούμε να παρέχουμε ένα μέσο με το οποίο αυτά τα νήματα μπορούν να αλληλεπιδρούν μεταξύ τους. Υπήρχε ένα πρόγραμμα για το Apple II και αργότερα για τον υπολογιστή και το Unix που ονομάζεται C-robots που ήταν ένα σύστημα αλληλεπίδρασης "ρομποτικών" οντοτήτων που προγραμματίστηκαν χρησιμοποιώντας μια απλή παράγωγη γλώσσα BASIC. Το παιχνίδι παρείχε σε εμένα και σε άλλους πολλές ώρες ψυχαγωγίας, αλλά ήταν επίσης ένας εξαιρετικός τρόπος για να εισαγάγουμε τις βασικές αρχές του υπολογισμού σε νεότερους μαθητές (οι οποίοι κατά λάθος πίστευαν ότι έπαιζαν απλά και όχι να μάθουν). Τα υποσυστήματα διερμηνέων που βασίζονται σε Java είναι πολύ πιο ισχυρά από τα αντίστοιχα προ-Java, επειδή είναι άμεσα διαθέσιμα σε οποιαδήποτε πλατφόρμα Java. Η COCOA έτρεξε σε συστήματα Unix και Macintoshes την ίδια μέρα που δούλευα σε υπολογιστή με Windows 95. Ενώ η Java χτυπιέται από ασυμβατότητες στις υλοποιήσεις της εργαλειοθήκης νήματος ή παραθύρου, αυτό που συχνά παραβλέπεται είναι αυτό: Πολύς κώδικας "λειτουργεί μόνο".