Analisis leksikal dan Java: Bahagian 1

Analisis dan penghuraian leksikal

Semasa menulis aplikasi Java, salah satu perkara yang lebih biasa yang anda perlukan untuk menghasilkan adalah penghurai. Penyusun terdiri dari yang sederhana hingga yang kompleks dan digunakan untuk segalanya dari melihat pilihan baris perintah hingga menafsirkan kod sumber Java. Dalam edisi JavaWorld Disember, saya menunjukkan kepada anda Jack, penjana penghurai automatik yang mengubah spesifikasi tatabahasa tingkat tinggi menjadi kelas Java yang menerapkan penghurai yang dijelaskan oleh spesifikasi tersebut. Bulan ini saya akan menunjukkan kepada anda sumber yang disediakan oleh Java untuk menulis penganalisis dan penghurai leksikal yang disasarkan. Penyusun yang agak sederhana ini mengisi jurang antara perbandingan rentetan sederhana dan tatabahasa kompleks yang disusun oleh Jack.

Tujuan penganalisis leksikal adalah untuk mengambil aliran watak input dan menyahkodnya menjadi token tahap yang lebih tinggi yang dapat difahami oleh pengurai. Penyusun menggunakan output penganalisis leksikal dan beroperasi dengan menganalisis urutan token yang dikembalikan. Pengurai memadankan urutan ini dengan keadaan akhir, yang mungkin merupakan salah satu daripada banyak keadaan akhir. Negeri akhir menentukan matlamatpengurai. Apabila keadaan akhir dicapai, program yang menggunakan penghurai melakukan beberapa tindakan - sama ada mengatur struktur data atau menjalankan beberapa kod khusus tindakan. Selain itu, penghurai dapat mengesan - dari urutan token yang telah diproses - apabila keadaan akhir yang sah tidak dapat dicapai; pada ketika itu penghurai mengenal pasti keadaan semasa sebagai keadaan ralat. Terserah kepada aplikasi untuk memutuskan tindakan apa yang harus diambil ketika pengurai mengenal pasti keadaan akhir atau keadaan ralat.

Pangkalan kelas Java standard merangkumi beberapa kelas penganalisis leksikal, namun tidak menentukan kelas penghurai tujuan umum. Di ruangan ini saya akan melihat secara mendalam mengenai penganalisis leksikal yang disertakan dengan Java.

Penganalisis leksikal Java

Spesifikasi Bahasa Java, versi 1.0.2, mendefinisikan dua kelas penganalisis leksikal, StringTokenizerdan StreamTokenizer. Dari namanya anda dapat menyimpulkan bahawa StringTokenizermenggunakan Stringobjek sebagai inputnya, dan StreamTokenizermenggunakan InputStreamobjek.

Kelas StringTokenizer

Dari dua kelas penganalisis leksikal yang ada, yang paling mudah difahami adalah StringTokenizer. Semasa anda membina StringTokenizerobjek baru , kaedah konstruktor secara nominal mengambil dua nilai - rentetan input dan tali pembatas. Kelas kemudian membina urutan token yang mewakili watak antara watak pembatas.

Sebagai penganalisis leksikal, StringTokenizerdapat didefinisikan secara formal seperti yang ditunjukkan di bawah ini.

[~ delim1, delim2, ..., delim N ] :: Token

Definisi ini terdiri daripada ungkapan biasa yang sesuai dengan setiap watak kecuali watak pembatas. Semua watak sepadan yang berdekatan dikumpulkan menjadi satu token dan dikembalikan sebagai Token.

Penggunaan kelas yang paling biasa StringTokenizeradalah untuk memisahkan sekumpulan parameter - seperti senarai nombor yang dipisahkan dengan koma. StringTokenizersangat sesuai dalam peranan ini kerana membuang pemisah dan mengembalikan data. The StringTokenizerkelas juga menyediakan satu mekanisme untuk mengenal pasti senarai di mana terdapat "null" token. Anda akan menggunakan token nol dalam aplikasi di mana beberapa parameter mempunyai nilai lalai atau tidak diperlukan untuk hadir dalam semua keadaan.

Applet di bawah adalah StringTokenizersenaman sederhana . Sumber untuk applet StringTokenizer ada di sini. Untuk menggunakan applet, ketikkan beberapa teks yang akan dianalisis ke kawasan rentetan input, kemudian ketikkan rentetan yang terdiri dari watak pemisah di daerah String Separator. Akhirnya, klik pada Tokenize! butang. Hasilnya akan muncul dalam senarai token di bawah rentetan input dan akan disusun sebagai satu token setiap baris.

Anda memerlukan penyemak imbas berkemampuan Java untuk melihat applet ini.

Pertimbangkan sebagai contoh rentetan, "a, b, d", diteruskan ke StringTokenizerobjek yang telah dibangun dengan koma (,) sebagai watak pemisah. Sekiranya anda meletakkan nilai-nilai ini di applet latihan di atas, anda akan melihat bahawa Tokenizerobjek mengembalikan rentetan "a," "b," dan "d." Sekiranya niat anda adalah untuk mengetahui bahawa satu parameter hilang, anda mungkin terkejut kerana tidak ada petunjuk mengenai ini dalam urutan token. Keupayaan untuk mengesan token yang hilang diaktifkan oleh Return Separator boolean yang dapat ditetapkan ketika anda membuat Tokenizerobjek. Dengan parameter ini ditetapkan ketika Tokenizerdibangun, setiap pemisah juga dikembalikan. Klik kotak pilihan untuk Return Separator di applet di atas, dan biarkan tali dan pemisah bersendirian. SekarangTokenizermengembalikan "a, koma, b, koma, koma, dan d." Dengan menyatakan bahawa anda mendapat dua watak pemisah secara berurutan, anda dapat menentukan bahawa token "null" disertakan dalam rentetan input.

Caranya untuk berjaya digunakan StringTokenizerdalam penghurai adalah menentukan input sedemikian rupa sehingga watak pembatas tidak muncul dalam data. Jelas anda dapat mengelakkan sekatan ini dengan merancangnya dalam aplikasi anda. Definisi kaedah di bawah ini dapat digunakan sebagai bagian dari applet yang menerima warna dalam bentuk nilai merah, hijau, dan biru dalam aliran parameternya.

/ ** * Huraikan parameter bentuk "10,20,30" sebagai tuple * RGB untuk nilai warna. * / 1 Warna getColor (String name) {2 String data; 3 StringTokenizer st; 4 int merah, hijau, biru; 5 6 data = getParameter (nama); 7 jika (data == null) 8 kembali nol; 9 10 st = StringTokenizer baru (data, ","); 11 cuba {12 red = Integer.parseInt (st.nextToken ()); 13 hijau = Integer.parseInt (st.nextToken ()); 14 biru = Integer.parseInt (st.nextToken ()); 15} tangkapan (Pengecualian e) {16 pulangan nol; // (NEGERI KESALAHAN) tidak dapat menguraikannya 17} 18 mengembalikan Warna baru (merah, hijau, biru); // (NEGERI TAMAT) selesai. 19}

Kod di atas menerapkan penghurai yang sangat sederhana yang membaca rentetan "nombor, nombor, nombor" dan mengembalikan Colorobjek baru . Pada baris 10, kod membuat StringTokenizerobjek baru yang berisi data parameter (anggap kaedah ini adalah sebahagian dari applet), dan senarai watak pemisah yang terdiri dari koma. Kemudian pada baris 12, 13, dan 14, setiap token diekstrak dari rentetan dan diubah menjadi nombor menggunakan parseIntkaedah Integer . Penukaran ini dikelilingi oleh try/catchblok sekiranya rentetan nombor itu bukan nombor yang sah atau Tokenizerpengecualian kerana pengecualiannya sudah habis. Sekiranya semua nombor berubah, keadaan akhir tercapai dan Colorobjek dikembalikan; jika tidak, keadaan ralat dicapai dan nol dikembalikan.

Salah satu ciri StringTokenizerkelas adalah bahawa ia mudah disusun. Lihat kaedah yang disebutkan getColordi bawah, iaitu garis 10 hingga 18 kaedah di atas.

/ ** * Uraikan tuple warna "r, g, b" ke dalam Colorobjek AWT . * / 1 Warna getColor (String data) {2 int merah, hijau, biru; 3 StringTokenizer st = StringTokenizer baru (data, ","); 4 cuba {5 merah = Integer.parseInt (st.nextToken ()); 6 hijau = Integer.parseInt (st.nextToken ()); 7 biru = Integer.parseInt (st.nextToken ()); 8} tangkapan (Pengecualian e) {9 return null; // (NEGERI KESALAHAN) tidak dapat menguraikannya 10} 11 mengembalikan Warna baru (merah, hijau, biru); // (NEGERI TAMAT) selesai. 12}

Penghurai yang sedikit lebih kompleks ditunjukkan dalam kod di bawah. Penghurai ini dilaksanakan dalam metode getColors, yang didefinisikan untuk mengembalikan array Colorobjek.

/ ** * Uraikan sekumpulan warna "r1, g1, b1: r2, g2, b2: ...: rn, gn, bn" ke dalam * array objek AWT Color. * / 1 Warna [] getColors (String data) {2 Vektor akum = Vektor baru (); 3 Warna cl, hasil []; 4 StringTokenizer st = StringTokenizer baru (data, ":"); 5 while (st.hasMoreTokens ()) {6 cl = getColor (st.nextToken ()); 7 jika (cl! = Null) {8 acc.addElement (cl); 9} lain {10 System.out.println ("Ralat - warna buruk."); 11} 12} 13 jika (acc.size () == 0) 14 return null; 15 hasil = Warna baru [acc.size ()]; 16 untuk (int i = 0; i <acc.size (); i ++) {17 hasil [i] = (Warna) acc.elementAt (i); 18} 19 keputusan pulangan; 20}

Dalam kaedah di atas, yang hanya sedikit berbeza dari getColorkaedah tersebut, kod pada baris 4 hingga 12 membuat Tokenizertoken baru untuk mengekstrak yang dikelilingi oleh watak titik dua (:). Seperti yang anda dapat baca dalam komen dokumentasi untuk kaedah tersebut, kaedah ini mengharapkan tupel warna dipisahkan oleh titik dua. Setiap panggilan ke nextTokendalam StringTokenizerkelas akan kembali tanda baru sehingga tali telah habis. Token yang dikembalikan adalah rentetan nombor yang dipisahkan dengan koma; tali token ini diberi makan getColor, yang kemudian mengeluarkan warna dari tiga nombor tersebut. Membuat StringTokenizerobjek baru menggunakan token yang dikembalikan oleh StringTokenizerobjek lain membolehkan kod penghurai yang kami tulis sedikit lebih canggih tentang bagaimana ia menafsirkan input rentetan.

Seperti yang berguna, anda akhirnya akan menghabiskan kemampuan StringTokenizerkelas dan harus beralih kepada kakaknya StreamTokenizer.

Kelas StreamTokenizer

Seperti yang ditunjukkan oleh nama kelas, StreamTokenizerobjek mengharapkan inputnya berasal dari InputStreamkelas. Seperti perkara di StringTokenizeratas, kelas ini mengubah aliran input menjadi potongan yang dapat ditafsirkan oleh kod penghuraian anda, tetapi di situlah kesamaan berakhir.

StreamTokenizeradalah penganalisis leksikal berdasarkan jadual . Ini bermaksud bahawa setiap kemungkinan watak input diberi makna, dan pengimbas menggunakan kepentingan watak semasa untuk memutuskan apa yang harus dilakukan. Dalam pelaksanaan kelas ini, watak diberikan satu daripada tiga kategori. Ini adalah:

  • Ruang kosong aksara - kepentingan leksikal mereka adalah terhad kepada kata-kata memisahkan

  • Karakter kata - mereka harus digabungkan ketika mereka berdekatan dengan watak kata lain

  • Watak biasa - mereka harus dikembalikan segera ke penghurai

Bayangkan pelaksanaan kelas ini sebagai mesin keadaan sederhana yang mempunyai dua keadaan - terbiar dan terkumpul . Di setiap keadaan input adalah watak dari salah satu kategori di atas. Kelas membaca watak, memeriksa kategorinya dan melakukan beberapa tindakan, dan beralih ke keadaan seterusnya. Jadual berikut menunjukkan mesin keadaan ini.

Nyatakan Masukan Tindakan Negeri baru
terbiar watak perkataan tolak watak semula terkumpul
watak biasa watak kembali terbiar
watak ruang kosong memakan watak terbiar
terkumpul watak perkataan tambah pada perkataan semasa terkumpul
watak biasa

kembalikan perkataan semasa

tolak watak semula

terbiar
watak ruang kosong

kembalikan perkataan semasa

memakan watak

terbiar

Di atas mekanisme sederhana ini StreamTokenizerkelas menambah beberapa heuristik. Ini termasuk pemprosesan nombor, pemprosesan rentetan kutipan, pemprosesan komen, dan pemprosesan akhir baris.

The first example is number processing. Certain character sequences can be interpreted as representing a numerical value. For example, the sequence of characters 1, 0, 0, ., and 0 adjacent to each other in the input stream represent the numerical value 100.0. When all of the digit characters (0 through 9), the dot character (.), and the minus (-) character are specified as being part of the word set, the StreamTokenizer class can be told to interpret the word it is about to return as a possible number. Setting this mode is achieved by calling the parseNumbers method on the tokenizer object that you instantiated (this is the default). If the analyzer is in the accumulate state, and the next character would not be part of a number, the currently accumulated word is checked to see if it is a valid number. If it is valid, it is returned, and the scanner moves to the next appropriate state.

Contoh seterusnya adalah pemprosesan rentetan yang dipetik. Selalunya wajar untuk memasukkan rentetan yang dikelilingi oleh watak petikan (biasanya dua kali (") atau tanda kutip tunggal (')) sebagai token tunggal. StreamTokenizerKelas ini membolehkan anda menentukan sebarang watak sebagai watak sebut harga. Secara lalai mereka adalah watak petikan tunggal (') dan petikan ganda ("). Mesin keadaan diubah untuk menggunakan watak dalam keadaan terkumpul sehingga watak petikan lain atau watak akhir baris diproses. Untuk membolehkan anda memetik watak petikan, penganalisis memperlakukan watak petikan yang didahului oleh garis miring belakang (\) dalam aliran input dan di dalam petikan sebagai watak kata.