Komptextkap05.doc Kap 5 Parser výrazov 5 Parser výrazov Parsing expressions Každý programovací jazyk je definovaný určitou množinou "gramatických pravidiel", tvoriacich syntax jazyka. Syntax jazyka predpisuje ako správne zapisovať príkazy a výrazy. Časť prekladača, ktorá rozoznáva syntax daného jazyka sa volá Parser. Ten riadi preklad, pretože analyzuje zdrojový text programu založený na syntaxi. Volá rutiny scannera pre získanie tokenov a rutiny tabuľky symbolov pre vloženie a vyhľadávanie jednotlivých identifikátorov programu. Keď parser kompilátora rozpoznal syntaktickú entitu, napr. aritmetický výraz, volá rutiny generátora kódu pre vyslanie vhodného cieľového kódu. Podobne parser interpretera volá rutiny exekútora pre vykonanie vhodnej operácie. V tejto kapitole sú dva projekty pre rozvoj zručností pre nasledujúce činnosti: písanie rutín parsera, založených na syntaktických diagramoch, rozpoznávanie výrazov, vykonávanie výrazov interpretovanie. POSTFIX.PRJ CALC.PRJ rozpoznanie jednoduchého aritmetického výrazu a jeho preklad do postfixového tvaru kalkulátor pre pascalovské výrazy prvky interpretera 5.1 Syntaktické diagramy Pre popis syntaxe sa často používajú syntaktické diagramy. Sú to grafické reprezentácie syntaktických pravidiel takzvané Conwayove diagramy. Napríklad: číslo e celé číslo bez znamienka. celé číslo bez znamienka celé číslo bez znamienka E Počas syntaktickej analýzy sledujeme čiary zľava doprava. V miestach, kde sa čiary rozdvojujú, je definovaná alternatíva, t.j. možnosť vynechania nejakého symbolu, alebo sa môžeme rozhodnúť vybrať si niektorú z možností na príslušnej trase. Čiara môže meniť smer postupu i do protismeru tak, ako ukazuje nasledujúci obrázok. V tom prípade vznikne begin cyklus a symbol môže byť opakovane akceptovaný. príkaz end Význam grafických značiek: E array = inálny symbol inál príkaz = neinálny symbol neinál 5.2 Jednoduché výrazy Na nasledujúcich obrázkoch sú syntaktické (Conwayove) diagramy pre vytváranie výrazov. Pri návrhu budeme zatiaľ uvažovať iba jednoduché výrazy, v ktorých sa používajú základné aritmetické operácie sčítania, odčítania, násobenia a delenia: /. Pozri program 51 Postfix Convertor. výraz 29.3.2007 19:57 Benedikovič 1/7
Komptextkap05.doc Kap 5 Parser výrazov (prvok): / identifikátor (činiteľ): číslo ( výraz ) Nasleduje príklad dekompozície jednoduchého výrazu na základe uvedených syntaktických diagramov: výraz výraz identif. číslo identif. identif. číslo alfa 3 / ( beta gama ) 5 5.3 Pascalovské výrazy Na nasledujúcom obrázku sú takmer kompletné pascalovské výrazy s dodržaním priority operátorov. Túto časť považujte ako preddavok programu 52 Kalkulačka Calculator, ktorý je na konci tejto kapitoly. Oproti časti s jednoduchými operáciami pribudli relačné a logické operátory s výnimkou operátora in. výraz: = < <= <> => > 29.3.2007 19:57 Benedikovič 2/7
Komptextkap05.doc Kap 5 Parser výrazov : OR (prvok): / DIV MOD AND (činiteľ): identifikátor číslo NOT ( výraz ) 5.4 Moduly kapitoly Význam písmen stavu: S = súbor nezmenený (index je číslo kapitoly, kde sa nachádza), N = nový súbor Modul Súbory Stav Common common.h S3 Basis basis.h, basis.c S3 Message message.h, message.c S3 Scanner scanner.h, scanner.c S3 Tabuľka symbolov symtab.h, symtab.c S4 5.4.1 Nezmenené moduly Moduly Common, Basis, Message, Scanner a Tabuľka symbolov sú od predchádzajúcich kapitol nezmenené, preto sa v prípade potreby možno pozrieť do príslušnej kapitoly. 5.5 Programy kapitoly 5.5.1 Konvertor zápisu výrazu z tvaru Infix na Postfix Poznáme viacero druhov zápisu výrazov v programovacích jazykoch. Líšia sa od seba napríklad poradím zápisu operandov a operátorov. Najčastejšie sa používajú dva typy: Zápis Infix klasické poradie operandov a operátorov napr.: a b. Zápis Postfix operátory sú zapísané až za operandami, na ktorých vykonávajú svoju operáciu, napr.: a b tento zápis sa tiež volá Obrátená poľská notácia Reverse Polish Notation RPN. Popularizovaný bol napríklad v kalkulačkách fy Hewlett Packard. Postfixový zápis je síce menej zrozumiteľný bežnému užívateľovi, ale je výhodný pre spracovanie na počítači. Je jednoznačný z hľadiska priority operátorov pričom nie sú potrebné zátvorky. Pre vyhodnotenie je využívaný zásobník. 29.3.2007 19:57 Benedikovič 3/7
Komptextkap05.doc Kap 5 Parser výrazov Algoritmus spracovania postfixového výrazu vykonáva nasledujúce kroky: Ak je zo vstupného reťazca prečítaný: operand potom sa hodnota operandu automaticky uloží do zásobníku, operátor potom sa vykoná operácia definovaná operátorom: v prípade binárneho operátora sa zo zásobníku vyberú dva operátory, v prípade unárneho operátora sa vyberie jeden operand, vykoná sa predpísaná operácia na operandoch a jej výsledok sa uloží naspäť do zásobníka. Príklad RPN: Infix: ( ( 17 49 ) / 4 2 3 ) ( 9 3 2 ) Postfix: 17 <zápor> 49 4 / 2 3 9 3 2 Pozor!! V postfixovom zápise sú jednotlivé tokeny oddelené vždy jednou medzerou. V uvedenom príklade ide o hodnoty: 17, 49, 4, 2, 3, 9, 3, 2 (to aby nevznikli pochybnosti typu 4 / 23!!!). Postup pri spracovaní infixového (na obrázku vľavo) a postfixového (vpravo) zápisu daného výrazu: ((1749)/423)(932) 17<zápor>494/2 39 32 32 6 2 6 17 32 6 2 6 16 16 Aký je postup pri konverzii infixového zápisu výrazu na tvar postfixový? V prvom kroku vytvoríme strom aritmetického výrazu. V uzloch budú uložené operátory a operandy podľa nasledujúcich pravidiel: vo vnútorných uzloch budú operátory (pre operácie), v listoch budú operandy (hodnoty), do koreňa stromu umiestnime operátor, ktorého činnosť bude vykonaná ako posledná, smerom k listom budú umiestnené operandy s rastúcou prioritou v danom výraze (zátvorky vo výraze slúžia k zvýšeniu priority operácie) Na obrázku je strom výrazu v infixovom zápise: (( 17 49 ) / 4 2 3 ) ( 9 3 2 ) 17 17 32 49 / 4 2 2 3 9 3 Postupnosť spracovania uzlov tohto binárneho stromu je určená prechádzkou postorder. To znamená pre každý uzol, že jeho obsah sa spracuje až po spracovaní jeho ľavého a pravého podstromu. Samotné spracovanie obsahu je vykonané tým, že sa jeho obsah sa uloží do výstupného reťazca. Obsah výstupného reťazca je reťazec v postfixovom formáte. 6 16 6 2 29.3.2007 19:57 Benedikovič 4/7
Komptextkap05.doc Kap 5 Parser výrazov Program 51: Postfix Convertor Program je uložený v súbore postfix.cpp. Pracuje ako parser jednoduchého aritmetického výrazu s operáciami,,, /. Pozri kapitolu 5.2 o jednoduchých výrazoch. Vstupné dáta sú čítané zo súboru, ktorý je uvedený ako argument príkazu štartu programu. Dáta v takomto súbore sú zapísané na základe pravidiel nám už známych zo scannera takto: <výraz>; <výraz>;... <výraz> Program Postfix Convertor vykoná prevod výrazu z infixového na postfixový tvar, ktorý je jednoduchší na vykonanie a nie sú navyše potrebné zátvorky. Štart preloženého programu: postfix <názov súboru s výrazmi> kde <názov súboru s výrazmi> je akceptovaný programom "postfix" cez argument argv[1]. Popis častí programu: postfix[], pp main() slúži pre uloženie textu výrazu konvertovaného do postfixového tvaru (RPN) buffer pre výstup postfixu a pracovný ukazovateľ jadrom programu je cyklus, ktorý sa vykonáva až po nájdení tokenu <PERIOD>, alebo po fyzický koniec, v ňom: pripraví do buffera postfix výstupný text a číta token (predpokladá sa začiatok výrazu!) volá Expression() pre parsing výrazu volá OutputPostfix() pre uloženie postfixvýrazu a vytlačí ho vykoná test, či je ďalší výraz na vstupe oddelený bodkočiarkou (;) Expression () volá funkciu SimpleExpression() (v tomto programe zatiaľ iba túto...) hlavný program rekurzívna Analýza výrazu SimpleExpression () Analýza jednoduchého výrazu hľadá tzv. y prvky pomocou funkcie Term() a zisťuje operácie s nimi cez operátory a operátory a uloží ho do buffera postfixu Term () Analýza u prvku podobná funkcii SimpleExpression(), ale hľadá tzv. faktory činitele volaním Factor() z operátormi a / operátory a / uloží ho do buffera postfixu Factor () Analýza faktora činiteľa ak sa vyskytne identifikátor, alebo číslo uloží ho do buffera postfixu pri výskyte ľavej zátvorky volá rekurzívne Expression() pre spracovanie výrazu vnoreného do zátvoriek (=zmena priority) OutPostfix () fyzické uloženie tokenu do buffera postfixového zápisu 5.5.2 Prvý pokus o kalkulačku výstup postfixvýrazu Program kalkulačka interpretuje operácie špecifikované infixovými výrazmi pomocou zásobníku. Spracúva iba priraďovací príkaz, pričom na pravej strane príkazu je výraz. Pretože nie je rozpoznávaný príkaz výstupu, výstup je riešený tak, že hodnotu, ktorú chceme vytlačiť uložíme do premennej output, ktorú interpreter kalkulačky spracúva tým spôsobom, že nevytvára pre ňu miesto v pamäti, ale zariadi výstup hodnoty výrazu na pravej strane priraďovacieho príkazu na špecifikované výstupné zariadenie. Program 52: Calculator Program je uložený v súbore calc.cpp. Súbor so vstupnými dátami môže obsahovať iba priraďovacie príkazy obsahujúce na ľavej strane ľubovoľný identifikátor premennej a na pravej strane výraz obsahujúci už naplnené premenné, spomínané operátory a guľaté zátvorky. Štart preloženého programu: calc <názov súboru s príkazmi> 29.3.2007 19:57 Benedikovič 5/7
Komptextkap05.doc Kap 5 Parser výrazov kde <názov súboru s príkazmi> je akceptovaný programom "calc" cez argument argv[1]. Popis častí programu: stack [], tos Main zásobník pre ukladanie vyhodnotených operácií zásobník a ukazovateľ na jeho vrchol jadrom programu je cyklus, ktorý sa vykonáva až po nájdení tokenu <PERIOD>, alebo po fyzický koniec, v ňom: ak je prečítaný token, volá sa AssignementStatement() pre vyhodnotenie priraďovacieho príkazu vyhodnotí sa oddeľovač; ak nie oddeľovačom bodkočiarka (;), je hlásená chyba hlavný program AssignementStatement () Analýza priraďovacieho príkazu prvá verzia v prípade, identifikátor nie je output, je uložený do tabuľky symbolov ako nová premenná ak nenasleduje lexém priradenia := hlási chybu, nie je to príkaz priradenia a je ukončená činnosť funkcie vyhodnotí sa výraz volaním funkcie Expression() ak je premenná output vypíše sa obsah vrcholu zásobníka je to hodnota výrazu na pravej strane príkazu ináč z vrcholu zásobníka sa prepíše hodnota do tabuľky symbolov k premennej Expression () rekurzívna Analýza výrazu volá funkciu SimpleExpression() pre vyhodnotenie jednoduchého výrazu v prípade, že sa ako ďalší token objaví relačný operátor, vyhodnotí sa funkciou SimpleExpression() a na vrchol zásobníka sa uloží hodnota 1 alebo 0 podľa výsledku relačnej operácie SimpleExpression () Analýza jednoduchého výrazu spracuje sa prípadný unárny operátor a volá spracovanie u funkciou Term() pokiaľ sa za om vyskytuje aditívny operátor (, alebo OR), spracúva ďalšie y Term() a do zásobníka uloží výsledok príslušnej operácie (súčet, rozdiel, alebo disjunkciu) Term () Analýza u prvku pokiaľ sa vyskytuje multiplikatívny operátor (, /, DIV, MOD alebo AND), spracúva faktory funkciou Factor() a do zásobníka uloží výsledok príslušnej operácie (súčin, podiel, celočíselné delenie alebo konjunkciu) Factor () Analýza faktora činiteľa Push () spracovanie činiteľa podľa aktuálneho tokenu: identifikátor do zásobníka uloží jeho hodnotu z tabuľky symbolov číslo do zásobníka uloží jeho hodnotu čísla podľa jeho typu (celé, reálne) NOT vyhodnotí sa Factor() a vrchol zásobníka obráti "logicky" svoju hodnotu ľavá zátvorka spracuje sa výraz vnorený do zátvoriek funkciou Expression() ináč je hlásená chyba nesprávneho výrazu po teste na preplnenie zvýši ukazovateľ na vrchol a uloží do neho hodnotu argumentu value Uloženie hodnoty do zásobníka Pop () makro Výber hodnoty zo zásobníka použitie makra poskytne hodnotu na vrchole zásobníka a zmenší ukazovateľ vrcholu na predchádzajúcu hodnotu Dôležitá poznámka: Pozor na identifikátor output!! Je to fiktívna premenná, ktorá iba indikuje žiadosť o výpis obsahu pravej strany priraďovacieho príkazu a ako s premennou sa s ňou nepracuje. Napríklad: a := 5; mocnina := a a; output := mocnina; Prvý riadok vyhodnotí číslo 5 a uloží ho do premennej a. Druhý príkaz vyhodnotí výraz a a a výsledok uloží prostredníctvom vrcholu zásobníka do premennej mocnina. Posledný riadok predstavuje činnosť výstupu hodnoty pravej strany príkazu. Identifikátor output zablokuje použitie premennej a spustí mechanizmus výpisu hodnoty pravej strany priraďovacieho príkazu. 5.6 Otázky a cvičenia 1. Trasujte volania funkcií parsera ručne, aby ste mohli sledovať jeho činnosť pri konverzii výrazu z infixu do postfixu. 2. V ktorom mieste výrazu detekuje parser chýbajúcu pravú zátvorku? 29.3.2007 19:57 Benedikovič 6/7
Komptextkap05.doc Kap 5 Parser výrazov 3. Parser vykonáva veľmi jednoduchú kontrolu chýb na najnižšej úrovni vo funkcii Factor(). Zamyslite sa, či môže byť vytvorená lepšia kontrola vo funkciách vyššej úrovne (s prípadným uzdravením chýb). 4. Vysvetlite, ako sú zapracované do parsera pravidlá priority pascalovských operátorov. 5. Pascal má iba 4 úrovne priority operátorov. a) Vysvetlite, prečo je nasledujúci výraz nesprávny: a = b AND c > d b) Trasujte z tohoto hľadiska volania funkcií a sledujte činnosť. c) Ako možno upraviť tabuľku priorít pascalovských operátorov tak, aby mal tento výraz zmysel? Ako by mal byť parser výrazov modifikovaný? 6. Je z hľadiska tohto parsera možná existencia prázdneho výrazu?. Čo s prázdnym príkazom? Ako kód parsera dovolí resp. nedovolí zostrojiť syntax prázdneho príkazu? 7. Popíšte podmienky, ktoré by mohli zapríčiniť preplnenie zásobníka.. Mal by parser kontrolovať podplnenie zásobníka? Má to zmysel? 9. Rozšírte program Calculator tak, aby manipuloval s malou množinou funkcií, napríklad Sqrt(), Sin(),... 29.3.2007 19:57 Benedikovič 7/7