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

Λεξική ανάλυση και Java: Μέρος 1

Λεξική ανάλυση και ανάλυση

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

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

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

Λεξικοί αναλυτές της Java

Η Java Language Specification, έκδοση 1.0.2, ορίζει δύο κατηγορίες λεξικών αναλυτών, StringTokenizer και StreamTokenizer. Από τα ονόματά τους μπορείτε να συμπεράνετε αυτό StringTokenizer χρήσεις Σειρά αντικείμενα ως είσοδο του, και StreamTokenizer χρήσεις InputStream αντικείμενα.

Η κατηγορία StringTokenizer

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

Ως λεξικός αναλυτής, StringTokenizer θα μπορούσε να οριστεί επίσημα όπως φαίνεται παρακάτω.

[~ delim1, delim2, ..., delimΝ] :: Διακριτικό 

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

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

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

Χρειάζεστε ένα πρόγραμμα περιήγησης με δυνατότητα Java για να δείτε αυτό το applet.

Θεωρήστε ως παράδειγμα μια συμβολοσειρά, "a, b, d", που μεταβιβάστηκε στο a StringTokenizer αντικείμενο που έχει κατασκευαστεί με κόμμα (,) ως διαχωριστικό χαρακτήρα. Εάν βάλετε αυτές τις τιμές στο applet του γυμναστηρίου παραπάνω, θα δείτε ότι το Tokenizer Το αντικείμενο επιστρέφει τις συμβολοσειρές "a," "b," και "d." Εάν η πρόθεσή σας ήταν να παρατηρήσετε ότι λείπει μια παράμετρος, ενδέχεται να εκπλαγείτε για να μην δείτε καμία ένδειξη για αυτό στην ακολουθία διακριτικών. Η δυνατότητα εντοπισμού διακριτικών που λείπουν ενεργοποιείται από το Return Separator boolean που μπορεί να οριστεί όταν δημιουργείτε ένα Tokenizer αντικείμενο. Με αυτήν την παράμετρο ορίζεται όταν το Tokenizer κατασκευάζεται, κάθε διαχωριστής επιστρέφεται επίσης. Κάντε κλικ στο πλαίσιο ελέγχου για Return Separator στην παραπάνω εφαρμογή και αφήστε τη συμβολοσειρά και το διαχωριστικό μόνα τους. Τώρα το Tokenizer επιστρέφει "a, κόμμα, b, κόμμα, κόμμα και d." Σημειώνοντας ότι έχετε δύο διαχωριστικούς χαρακτήρες διαδοχικά, μπορείτε να προσδιορίσετε ότι ένα διακριτικό "null" συμπεριλήφθηκε στη συμβολοσειρά εισόδου.

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

 / ** * Αναλύστε μια παράμετρο της φόρμας "10,20,30" ως πλειάδα * RGB για μια τιμή χρώματος. * / 1 Color getColor (Όνομα συμβολοσειράς) {2 Δεδομένα συμβολοσειράς; 3 StringTokenizer st; 4 int κόκκινο, πράσινο, μπλε? 5 6 δεδομένα = getParameter (όνομα); 7 if (data == null) 8 επιστροφή null; 9 10 st = νέο StringTokenizer (δεδομένα, ","); 11 δοκιμάστε το {12 red = Integer.parseInt (st.nextToken ()); 13 πράσινο = Integer.parseInt (st.nextToken ()); 14 μπλε = Integer.parseInt (st.nextToken ()); 15} catch (Εξαίρεση e) {16 return null; // (ERROR STATE) δεν μπόρεσε να το αναλύσει 17} 18 επιστροφή νέου χρώματος (κόκκινο, πράσινο, μπλε). // (ΤΕΛΟΣ ΚΑΤΑΣΤΑΣΗΣ) ολοκληρώθηκε. 19} 

Ο παραπάνω κώδικας εφαρμόζει έναν πολύ απλό αναλυτή που διαβάζει τη συμβολοσειρά "αριθμός, αριθμός, αριθμός" και επιστρέφει ένα νέο Χρώμα αντικείμενο. Στη γραμμή 10, ο κώδικας δημιουργεί ένα νέο StringTokenizer αντικείμενο που περιέχει τα δεδομένα παραμέτρων (υποθέστε ότι αυτή η μέθοδος είναι μέρος μιας μικροεφαρμογής) και μια λίστα χαρακτήρων διαχωριστή που αποτελείται από κόμματα. Στη συνέχεια, στις γραμμές 12, 13 και 14, κάθε διακριτικό εξάγεται από τη συμβολοσειρά και μετατρέπεται σε αριθμό χρησιμοποιώντας τον ακέραιο parseInt μέθοδος. Αυτές οι μετατροπές περιβάλλονται από ένα προσπάθησε να πιάσεις μπλοκ σε περίπτωση που οι συμβολοσειρές αριθμών δεν ήταν έγκυροι αριθμοί ή το Tokenizer ρίχνει μια εξαίρεση επειδή έχει εξαντληθεί τα διακριτικά. Εάν μετατραπούν όλοι οι αριθμοί, επιτυγχάνεται η κατάσταση τερματισμού και a Χρώμα το αντικείμενο επιστρέφεται. Αλλιώς επιτυγχάνεται η κατάσταση σφάλματος και μηδενικό επιστρέφεται.

Ένα χαρακτηριστικό του StringTokenizer τάξη είναι ότι στοιβάζεται εύκολα. Κοιτάξτε τη μέθοδο που ονομάζεται getColor παρακάτω, που είναι οι γραμμές 10 έως 18 της παραπάνω μεθόδου.

 / ** * Αναλύστε μια έγχρωμη πλειάδα "r, g, b" σε ένα AWT Χρώμα αντικείμενο. * / 1 Χρώμα getColor (δεδομένα συμβολοσειράς) {2 int κόκκινο, πράσινο, μπλε; 3 StringTokenizer st = νέο StringTokenizer (δεδομένα, ","); 4 δοκιμάστε το {5 red = Integer.parseInt (st.nextToken ()); 6 πράσινο = Integer.parseInt (st.nextToken ()); 7 μπλε = Integer.parseInt (st.nextToken ()); 8} catch (Εξαίρεση e) {9 return null; // (ERROR STATE) δεν μπόρεσε να το αναλύσει 10} 11 επιστροφή νέου χρώματος (κόκκινο, πράσινο, μπλε). // (ΤΕΛΟΣ ΚΑΤΑΣΤΑΣΗΣ) ολοκληρώθηκε. 12} 

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

 / ** * Αναλύστε ένα σύνολο χρωμάτων "r1, g1, b1: r2, g2, b2: ...: rn, gn, bn" σε * μια σειρά αντικειμένων AWT Color. * / 1 Χρώμα [] getColors (Δεδομένα συμβολοσειράς) {2 Διάνυσμα συσσώρευσης = νέο διάνυσμα (); 3 Color cl, αποτέλεσμα []; 4 StringTokenizer st = νέο StringTokenizer (δεδομένα, ":"); 5 ενώ (st.hasMoreTokens ()) {6 cl = getColor (st.nextToken ()); 7 if (cl! = Null) {8 acc.addElement (cl); 9} αλλιώς {10 System.out.println ("Σφάλμα - κακό χρώμα."); 11} 12} 13 εάν (συσσωρευμένο μέγεθος () == 0) 14 επιστροφή null; 15 αποτέλεσμα = νέο χρώμα [συσσωρευμένο μέγεθος ()]; 16 για (int i = 0; i <acc.size (); i ++) {17 αποτέλεσμα [i] = (Χρώμα) acc.elementAt (i); 18} 19 αποτέλεσμα επιστροφής; 20} 

Στην παραπάνω μέθοδο, η οποία είναι ελαφρώς διαφορετική από την getColor μέθοδος, ο κώδικας στις γραμμές 4 έως 12 δημιουργεί ένα νέο Tokenizer για εξαγωγή διακριτικών που περιβάλλεται από το άνω και κάτω τελεία (:). Όπως μπορείτε να διαβάσετε στο σχόλιο τεκμηρίωσης για τη μέθοδο, αυτή η μέθοδος αναμένει ότι οι χρωματικές πλειάδες διαχωρίζονται με άνω και κάτω τελεία. Κάθε κλήση προς επόμενο κουπόνι στο StringTokenizer Η τάξη θα επιστρέψει ένα νέο διακριτικό έως ότου εξαντληθεί η συμβολοσειρά. Οι μάρκες που επιστρέφονται θα είναι οι σειρές των αριθμών που διαχωρίζονται με κόμματα. τροφοδοτούνται αυτές οι συμβολοσειρές συμβόλων getColor, το οποίο στη συνέχεια εξάγει ένα χρώμα από τους τρεις αριθμούς. Δημιουργία νέου StringTokenizer αντικείμενο χρησιμοποιώντας ένα διακριτικό που επιστρέφεται από άλλο StringTokenizer Το αντικείμενο επιτρέπει στον κώδικα ανάλυσης που έχουμε γράψει να είναι λίγο πιο εξελιγμένος για το πώς ερμηνεύει την είσοδο συμβολοσειράς.

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

Η τάξη StreamTokenizer

Όπως υποδηλώνει το όνομα της τάξης, α StreamTokenizer αντικείμενο αναμένει ότι η εισαγωγή του θα προέρχεται από ένα InputStream τάξη. Σαν το StringTokenizer παραπάνω, αυτή η τάξη μετατρέπει τη ροή εισόδου σε κομμάτια που μπορεί να ερμηνεύσει ο κώδικας ανάλυσης, αλλά εκεί τελειώνει η ομοιότητα.

StreamTokenizer είναι ένα με βάση το τραπέζι λεξικο αναλυτης. Αυτό σημαίνει ότι σε κάθε πιθανό χαρακτήρα εισαγωγής έχει μια σημασία και ο σαρωτής χρησιμοποιεί τη σημασία του τρέχοντος χαρακτήρα για να αποφασίσει τι να κάνει. Κατά την εφαρμογή αυτής της τάξης, στους χαρακτήρες εκχωρείται μία από τις τρεις κατηγορίες. Αυτά είναι:

  • Κενός χώρος χαρακτήρες - η λεξική τους σημασία περιορίζεται στο διαχωρισμό των λέξεων

  • Λέξη χαρακτήρες - θα πρέπει να συγκεντρώνονται όταν γειτνιάζουν με άλλον χαρακτήρα

  • Συνήθης χαρακτήρες - θα πρέπει να επιστραφούν αμέσως στον αναλυτή

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

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

επιστρέψτε την τρέχουσα λέξη

ώθηση πίσω χαρακτήρα

αδρανής
κενό διάστημα χαρακτήρας

επιστρέψτε την τρέχουσα λέξη

καταναλώνουν χαρακτήρα

αδρανής

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

Το πρώτο παράδειγμα είναι η επεξεργασία αριθμών. Ορισμένες ακολουθίες χαρακτήρων μπορούν να ερμηνευθούν ως αντιπροσωπευτικές αριθμητικής τιμής. Για παράδειγμα, η ακολουθία των χαρακτήρων 1, 0, 0,., Και 0 που γειτνιάζουν μεταξύ τους στη ροή εισόδου αντιπροσωπεύουν την αριθμητική τιμή 100.0. Όταν όλοι οι χαρακτήρες ψηφίων (0 έως 9), ο χαρακτήρας τελείας (.) Και ο χαρακτήρας μείον (-) καθορίζονται ως μέρος του λέξη σύνολο, το StreamTokenizer μπορεί να κληθεί στην τάξη να ερμηνεύσει τη λέξη που πρόκειται να επιστρέψει ως πιθανός αριθμός. Η ρύθμιση αυτής της λειτουργίας επιτυγχάνεται καλώντας το parseNumbers μέθοδο στο αντικείμενο tokenizer που δημιουργήσατε (αυτή είναι η προεπιλογή). Εάν ο αναλυτής είναι σε κατάσταση συσσώρευσης και ο επόμενος χαρακτήρας θα δεν γίνετε μέρος ενός αριθμού, η τρέχουσα συσσωρευμένη λέξη ελέγχεται για να δείτε εάν είναι έγκυρος αριθμός. Εάν είναι έγκυρο, επιστρέφεται και ο σαρωτής μετακινείται στην επόμενη κατάλληλη κατάσταση.

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