Programovanie

Lexikálna analýza, 2. časť: Vytvorenie aplikácie

Minulý mesiac som sa pozrel na triedy, ktoré poskytuje Java, aby robili základné lexikálne analýzy. Tento mesiac si prejdem jednoduchú aplikáciu, ktorá používa StreamTokenizer implementovať interaktívnu kalkulačku.

Ak si chceme v krátkosti prečítať článok z minulého mesiaca, existujú dve triedy lexikálnych analyzátorov, ktoré sú súčasťou štandardnej distribúcie Java: StringTokenizer a StreamTokenizer. Tieto analyzátory prevádzajú svoj vstup na diskrétne tokeny, ktoré syntaktický analyzátor môže použiť na pochopenie daného vstupu. Analyzátor implementuje gramatiku, ktorá je definovaná ako jeden alebo viac cieľových stavov dosiahnutých sledovaním rôznych sekvencií tokenov. Po dosiahnutí cieľového stavu syntaktického analyzátora vykoná určitú akciu. Keď syntaktický analyzátor zistí, že vzhľadom na aktuálnu postupnosť tokenov neexistujú žiadne možné stavy cieľov, definuje to ako chybový stav. Keď syntaktický analyzátor dosiahne chybový stav, vykoná akciu obnovy, ktorá syntaktický analyzátor vráti späť do bodu, v ktorom môže začať znova analyzovať. Spravidla sa to implementuje konzumáciou tokenov, kým sa syntaktický analyzátor nevráti do platného východiskového bodu.

Minulý mesiac som vám ukázal niekoľko metód, ktoré používali a StringTokenizer analyzovať niektoré vstupné parametre. Tento mesiac vám ukážem aplikáciu, ktorá používa a StreamTokenizer objekt na analýzu vstupného toku a implementáciu interaktívnej kalkulačky.

Budovanie aplikácie

Naším príkladom je interaktívna kalkulačka, ktorá je podobná príkazu Unix bc (1). Ako uvidíte, tlačí na StreamTokenizer triedy až po hranicu svojej použiteľnosti ako lexikálny analyzátor. Slúži teda ako dobrá ukážka toho, kde je možné vytýčiť hranicu medzi „jednoduchými“ a „zložitými“ analyzátormi. Tento príklad je aplikácia Java, a preto sa najlepšie spúšťa z príkazového riadku.

Ako rýchle zhrnutie svojich schopností kalkulačka prijíma výrazy vo forme

[názov premennej] "=" výraz 

Názov premennej je voliteľný a môže ním byť ľubovoľný reťazec znakov v predvolenom rozsahu slov. (Na obnovenie pamäte týmto znakom môžete použiť applet cvičenca z článku z minulého mesiaca.) Ak je názov premennej vynechaný, hodnota výrazu sa jednoducho vytlačí. Ak je názov premennej k dispozícii, je premennej priradená hodnota výrazu. Po priradení premenných je možné ich použiť v neskorších výrazoch. Napĺňajú teda úlohu „spomienok“ na modernej ručnej kalkulačke.

Výraz je zložený z operandov vo forme číselných konštánt (konštanty s dvojitou presnosťou, konštanty s pohyblivou rádovou čiarkou) alebo názvov premenných, operátorov a zátvoriek na zoskupenie konkrétnych výpočtov. Zákonné operátory sú sčítanie (+), odčítanie (-), násobenie (*), delenie (/), bitový operátor AND (&), bitový operátor OR (|), bitový operátor XOR (#), umocňovanie (^) a unárna negácia buď s mínusom (-) pre výsledok doplnku dvoch alebo s ofinou (!) pre výsledok doplnku.

Okrem týchto vyhlásení môže naša aplikácia kalkulačky obsahovať aj jeden zo štyroch príkazov: „výpis“, „vymazanie“, „pomoc“ a „ukončenie“. The skládka príkaz vypíše všetky premenné, ktoré sú momentálne definované, ako aj ich hodnoty. The jasný príkaz vymaže všetky aktuálne definované premenné. The Pomoc Príkaz vypíše niekoľko riadkov pomocného textu, aby mohol používateľ začať. The skončiť príkaz spôsobí ukončenie aplikácie.

Celá vzorová aplikácia sa skladá z dvoch analyzátorov - jedného pre príkazy a príkazy a jedného pre výrazy.

Tvorba syntaktického analyzátora príkazov

Analyzátor príkazov je implementovaný v aplikačnej triede pre príklad STExample.java. (Ukazovateľ na kód nájdete v časti Zdroje.) hlavný metóda pre túto triedu je definovaná nižšie. Prejdem ti kúsky.

 1 public static void main (String args []) hodí IOException {2 Hashtable variables = new Hashtable (); 3 StreamTokenizer st = nový StreamTokenizer (System.in); 4 st.eolIsSignificant (true); 5 st.lowerCaseMode (true); 6. st .ordinaryChar ('/'); 7 st .ordinaryChar ('-'); 

V kóde vyššie prvá vec, ktorú urobím, je prideliť a java.util.Hashtable triedy na uchovanie premenných. Potom pridelím a StreamTokenizer a mierne ho upraviť od predvolených nastavení. Zmeny sú odôvodnené takto:

  • eolIsSignificant je nastavený na pravda takže tokenizer vráti indikáciu konca riadku. Koniec riadku používam ako bod, kde sa výraz končí.

  • lowerCaseMode je nastavený na pravda takže názvy premenných budú vždy vrátené malými písmenami. Týmto spôsobom názvy premenných nerozlišujú veľké a malé písmená.

  • Symbol lomky (/) je nastavený ako obyčajný znak, aby sa nepoužil na označenie začiatku komentára, a môže byť namiesto neho použitý ako operátor rozdelenia.

  • Mínusový znak (-) je nastavený na obyčajný znak, takže reťazec „3-3“ sa segmentuje na tri tokeny - „3“, „-“ a „3“ - nielen na „3“ a „-3.“ (Pamätajte, že analýza čísel je predvolene nastavená na „zapnuté“.)

Akonáhle je tokenizer nastavený, syntaktický analyzátor príkazov beží v nekonečnej slučke (kým nerozpozná príkaz „quit“, v ktorom okamihu skončí). Toto je zobrazené nižšie.

 8 while (true) {9 Expression res; 10 int c = StreamTokenizer.TT_EOL; 11 Reťazec varName = null; 12 13 System.out.println ("Zadajte výraz ..."); 14 vyskúšajte {15 while (true) {16 c = st.nextToken (); 17 if (c == StreamTokenizer.TT_EOF) {18 System.exit (1); 19} else if (c == StreamTokenizer.TT_EOL) {20 pokračovať; 21} else if (c == StreamTokenizer.TT_WORD) {22 if (st.sval.compareTo ("dump") == 0) {23 dumpVariables (premenné); 24 pokračovať; 25} else if (st.sval.compareTo ("clear") == 0) {26 premenných = new Hashtable (); 27 pokračovať; 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 pokračovať; 35} 36 varName = st.sval; 37 c = st.nextToken (); 38} 39 zlom; 40} 41 if (c! = '=') {42 hod nový SyntaxError ("chýba začiatočné znamienko '=' znak."); 43} 

Ako vidíte na riadku 16, prvý token sa volá vyvolaním nextToken na StreamTokenizer objekt. Týmto sa vráti hodnota označujúca druh tokenu, ktorý bol skenovaný. Návratová hodnota bude buď jednou z definovaných konštánt v StreamTokenizer trieda alebo to bude znaková hodnota. „Meta“ tokeny (tie, ktoré nie sú iba hodnotovými hodnotami), sú definované takto:

  • TT_EOF - To znamená, že ste na konci vstupného toku. Na rozdiel od StringTokenizer, nie je hasMoreTokens metóda.

  • TT_EOL - Toto vám povie, že objekt práve prešiel sekvenciou konca riadku.

  • TT_NUMBER - Tento typ tokenu informuje váš syntaktický analyzátor o tom, že na vstupe bolo vidieť číslo.

  • TT_WORD - Tento typ tokenu označuje, že bolo skenované celé „slovo“.

Ak výsledkom nie je ani jedna z vyššie uvedených konštánt, bude to buď hodnota znaku predstavujúca znak v „bežnom“ rozsahu znakov, ktorý bol naskenovaný, alebo jeden z ponúkaných znakov, ktorý ste nastavili. (V mojom prípade nie je nastavený žiadny znak úvodzovky.) Keď je výsledkom jeden z vašich úvodzoviek, citovaný reťazec nájdete v premennej inštancie reťazca. sval z StreamTokenizer objekt.

Kód v riadkoch 17 až 20 sa zaoberá indikáciami konca a konca súboru, zatiaľ čo v riadku 21 sa použije klauzula if, ak sa vrátil token slova. V tomto jednoduchom príklade je to slovo buď príkaz, alebo názov premennej. Riadky 22 až 35 sa zaoberajú štyrmi možnými príkazmi. Ak sa dostane riadok 36, musí to byť názov premennej; následne si program nechá kópiu názvu premennej a získa ďalší token, ktorý musí byť znamienkom rovnosti.

Ak na riadku 41 nebol token znakom rovná sa, náš jednoduchý syntaktický analyzátor zistí chybový stav a na jeho signalizáciu hodí výnimku. Vytvoril som dve všeobecné výnimky, Chyba syntaxe a ExecError, aby sa odlíšili chyby analýz od chýb za behu. The hlavný metóda pokračuje riadkom 44 nižšie.

44 res = ParseExpression.expression (st); 45} catch (SyntaxError se) {46 res = null; 47 varName = null; 48 System.out.println ("\ n Bola zistená syntaxová chyba! -" + se.getMsg ()); 49 while (c! = StreamTokenizer.TT_EOL) 50 c = st.nextToken (); 51 pokračovať; 52} 

V riadku 44 sa výraz napravo od znamienka rovnosti analyzuje s analyzátorom výrazov definovaným v znaku ParseExpression trieda. Upozorňujeme, že riadky 14 až 44 sú zabalené v bloku try / catch, ktorý zachytáva chyby syntaxe a zaoberá sa nimi. Keď sa zistí chyba, syntaktickou činnosťou syntaktického analyzátora je spotrebovať všetky tokeny až po nasledujúci token konca riadku vrátane. Toto je zobrazené na riadkoch 49 a 50 vyššie.

V tomto okamihu, ak nebola vyvolaná výnimka, aplikácia úspešne analyzovala vyhlásenie. Poslednou kontrolou je zistiť, či je nasledujúci token koncom riadku. Ak nie je, chyba zostala nezistená. Najbežnejšou chybou budú nezodpovedajúce zátvorky. Táto kontrola je uvedená v riadkoch 53 až 60 nižšie uvedeného kódu.

53 c = st.nextToken (); 54 if (c! = StreamTokenizer.TT_EOL) {55 if (c == ')') 56 System.out.println ("\ nZistila sa chyba syntaxe! - Mnoho zatváracích parenov."); 57 else 58 System.out.println ("\ nFalošný token na vstupe -" + c); 59 while (c! = StreamTokenizer.TT_EOL) 60 c = st.nextToken (); 61} else { 

Keď je nasledujúci token koniec riadku, program vykoná riadky 62 až 69 (zobrazené nižšie). V tejto časti metódy sa vyhodnocuje analyzovaný výraz. Ak bol názov premennej nastavený v riadku 36, výsledok sa uloží do tabuľky symbolov. V obidvoch prípadoch, ak nie je vyvolaná žiadna výnimka, výraz a jeho hodnota sa vytlačia do streamu System.out, aby ste videli, čo syntaktický analyzátor dekódoval.

62 vyskúšať {63 Double z; 64 System.out.println ("Analyzovaný výraz:" + res.unparse ()); 65 z = nový Double (res.value (variables)); 66 System.out.println ("Hodnota je:" + z); 67 if (varName! = Null) {68 variables.put (varName, z); 69 System.out.println ("Priradené k:" + varName); 70} 71} catch (ExecError ee) {72 System.out.println ("Chyba vykonania," + ee.getMsg () + "!"); 73} 74} 75} 76} 

V STPriklad triedy, StreamTokenizer používa syntaktický analyzátor príkazového procesora. Tento typ syntaktického analyzátora sa bežne používa v shellovom programe alebo v akejkoľvek situácii, v ktorej užívateľ interaktívne vydáva príkazy. Druhý analyzátor je zapuzdrený v ParseExpression trieda. (Celý zdroj nájdete v časti Zdroje.) Táto trieda analyzuje výrazy kalkulačky a je vyvolaná v riadku 44 vyššie. Je to tu StreamTokenizer čelí svojej najprísnejšej výzve.

Tvorba syntaktického analyzátora výrazov

Gramatika výrazov kalkulačky definuje algebraickú syntax tvaru "[item] operator [item]." Tento typ gramatiky sa objavuje opakovane a nazýva sa operátor gramatiku. Praktická notácia pre gramatiku operátora je:

id („OPERATOR“ id) * 

Vyššie uvedený kód bude znieť „Terminál ID, za ktorým nasleduje nula alebo viac výskytov n-tice ID operátora.“ The StreamTokenizer trieda sa javí ako celkom ideálny na analýzu takýchto prúdov, pretože dizajn prirodzene rozdeľuje vstupný prúd na slovo, čísloa obyčajný znak žetóny. Ako vám ukážem, je to pravda do istej miery.

The ParseExpression class je priamy syntaktický analyzátor pre výrazy rekurzívneho pôvodu, priamo z vysokoškolskej triedy kompilátora a dizajnu. The Vyjadrenie Metóda v tejto triede je definovaná takto:

 1 statický výrazový výraz (StreamTokenizer st) vyvolá SyntaxError {2 výsledok výrazu; 3 boolean hotovo = nepravda; 4 5 výsledok = súčet (st); 6 while (! Done) {7 try {8 switch (st.nextToken ()) 9 case '&': 10 result = new Expression (OP_AND, result, sum (st)); 11 zlom; 12 case '23} catch (IOException ioe) {24 throw new SyntaxError ("Got an I / O Exception."); 25} 26} 27 návratový výsledok; 28} 
$config[zx-auto] not found$config[zx-overlay] not found