Programmēšana

Leksiskā analīze, 2. daļa: Lietojumprogrammas izveide

Pagājušajā mēnesī es apskatīju klases, kuras Java nodrošina pamata leksiskās analīzes veikšanai. Šajā mēnesī es izietu cauri vienkāršai lietojumprogrammai, kas izmanto StreamTokenizer ieviest interaktīvu kalkulatoru.

Lai īsi pārskatītu pagājušā mēneša rakstu, ir divas leksisko-analizatoru klases, kas iekļautas standarta Java izplatīšanā: StringTokenizer un StreamTokenizer. Šie analizatori pārveido ievadi atsevišķos marķieros, kurus parsētājs var izmantot, lai izprastu doto ievadi. Parsētājs ievieš gramatiku, kas tiek definēta kā viens vai vairāki mērķa stāvokļi, kas sasniegti, redzot dažādas žetonu secības. Kad parsera mērķis ir sasniegts, tas veic kādu darbību. Kad parsētājs konstatē, ka, ņemot vērā pašreizējo marķieru secību, nav iespējamu mērķa stāvokļu, tas to definē kā kļūdas stāvokli. Kad parsētājs sasniedz kļūdas stāvokli, tas veic atkopšanas darbību, kas parsētāju atgriež līdz punktam, kurā tas var atsākt parsēšanu. Parasti tas tiek īstenots, patērējot marķierus, līdz parsētājs atgriežas derīgā sākuma punktā.

Pagājušajā mēnesī es jums parādīju dažas metodes, kurās izmantoja a StringTokenizer parsēt dažus ievades parametrus. Šomēnes parādīšu lietojumprogrammu, kurā tiek izmantots StreamTokenizer objekts, lai parsētu ievades straumi un ieviestu interaktīvu kalkulatoru.

Lietojumprogrammas veidošana

Mūsu piemērs ir interaktīvs kalkulators, kas ir līdzīgs komandai Unix bc (1). Kā redzēsiet, tas nospiež StreamTokenizer klases līdz leksiskā analizatora lietderības robežai. Tādējādi tas labi parāda, kur var novilkt robežu starp "vienkāršiem" un "sarežģītiem" analizatoriem. Šis piemērs ir Java lietojumprogramma, un tāpēc tas vislabāk darbojas no komandrindas.

Kā ātrs savu iespēju apkopojums, kalkulators pieņem izteicienus formā

[mainīgais nosaukums] "=" izteiksme 

Mainīgā nosaukums nav obligāts, un tas var būt jebkura rakstzīmju virkne noklusējuma vārdu diapazonā. (Lai atsvaidzinātu atmiņu ar šīm rakstzīmēm, varat izmantot pagājušā mēneša raksta exerciser sīklietotni.) Ja mainīgā nosaukums tiek izlaists, izteiksmes vērtība vienkārši tiek izdrukāta. Ja ir mainīgā nosaukums, mainīgajam tiek piešķirta izteiksmes vērtība. Kad mainīgie ir piešķirti, tos var izmantot vēlākos izteicienos. Tādējādi viņi aizpilda "atmiņu" lomu uz moderna rokas kalkulatora.

Izteiksme sastāv no operandiem skaitlisko konstanšu veidā (dubultprecizitātes, peldošā komata konstantes) vai mainīgo nosaukumos, operatoros un iekavās konkrētu aprēķinu grupēšanai. Juridiskie operatori ir saskaitīšana (+), atņemšana (-), reizināšana (*), dalīšana (/), bitu AND un (&), bitu kustība OR (|), bitu kustība XOR (#), eksponēšana (^) un viennozīmīga noliegšana vai nu ar mīnusu (-) divnieku papildinājuma rezultātam, vai ar sprādzienu (!) tiem, kuri papildina rezultātu.

Papildus šiem paziņojumiem mūsu kalkulatora lietojumprogramma var veikt arī vienu no četrām komandām: "dump", "clear", "help" un "quit". The izgāzt komanda izdrukā visus pašreiz definētos mainīgos, kā arī to vērtības. The skaidrs komanda izdzēš visus pašlaik definētos mainīgos. The palīdzība komanda izdrukā dažas palīdzības teksta rindas, lai sāktu lietotāju. The atmest komanda liek lietojumprogrammai iziet.

Visu lietojumprogrammas piemēru veido divi parsētāji - viens komandām un paziņojumiem un viens izteicieniem.

Komandu parsētāja izveide

Komandu parsētājs tiek ieviests lietojumprogrammas klasē, piemēram, STExample.java. (Skatiet koda rādītāju sadaļā Resursi.) galvenais metode šai klasei ir definēta zemāk. Es jums izstaigāšu gabalus.

 1 publiskais statiskais tukšums main (String args []) izmet IOException {2 Hashtable mainīgie = new Hashtable (); 3 StreamTokenizer st = jauns StreamTokenizer (System.in); 4 st.eolIsNozīmīgs (patiess); 5. st.lowerCaseMode (patiess); 6. st. NeparastsChar ('/'); 7. st. NeparastsChar ('-'); 

Virs kodā pirmā lieta, ko es daru, ir piešķirt a java.util.Hashtable klase, lai turētu mainīgos. Pēc tam es piešķiru a StreamTokenizer un nedaudz pielāgojiet to pēc noklusējuma. Izmaiņu pamatojums ir šāds:

  • eolIsNozīmīgs ir iestatīts uz taisnība lai marķieris atgrieztu norādi par rindas beigām. Es izmantoju rindas beigas kā punktu, kur beidzas izteiksme.

  • lowerCaseMode ir iestatīts uz taisnība tā, lai mainīgo nosaukumi vienmēr tiktu atgriezti ar mazajiem burtiem. Tādējādi mainīgo nosaukumi nav reģistrjutīgi.

  • Slīpsvītras raksturs (/) ir iestatīts kā parasts raksturs, lai to neizmantotu, lai norādītu komentāra sākumu, un to var izmantot kā sadalīšanas operatoru.

  • Mīnusa zīme (-) ir iestatīta kā parasta rakstzīme, lai virkne "3-3" segmentētos trīs žetonos - "3", "-" un "3" - nevis tikai "3" un "-3." (Atcerieties, ka pēc noklusējuma numuru parsēšana ir iestatīta uz “ieslēgts”.)

Kad marķieris ir iestatīts, komandu parsētājs darbojas bezgalīgā ciklā (līdz brīdim, kad tas atpazīst komandu "quit", kurā brīdī tas iziet). Tas parādīts zemāk.

 8 kamēr (taisnība) {9 izteiksmes rez; 10 int c = StreamTokenizer.TT_EOL; 11 virkne varName = null; 12 13 System.out.println ("Ievadiet izteiksmi ..."); 14 mēģiniet {15 kamēr (taisnība) {16 c = st.nextToken (); 17 if (c == StreamTokenizer.TT_EOF) {18 System.exit (1); 19} cits, ja (c == StreamTokenizer.TT_EOL) {20 turpinās; 21} else if (c == StreamTokenizer.TT_WORD) {22 if (st.sval.compareTo ("dump") == 0) {23 dumpVariables (mainīgie); 24 turpināt; 25} else if (st.sval.compareTo ("notīrīt") == 0) {26 mainīgie = new Hashtable (); 27 turpināt; 28} else if (st.sval.compareTo ("quit") == 0) {29 System.exit (0); 30} else if (st.sval.compareTo ("exit") == 0) {31 System.exit (0); 32} else if (st.sval.compareTo ("help") == 0) {33 help (); 34 turpināt; 35} 36 varName = st.sval; 37 c = st.nextToken (); 38} 39 pārtraukums; 40} 41 if (c! = '=') {42 mest jaunu SyntaxError ("trūkst sākotnējās '=' zīmes."); 43} 

Kā redzat 16. rindā, pirmais marķieris tiek izsaukts, izsaucot nextToken uz StreamTokenizer objekts. Tas atgriež vērtību, kas norāda skenētā marķiera veidu. Atgriešanās vērtība vai nu būs viena no definētajām konstantēm StreamTokenizer klase vai tā būs rakstzīmes vērtība. "Meta" marķierus (tos, kas nav tikai rakstzīmju vērtības) definē šādi:

  • TT_EOF - Tas norāda, ka esat ievades straumes beigās. Atšķirībā no StringTokenizer, tur nav hasMoreTokens metodi.

  • TT_EOL - Tas jums saka, ka objekts tikko ir pabeidzis rindas beigu secību.

  • TT_NUMBER - Šis marķiera tips parsētāja kodam norāda, ka ievadā ir redzams skaitlis.

  • TT_WORD - Šis marķiera tips norāda, ka ir skenēts viss “vārds”.

Ja rezultāts nav viena no iepriekš minētajām konstantēm, tā ir vai nu rakstzīmes vērtība, kas apzīmē skenēto rakstzīmi “parastajā” rakstzīmju diapazonā, vai arī viena no jūsu iestatītajām pēdiņu rakstzīmēm. (Manā gadījumā nav iestatīts neviens pēdiņu raksturs.) Ja rezultāts ir viena no jūsu piedāvātajām rakstzīmēm, citēto virkni var atrast virknes instances mainīgajā sval no StreamTokenizer objekts.

Kods no 17. līdz 20. rindai attiecas uz rindas beigām un faila beigu norādēm, savukārt 21. rindiņā tiek ņemta klauzula if, ja tika atgriezts vārda marķieris. Šajā vienkāršajā piemērā vārds ir vai nu komanda, vai mainīgā nosaukums. 22. līdz 35. rindā ir četras iespējamās komandas. Ja tiek sasniegta 36. rinda, tad tam jābūt mainīgā nosaukumam; līdz ar to programma saglabā mainīgā nosaukuma kopiju un iegūst nākamo marķieri, kam jābūt vienādības zīmei.

Ja 41. rindā marķieris nebija vienlīdzības zīme, mūsu vienkāršais parsētājs nosaka kļūdas stāvokli un izmet izņēmumu, lai to signalizētu. Es izveidoju divus vispārīgus izņēmumus, SyntaxError un ExecError, lai atšķirtu parsēšanas laika kļūdas no izpildlaika kļūdām. The galvenais metode turpinās ar 44. rindiņu zemāk.

44 res = ParseExpression.expression (st); 45} catch (SyntaxError se) {46 res = null; 47 varName = null; 48 System.out.println ("\ n Konstatēta sintakses kļūda! -" + se.getMsg ()); 49 kamēr (c! = StreamTokenizer.TT_EOL) 50 c = st.nextToken (); 51 turpināt; 52} 

44. rindā izteiksme pa labi no vienādības zīmes tiek parsēta ar izteiksmes parsētāju, kas definēts ParseExpression klasē. Ņemiet vērā, ka rindas no 14 līdz 44 ir ietītas mēģinājuma / uztveršanas blokā, kas notver sintakses kļūdas un ar tām nodarbojas. Kad tiek atklāta kļūda, parsētāja atkopšanas darbība ir patērēt visus marķierus līdz pat nākamajam rindas beigu marķierim. Tas parādīts iepriekš 49. un 50. rindā.

Šajā brīdī, ja izņēmums netika izmests, lietojumprogramma ir veiksmīgi parsējusi paziņojumu. Pēdējā pārbaude ir redzēt, ka nākamais marķieris ir rindas beigas. Ja tā nav, kļūda nav atklāta. Visizplatītākā kļūda būs neatbilstošās iekavas. Šī pārbaude ir parādīta zemāk redzamā koda 53. līdz 60. rindā.

53 c = st.nextToken (); 54 if (c! = StreamTokenizer.TT_EOL) {55 if (c == ')') 56 System.out.println ("\ nSintakses kļūda konstatēta! - Daudziem aizvēršanas parēniem."); 57 cits 58 System.out.println ("\ nPatiesa marķiera ievade -" + c); 59 kamēr (c! = StreamTokenizer.TT_EOL) 60 c = st.nextToken (); 61} cits { 

Kad nākamais marķieris ir rindas beigas, programma izpilda 62. līdz 69. rindu (parādīts zemāk). Šajā metodes sadaļā tiek novērtēta parsētā izteiksme. Ja mainīgā nosaukums tika iestatīts 36. rindā, rezultāts tiek saglabāts simbolu tabulā. Jebkurā gadījumā, ja netiek izmests neviens izņēmums, izteiksme un tās vērtība tiek drukāta plūsmā System.out, lai jūs varētu redzēt, ko atšifrēja parsētājs.

62 mēģiniet {63 Double z; 64 System.out.println ("Parsētā izteiksme:" + res.unparse ()); 65 z = jauns Double (res.value (mainīgie)); 66 System.out.println ("Vērtība ir:" + z); 67 if (varName! = Null) {68 mainīgie.put (varName, z); 69 System.out.println ("Piešķirts:" + varName); 70} 71} catch (ExecError ee) {72 System.out.println ("Izpildes kļūda," + ee.getMsg () + "!"); 73} 74} 75} 76} 

Iekš STPiemērs klase, StreamTokenizer tiek izmantots komandu procesora parsētājā. Šāda veida parsētājus parasti izmanto čaulas programmā vai jebkurā situācijā, kurā lietotājs interaktīvi izsniedz komandas. Otrais parsētājs ir iekapsulēts ParseExpression klasē. (Pilnu avotu skatiet sadaļā Resursi.) Šī klase parsē kalkulatora izteiksmes un tiek izsaukta iepriekš 44. rindā. Tas ir šeit StreamTokenizer saskaras ar savu visstingrāko izaicinājumu.

Izteiksmes parsētāja veidošana

Kalkulatora izteicienu gramatika nosaka formas "[vienums] operators [vienums]" algebras sintaksi. Šāda veida gramatika parādās atkal un atkal, un to sauc par operators gramatika. Ērts apzīmējums operatora gramatikai ir:

id ("OPERATOR" id) * 

Iepriekš minētais kods būtu šāds: "ID termināls, kam seko nulle vai vairāk operatora ID kopas gadījumu". The StreamTokenizer klase, šķiet, ir diezgan ideāla, lai analizētu šādas plūsmas, jo dizains dabiski sadala ievades plūsmu vārdu, numuru, un parasts raksturs žetoni. Kā es jums parādīšu, tā ir taisnība līdz punktam.

The ParseExpression klase ir vienkāršs, rekursīvas izcelsmes izteicienu parsētājs tieši no bakalaura sastādītāja dizaina klases. The Izteiksme metode šajā klasē ir definēta šādi:

 1 statiskā izteiksmes izteiksme (StreamTokenizer st) izmet SyntaxError {2 izteiksmes rezultāts; 3 Būla vērtība = nepatiesa; 4 5 rezultāts = summa (st); 6 kamēr (! Izdarīts) {7 mēģiniet {8 pārslēgt (st.nextToken ()) 9 gadījums '&': 10 rezultāts = jauna izteiksme (OP_AND, rezultāts, summa (st)); 11 pārtraukums; 12 gadījums '23} catch (IOException ioe) {24 mest jaunu SyntaxError ("Got I / O izņēmums."); 25} 26} 27 atgriešanās rezultāts; 28} 
$config[zx-auto] not found$config[zx-overlay] not found