Ohjelmointi

Leksikaalinen analyysi ja Java: osa 1

Leksikaalinen analyysi ja jäsentäminen

Kun kirjoitat Java-sovelluksia, yksi yleisimmistä asioista, jotka sinun on tuotettava, on jäsennin. Parserit vaihtelevat yksinkertaisista monimutkaisiin ja niitä käytetään kaikkeen komentorivivaihtoehtojen tarkastelusta Java-lähdekoodin tulkintaan. Sisään JavaWorldJoulukuun numerossa näytin sinulle Jackin, automaattisen jäsenningeneraattorin, joka muuntaa korkean tason kieliopin määritykset Java-luokiksi, jotka toteuttavat kyseisissä eritelmissä kuvatun jäsentimen. Tässä kuussa näytän sinulle resurssit, jotka Java tarjoaa kohdennettujen leksikaalisten analysaattoreiden ja jäsentäjien kirjoittamiseen. Nämä hieman yksinkertaisemmat jäsentimet täyttävät aukon yksinkertaisen merkkijonojen vertailun ja Jackin laatimien monimutkaisten kieliopien välillä.

Leksikaalisten analysaattorien tarkoituksena on ottaa syöttömerkkejä ja purkaa ne korkeamman tason tunnuksiksi, jotka jäsennin voi ymmärtää. Parserit kuluttavat leksikaalisen analysaattorin lähdön ja toimivat analysoimalla palautettujen merkkien sekvenssin. Jäsennys sovittaa nämä sekvenssit lopputilaan, joka voi olla yksi mahdollisesti monista lopputiloista. Lopputilat määrittävät tavoitteet jäsennin. Kun lopputila saavutetaan, jäsennintä käyttävä ohjelma suorittaa jonkin toiminnon - joko perustaa tietorakenteet tai suorittaa jonkin toimintokohtaisen koodin. Lisäksi jäsentäjät voivat havaita - käsitellyt merkkijonot - kun laillista lopputilaa ei voida saavuttaa; siinä vaiheessa jäsennin tunnistaa nykyisen tilan virhetilaksi. Sovelluksen on päätettävä, mitä toimia suoritetaan, kun jäsennin tunnistaa joko lopputilan tai virhetilan.

Tavallinen Java-luokan pohja sisältää pari leksikaalista analysaattoriluokkaa, mutta se ei määritä mitään yleiskäyttöisiä jäsenninluokkia. Tässä sarakkeessa tarkastelen perusteellisesti Java: n mukana tulevia leksikaalisia analysaattoreita.

Java: n leksikaaliset analysaattorit

Java Language Specification, versio 1.0.2, määrittelee kaksi leksikaalista analysaattoriluokkaa, StringTokenizer ja StreamTokenizer. Niiden nimistä voit päätellä sen StringTokenizer käyttää Merkkijono - kohteet sen syötteenä ja StreamTokenizer käyttää InputStream esineitä.

StringTokenizer-luokka

Kahdesta saatavilla olevasta leksikaalianalysaattoriluokasta helpoin ymmärtää on StringTokenizer. Kun rakennat uuden StringTokenizer objekti, konstruktorin menetelmä ottaa nimellisesti kaksi arvoa - syöttömerkkijonon ja erotinmerkkijonon. Sitten luokka muodostaa merkkijonon, joka edustaa merkkejä erotinmerkkien välillä.

Leksikaalisena analysaattorina StringTokenizer voidaan muodollisesti määritellä kuten alla on esitetty.

[~ delim1, delim2, ..., delimN] :: Tunnus 

Tämä määritelmä koostuu säännöllisestä lausekkeesta, joka vastaa kaikkia merkkejä paitsi erotinmerkit. Kaikki vierekkäiset vastaavat merkit kerätään yhdeksi tunnukseksi ja palautetaan tunnukseksi.

Yleisin käyttö StringTokenizer luokka on tarkoitettu erottamaan joukko parametreja, kuten pilkuilla erotettu luettelo numeroista. StringTokenizer on ihanteellinen tässä roolissa, koska se poistaa erotimet ja palauttaa tiedot. StringTokenizer luokka tarjoaa myös mekanismin sellaisten luetteloiden tunnistamiseksi, joissa on "null" -tunnuksia. Käytä tyhjiä tunnuksia sovelluksissa, joissa joillakin parametreilla on joko oletusarvot tai niiden ei tarvitse olla kaikissa tapauksissa mukana.

Alla oleva sovelma on yksinkertainen StringTokenizer kuntoilija. StringTokenizer-sovelman lähde on täällä. Jos haluat käyttää sovetta, kirjoita analysoitavaa tekstiä syöttömerkkijonoalueelle ja kirjoita sitten erotinmerkeistä koostuva merkkijono erotinmerkkijonoalueelle. Napsauta lopuksi Tokenize! -painiketta. Tulos näkyy merkkiluettelossa syötemerkkijonon alapuolella ja järjestetään yhdeksi tunnukseksi riviä kohden.

Tarvitset Java-yhteensopivan selaimen nähdäksesi tämän sovelman.

Tarkastellaan esimerkkinä merkkijono "a, b, d", joka on välitetty a: lle StringTokenizer objekti, joka on muodostettu pilkulla (,) erotinmerkkinä. Jos laitat nämä arvot yllä olevaan kunto-appletiin, näet, että Tokenizer object palauttaa merkkijonot "a", "b" ja "d". Jos aikomuksesi oli huomata, että yksi parametri puuttui, saatat olla yllättynyt, jos et näe merkkejä tästä tunnussekvenssissä. Puuttuvien tunnusten havaitsemismahdollisuuden sallii Return Separator -booli, joka voidaan asettaa, kun luot Tokenizer esine. Kun tämä parametri on asetettu, kun Tokenizer on rakennettu, myös jokainen erotin palautetaan. Napsauta Return Separator -valintaruutua yllä olevassa sovelmassa ja jätä merkkijono ja erotin yksin. Nyt Tokenizer palauttaa a, pilkku, b, pilkku, pilkku ja d. Huomaa, että saat kaksi erotusmerkkiä peräkkäin, voit määrittää, että "null" -merkki sisältyi syöttömerkkijonoon.

Temppu onnistuneeseen käyttöön StringTokenizer jäsentimessä määrittelee syötteen siten, että erotinmerkki ei näy tiedoissa. Voit selvästi välttää tämän rajoituksen suunnittelemalla sitä sovelluksessasi. Alla olevaa menetelmämääritelmää voidaan käyttää osana applettia, joka hyväksyy värin punaisen, vihreän ja sinisen arvon muodossa parametrivirtaan.

 / ** * jäsennä parametrin muoto "10,20,30" * RGB-sekvenssinä väriarvolle. * / 1 Väri getColor (Merkkijonon nimi) {2 Merkkijonon tiedot; 3 StringTokenizer st; 4 int punainen, vihreä, sininen; 5 6 data = getParameter (nimi); 7 if (data == null) 8 palauttaa null; 9 10 st = uusi StringTokenizer (data, ","); 11 kokeile {12 punaista = Kokonaisluku.parseInt (st.nextToken ()); 13 vihreä = Kokonaisluku.parseInt (st.sextToken ()); 14 sininen = Kokonaisluku.parseInt (st.sextToken ()); 15} saalis (poikkeus e) {16 return null; // (VIRHEEN TILA) ei voitu jäsentää sitä 17} 18 palauttaa uuden värin (punainen, vihreä, sininen); // (END STATE) valmis. 19} 

Yllä oleva koodi toteuttaa hyvin yksinkertaisen jäsentimen, joka lukee merkkijonon "numero, numero, numero" ja palauttaa uuden Väri esine. Rivillä 10 koodi luo uuden StringTokenizer objekti, joka sisältää parametritiedot (olettaen, että tämä menetelmä on osa sovelma), ja erotinmerkkiluettelo, joka koostuu pilkuista. Sitten riveillä 12, 13 ja 14 kukin tunniste puretaan merkkijonosta ja muunnetaan luvuksi kokonaislukuna parseInt menetelmä. Näitä tuloksia ympäröi a yritä saada kiinni lohko, jos numerosarjat eivät olleet kelvollisia numeroita tai Tokenizer heittää poikkeuksen, koska merkit ovat loppuneet. Jos kaikki luvut muunnetaan, lopputila saavutetaan ja a Väri esine palautetaan; muuten virhetila saavutetaan ja tyhjä palautetaan.

Yksi ominaisuus StringTokenizer luokka on, että se on helposti pinottava. Katso nimettyä menetelmää getColor alla, mikä on edellisen menetelmän rivit 10-18.

 / ** * jäsennä värikopio "r, g, b" AWT: ksi Väri esine. * / 1 Väri getColor (merkkijonotiedot) {2 int punainen, vihreä, sininen; 3 StringTokenizer st = uusi StringTokenizer (data, ","); 4 kokeile {5 punaista = Kokonaisluku.parseInt (st.nextToken ()); 6 vihreä = kokonaisluku.parseInt (st.sextToken ()); 7 sininen = Kokonaisluku.parseInt (st.sextToken ()); 8} saalis (poikkeus e) {9 return null; // (VIRHEEN TILA) ei voitu jäsentää sitä 10} 11 palauttaa uuden värin (punainen, vihreä, sininen); // (END STATE) valmis. 12} 

Hieman monimutkaisempi jäsennin näkyy alla olevassa koodissa. Tämä jäsennin on toteutettu menetelmässä getColors, joka on määritetty palauttamaan taulukko Väri esineitä.

 / ** * jäsennä joukko värejä "r1, g1, b1: r2, g2, b2: ...: rn, gn, bn" * joukoksi AWT Color -objekteja. * / 1 Väri [] getColors (merkkijonodata) {2 Vektorikoko = uusi vektori (); 3 Väri cl, tulos []; 4 StringTokenizer st = uusi StringTokenizer (data, ":"); 5 taas (st. HasMoreTokens ()) {6 cl = getColor (st.nextToken ()); 7 if (cl! = Null) {8 akumul.addElement (cl); 9} else {10 System.out.println ("Virhe - huono väri."); 11} 12} 13 if (umul.size () == 0) 14 return null; 15 tulos = uusi väri [koko. Koko ()]; 16 (int i = 0; i <kertakoko (); i ++) {17 tulos [i] = (väri) akumul.elementAt (i); 18} 19 palautustulos; 20} 

Yllä olevassa menetelmässä, joka eroaa vain hieman menetelmästä getColor -menetelmällä rivillä 4-12 oleva koodi luo uuden Tokenizer poimia kaksoispisteen (:) merkin ympäröimiä tunnuksia. Kuten voit lukea menetelmän dokumentaatiokommentista, tämä menetelmä odottaa, että väriryhmät erotetaan kaksoispisteillä. Jokainen kutsu seuraavaToken että StringTokenizer luokka palauttaa uuden tunnuksen, kunnes merkkijono on käytetty loppuun. Palautetut merkit ovat pilkuilla erotettuja numerosarjoja; nämä merkkijonot syötetään getColor, joka sitten poimi värin kolmesta numerosta. Uuden luominen StringTokenizer objektin toisen palauttaman tunnuksen avulla StringTokenizer object sallii kirjoittamamme jäsenninkoodin olla hieman hienostuneempi siitä, miten se tulkitsee merkkijonon syötteen.

Niin hyödyllistä kuin se onkin, loppujen lopuksi uuputat StringTokenizer luokan ja täytyy siirtyä isoveljensä luokse StreamTokenizer.

StreamTokenizer-luokka

Kuten luokan nimi viittaa, a StreamTokenizer objekti odottaa syötteensä tulevan InputStream luokassa. Kuin StringTokenizer yllä, tämä luokka muuntaa tulovirran paloiksi, joita jäsentämiskoodisi voi tulkita, mutta tässä samankaltaisuus loppuu.

StreamTokenizer on pöytätietoinen leksikaalinen analysaattori. Tämä tarkoittaa, että jokaiselle mahdolliselle syötemerkille annetaan merkitys, ja skanneri käyttää nykyisen merkin merkitystä päättää mitä tehdä. Tämän luokan toteutuksessa hahmoille määritetään yksi kolmesta luokasta. Nämä ovat:

  • Välilyönti merkit - niiden sanallinen merkitys rajoittuu sanojen erottamiseen

  • Sana merkit - ne tulisi koota, kun ne ovat toisen sanamerkin vieressä

  • Tavallinen merkit - ne on palautettava välittömästi jäsentäjälle

Kuvittele tämän luokan toteutus yksinkertaisena tilakoneena, jolla on kaksi tilaa - tyhjäkäynnillä ja kerääntyä. Jokaisessa tilassa tulo on merkki jostakin yllä olevista luokista. Luokka lukee merkin, tarkistaa sen luokan ja tekee jonkin toiminnon ja siirtyy seuraavaan tilaan. Seuraava taulukko esittää tämän tilakoneen.

OsavaltioTuloToimintaUusi valtio
tyhjäkäynnilläsana merkkityönnä taaksepäin merkkikerääntyä
tavallinen merkkipaluu merkkityhjäkäynnillä
välilyönti merkkikuluttaa luonnettatyhjäkäynnillä
kerääntyäsana merkkilisää nykyiseen sanaankerääntyä
tavallinen merkki

palauta nykyinen sana

työnnä taaksepäin merkki

tyhjäkäynnillä
välilyönti merkki

palauta nykyinen sana

kuluttaa luonnetta

tyhjäkäynnillä

Tämän yksinkertaisen mekanismin lisäksi StreamTokenizer luokka lisää useita heuristiikkaa. Näitä ovat numeroiden käsittely, lainattujen merkkijonojen käsittely, kommenttien käsittely ja rivin lopullinen käsittely.

Ensimmäinen esimerkki on numeroiden käsittely. Tiettyjen merkkijonojen voidaan tulkita edustavan numeerista arvoa. Esimerkiksi merkkijono 1, 0, 0,. Ja 0 vierekkäin tulovirrassa edustaa lukuarvoa 100,0. Kun kaikki numeromerkit (0–9), pistemerkki (.) Ja miinus (-) on määritelty osana sana aseta StreamTokenizer luokan voidaan kertoa tulkitsevan palattavan sanan mahdolliseksi numeroksi. Tämän tilan asettaminen saavutetaan soittamalla parseNumbers instantioidun tokenizer-objektin menetelmä (tämä on oletus). Jos analysaattori on kerääntyvässä tilassa, ja seuraava merkki tekisi ei olla osa numeroa, tällä hetkellä kertynyt sana tarkistetaan, onko se kelvollinen numero. Jos se on kelvollinen, se palautetaan ja skanneri siirtyy seuraavaan sopivaan tilaan.

Seuraava esimerkki on lainattu merkkijonojen käsittely. Usein on toivottavaa välittää merkkijono, jota ympäröi lainausmerkki (tyypillisesti kaksinkertainen (") tai yksittäinen (') lainaus) yhtenä tunnuksena. StreamTokenizer luokassa voit määrittää minkä tahansa merkin lainausmerkiksi. Oletusarvoisesti ne ovat yhden lainauksen (') ja kaksoislainauksen (") merkkejä. Tilakone on muokattu kuluttamaan merkkejä kerääntyvässä tilassa, kunnes joko toinen lainausmerkki tai rivin lopussa oleva merkki käsitellään. lainausmerkki, analysaattori käsittelee lainausmerkin, jota edeltää kauttaviiva (\) tulovirrassa ja lainauksen sisällä sanamerkkinä.