Programavimas

Leksinė analizė, 2 dalis. Programos kūrimas

Praėjusį mėnesį peržiūrėjau klases, kurias „Java“ teikia pagrindinei leksinei analizei atlikti. Šį mėnesį apžiūrėsiu paprastą programą, kuri naudojasi „StreamTokenizer“ įdiegti interaktyvią skaičiuoklę.

Trumpai apžvelgiant praėjusio mėnesio straipsnį, yra dvi leksikos analizatorių klasės, kurios yra įtrauktos į standartinį „Java“ paskirstymą: „StringTokenizer“ ir „StreamTokenizer“. Šie analizatoriai konvertuoja savo įvestį į atskirus žetonus, kuriuos analizatorius gali naudoti tam, kad suprastų tam tikrą įvestį. Analizatorius įgyvendina gramatiką, kuri apibrėžiama kaip viena ar kelios tikslo būsenos, pasiektos matant įvairias žetonų sekas. Pasiekus analizatoriaus tikslo būseną, jis atlieka tam tikrą veiksmą. Kai analizatorius nustato, kad nėra galimų tikslo būsenų, atsižvelgiant į dabartinę žetonų seką, jis tai apibrėžia kaip klaidos būseną. Kai analizatorius pasiekia klaidos būseną, jis atlieka atkūrimo veiksmą, kuris grąžina analizatorių į tašką, kuriame jis gali vėl pradėti analizuoti. Paprastai tai įgyvendinama vartojant žetonus, kol analizatorius grįš į galiojantį pradinį tašką.

Praėjusį mėnesį parodžiau jums keletą metodų, kuriuose buvo naudojamas a „StringTokenizer“ išanalizuoti kai kuriuos įvesties parametrus. Šį mėnesį aš jums parodysiu programą, kurioje naudojamas a „StreamTokenizer“ objektas išanalizuoti įvesties srautą ir įdiegti interaktyvią skaičiuoklę.

Programos kūrimas

Mūsų pavyzdys yra interaktyvus skaičiuotuvas, panašus į komandą „Unix bc (1)“. Kaip pamatysite, jis stumia „StreamTokenizer“ klasę iki pat jo, kaip leksinio analizatoriaus, naudingumo ribos. Taigi tai gerai parodo, kur galima nubrėžti ribą tarp „paprastų“ ir „sudėtingų“ analizatorių. Šis pavyzdys yra „Java“ programa, todėl geriausiai veikia iš komandinės eilutės.

Skaičiuoklė kaip greitą savo galimybių santrauką priima formos išraiškas

[kintamo pavadinimo] "=" išraiška 

Kintamojo pavadinimas yra neprivalomas ir gali būti bet kokia simbolių eilutė numatytame žodžių diapazone. (Jei norite atnaujinti šių simbolių atmintį, galite naudoti praėjusio mėnesio straipsnio treniruoklio programėlę.) Jei kintamojo pavadinimas praleidžiamas, išraiškos reikšmė tiesiog atspausdinama. Jei yra kintamojo pavadinimas, išraiškos reikšmė priskiriama kintamajam. Priskyrus kintamuosius, juos galima naudoti vėlesnėse išraiškose. Taigi jie užpildo „prisiminimų“ vaidmenį modernioje rankinėje skaičiuoklėje.

Išraiška susideda iš operandų skaitinių konstantų pavidalu (dvigubo tikslumo, slankiojo kablelio konstantos) arba kintamųjų pavadinimų, operatorių ir skliaustų tam tikrų skaičiavimų grupavimui. Teisiniai operatoriai yra sudėjimas (+), atimimas (-), dauginimas (*), dalijimas (/), bitų AND (&), bitų OR (|), bitų XOR (#), eksponentų (^) ir vienarūšių neigimų atlikimas arba atėmus (-) dviese papildymo rezultatą, arba sprogimą (!), jei rezultatas yra papildomas.

Be šių teiginių, mūsų skaičiuoklės programa taip pat gali atlikti vieną iš keturių komandų: „dump“, „clear“, „help“ ir „quit“. išpilti komanda išspausdina visus šiuo metu apibrėžtus kintamuosius ir jų reikšmes. aišku komanda ištrina visus šiuo metu apibrėžtus kintamuosius. pagalba komanda išspausdina kelias pagalbos teksto eilutes, kad vartotojas galėtų pradėti. mesti komanda priverčia programą išeiti.

Visą programos pavyzdį sudaro du analizatoriai - vienas komandoms ir sakiniams, kitas išraiškoms.

Komandų analizatoriaus kūrimas

Komandų analizatorius yra įdiegtas STExample.java pavyzdžio programų klasėje. (Žr. Išteklių skyrių, kur rasite žymeklį į kodą.) pagrindinis tos klasės metodas yra apibrėžtas toliau. Aš eisiu jums per gabalus.

 1 viešasis statinis tuštumas main (String args []) išmeta IOException {2 Hashtable kintamieji = new Hashtable (); 3 „StreamTokenizer st“ = naujas „StreamTokenizer“ (System.in); 4 st.eolIsS Reikšmingas (tiesa); 5 st.lowerCaseMode (true); 6 Šv. Ypatingas Chhar ('/'); 7-oji nepaprastojiChar ('-'); 

Pirmiau pateiktame kode pirmiausia paskiriu a java.util.Hashtable klasėje, kad būtų laikomi kintamieji. Po to skiriu a „StreamTokenizer“ ir šiek tiek pakoreguokite jį pagal numatytuosius nustatymus. Pokyčių pagrindimas yra toks:

  • eolIsSvarbus yra nustatytas į tiesa kad žymeklis grąžins eilutės pabaigos nuorodą. Aš naudoju eilutės pabaigą kaip tašką, kur baigiasi išraiška.

  • lowerCaseMode yra nustatytas į tiesa kad kintamųjų pavadinimai visada būtų grąžinti mažosiomis raidėmis. Tokiu būdu kintamųjų pavadinimai neskiria didžiųjų ir mažųjų raidžių.

  • Pasvirasis ženklas (/) nustatomas kaip įprastas simbolis, todėl jis nebus naudojamas komentaro pradžiai nurodyti ir gali būti naudojamas kaip padalijimo operatorius.

  • Minuso simbolis (-) yra įprastas simbolis, todėl eilutė „3-3“ bus suskirstyta į tris žetonus - „3“, „-“ ir „3“, o ne tik į „3“ ir "-3". (Atminkite, kad pagal numatytuosius nustatymus skaičių analizavimas nustatytas į „įjungta“.)

Kai žetografas yra nustatytas, komandų analizatorius veikia begaline kilpa (kol atpažins komandą „quit“, kurioje vietoje ji išeina). Tai parodyta žemiau.

 8 o (tiesa) {9 Išraiška res; 10 int c = „StreamTokenizer.TT_EOL“; 11 eilutė varName = null; 12 13 System.out.println ("Įveskite išraišką ..."); 14 pabandykite {15, kol (tiesa) {16 c = st.nextToken (); 17 if (c == StreamTokenizer.TT_EOF) {18 System.exit (1); 19} else if (c == StreamTokenizer.TT_EOL) {20 toliau; 21} else if (c == StreamTokenizer.TT_WORD) {22 if (st.sval.compareTo ("dump") == 0) {23 dumpVariables (kintamieji); 24 tęsti; 25} else if (st.sval.compareTo ("clear") == 0) {26 kintamieji = new Hashtable (); 27 toliau; 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 ("pagalba") == 0) {33 pagalba (); 34 tęsti; 35} 36 varName = st.sval; 37 c = st.nextToken (); 38} 39 pertrauka; 40} 41 if (c! = '=') {42 mesti naują „SyntaxError“ („trūksta pradinio '=' ženklo."); 43} 

Kaip matote 16 eilutėje, pirmasis žetonas iškviečiamas iškviečiant kitasToken ant „StreamTokenizer“ objektas. Tai pateikia vertę, nurodančią nuskaityto prieigos rakto rūšį. Grąžinimo vertė bus viena iš apibrėžtųjų konstantų „StreamTokenizer“ klasę arba tai bus simbolio reikšmė. „Meta“ žetonai (tie, kurie nėra vien simbolių reikšmės) apibrėžiami taip:

  • TT_EOF - Tai rodo, kad esate įvesties srauto pabaigoje. Skirtingai „StringTokenizer“, nėra hasMoreTokens metodas.

  • TT_EOL - Tai jums sako, kad objektas ką tik praėjo eilutės pabaigos seką.

  • TT_NUMBER - Šis žetono tipas jūsų analizatoriaus kodui nurodo, kad įvestyje matytas skaičius.

  • TT_WORD - Šis žetono tipas rodo, kad buvo nuskaitytas visas „žodis“.

Kai rezultatas nėra viena iš aukščiau nurodytų konstantų, tai yra simbolio vertė, žyminti simbolį „įprasto“ simbolių diapazone, kuris buvo nuskaitytas, arba vienas iš jūsų nustatytų citatos simbolių. (Mano atveju nenustatytas joks citatos simbolis.) Kai rezultatas yra vienas iš jūsų citatos simbolių, cituojamą eilutę galima rasti eilutės egzemplioriaus kintamajame sval„StreamTokenizer“ objektas.

17–20 eilutėse pateiktas kodas nurodo eilutės pabaigos ir failo pabaigos nuorodas, o 21 eilutėje laikomasi sąlygos, jei žodžio žetonas buvo grąžintas. Šiame paprastame pavyzdyje žodis yra komanda arba kintamojo vardas. 22–35 eilutėse pateikiamos keturios galimos komandos. Jei pasiekiama 36 eilutė, tai turi būti kintamojo vardas; todėl programa saugo kintamojo pavadinimo kopiją ir gauna kitą žetoną, kuris turi būti lygybės ženklas.

Jei 41 eilutėje žetonas nebuvo lygybės ženklas, paprastas mūsų analizatorius aptinka klaidos būseną ir išmeta išimtį, kad tai praneštų. Sukūriau dvi bendras išimtis, Sintaksės klaida ir „ExecError“, atskirti analizės laiko klaidas nuo vykdymo laiko klaidų. pagrindinis metodas tęsiamas toliau esančia 44 eilute.

44 res = ParseExpression.expression (st); 45} gaudymas (SyntaxError se) {46 res = null; 47 varName = null; 48 System.out.println ("\ n Aptikta sintaksės klaida! -" + se.getMsg ()); 49 o (c! = StreamTokenizer.TT_EOL) 50 c = st.nextToken (); 51 tęsti; 52} 

44 eilutėje lygybės ženklo dešinėje esanti išraiška analizuojama kartu su išraiškos analizatoriumi, apibrėžtu ParseExpression klasė. Atkreipkite dėmesį, kad 14–44 eilutės suvyniotos į bandymo / gaudymo bloką, kuris sulaiko sintaksės klaidas ir jas sprendžia. Aptikus klaidą, analizatoriaus atkūrimo veiksmas yra sunaudoti visus prieigos raktus iki kito eilutės pabaigos ženklo imtinai. Tai parodyta aukščiau esančiose 49 ir ​​50 eilutėse.

Šiuo metu, jei nebuvo padaryta išimtis, programa sėkmingai išanalizavo pareiškimą. Paskutinis patikrinimas yra tai, kad kitas ženklas yra eilutės pabaiga. Jei taip nėra, klaida nepastebėta. Dažniausia klaida bus neatitinkantys skliaustai. Šis patikrinimas parodytas žemiau esančio kodo 53–60 eilutėse.

53 c = st.nextToken (); 54 if (c! = StreamTokenizer.TT_EOL) {55 if (c == ')') 56 System.out.println ("Aptikta sintaksės klaida! - Daugeliui uždarančių parenų."); 57 kita 58 System.out.println ("\ nBogus prieigos raktas -" + c); 59 o (c! = StreamTokenizer.TT_EOL) 60 c = st.nextToken (); 61} dar { 

Kai kitas žetonas yra eilutės pabaiga, programa vykdo 62–69 eilutes (parodyta žemiau). Šiame metodo skyriuje vertinama išanalizuota išraiška. Jei kintamojo vardas buvo nustatytas 36 eilutėje, rezultatas saugomas simbolių lentelėje. Bet kuriuo atveju, jei nėra išimties, išraiška ir jos vertė išspausdinama į „System.out“ srautą, kad galėtumėte pamatyti, ką dekoduotojas iššifravo.

62 pabandykite {63 Dvigubai z; 64 System.out.println ("Išanalizuota išraiška:" + res.unparse ()); 65 z = naujas dvigubas (res.value (kintamieji)); 66 System.out.println ("Reikšmė yra:" + z); 67 if (varName! = Null) {68 kintamieji.put (varName, z); 69 System.out.println ("Priskirtas:" + varName); 70} 71} catch (ExecError ee) {72 System.out.println ("Vykdymo klaida," + ee.getMsg () + "!"); 73} 74} 75} 76} 

Viduje konors ST pavyzdys klasė „StreamTokenizer“ naudoja komandų procesoriaus analizatorius. Šio tipo analizatorius paprastai būtų naudojamas apvalkalo programoje arba bet kurioje situacijoje, kai vartotojas interaktyviai išleidžia komandas. Antrasis analizatorius yra įkomponuotas į ParseExpression klasė. (Išsamų šaltinį rasite skyriuje Ištekliai.) Ši klasė analizuoja skaičiuoklės išraiškas ir yra iškviečiama 44 eilutėje aukščiau. Tai čia „StreamTokenizer“ susiduria su savo griežčiausiu iššūkiu.

Išraiškos analizatoriaus kūrimas

Skaičiuoklės išraiškų gramatika apibrėžia formos „[elementas] operatorius [elementas]“ sintaksę. Šio tipo gramatika vėl ir vėl iškyla ir vadinama operatorius gramatika. Patogus operatoriaus gramatikos žymėjimas yra:

id („OPERATOR“ ID) * 

Aukščiau pateiktas kodas būtų „ID terminalas, po kurio nulis ar daugiau operatoriaus ID atkartojimo atvejų“. „StreamTokenizer“ klasė atrodytų gana ideali analizuoti tokius srautus, nes dizainas natūraliai suskaido įvesties srautą žodis, numerisir paprastas personažas žetonai. Kaip aš jums parodysiu, tai tiesa iki tam tikro taško.

ParseExpression klasė yra nesudėtingas, rekursyvaus nusileidimo išraiškų analizatorius, tiesiogiai iš bakalauro kompiliatoriaus dizaino klasės. Išraiška šios klasės metodas apibrėžiamas taip:

 1 statinė išraiškos išraiška („StreamTokenizer st“) išmeta „SyntaxError“ {2 išraiškos rezultatas; 3 loginė padaryta = klaidinga; 4 5 rezultatas = suma (st); 6, kol (! Padaryta) {7 pabandykite {8 perjungti (st.nextToken ()) 9 atvejis '&': 10 rezultatas = nauja išraiška (OP_AND, rezultatas, suma (st)); 11 pertrauka; 12 atvejų '23} gaudymas (IOException ioe) {24 mesti naują SyntaxError ("Turiu įvesties / išvesties išimtį."); 25} 26} 27 grąžinimo rezultatas; 28} 
$config[zx-auto] not found$config[zx-overlay] not found