LR-jäsennys Antti-Juhani Kaijanaho 3. lokakuuta 2016 Tämä lisämoniste esittelee Yaccin, CUPin ja muiden vastaavien ohjelmien käyttämän LR-jäsennysmenetelmäperheen. Se ei kuulu kurssin koealueeseen. Tehtävänä on rakentaa annetusta kontekstittomasta kieliopista vekotin, joka deterministisesti kykenee hyväksymään tai hylkäämään mielivaltaisen päätemerkkijonon. Kaikilla kieliopeilla se ei valitettavasti onnistu. Jatkossa oletetaan, että tarkasteltavissa kieliopeissa aloitussymboli ei esiinny minkään produktion oikealla puolella ja että aloitussymbolilla S on täsmälleen yksi produktio S A (jollakin muulla välikesymbolilla A). 1 Shift/reduce-jäsentäminen Tarkastellaan kielioppia S E E T E E + T T F T T F F c F (E) TIEA241 Automaatit ja kieliopit, syksy 2016. Tämän esityksen pääasiallinen lähde on Alfred V. Aho & Monica S. Lam & Ravi Sethi & Jeffrey D. Ullman: Compilers: Principles, Techniques & Tools, 2nd ed. Boston: Addison-Wesley, 2007; sivut 233 277. 1
sekä merkkijonoa w = c + c c. Sen eräs johto etenee seuraavasti: S E T E + T E + T F E + T c E + F c E + c c T + c c F + c c c + c c Johdossa valitaan systemaattisesti oikeanpuolimmaisin välikesymboli käsittelyyn. Jos johdon lukee lopusta alkuun c + c c F + c c T + c c E + c c E + F c E + T c E + T F E + T T E S huomaa, että päätemerkit syödään johdonmukaisesti vasemmalta oikealle ilman peruutusta. Tämä on alhaalta ylös ja vasemmalta oikealle etenevä jäsennys, jossa rakennetaan oikeanpuolimmainen johto. Englanniksi sama sanotaan sanoilla bottom-up left-to-right parse with rightmost derivation. Tämä lyhennetään LR: Left-to-right parse, Rightmost derivation. Kukin käännetyn johdon askel voidaan tulkita koostuvan kahdesta vaiheesta: shift n siirtää lukupäätä n merkkiä oikealle ja reduce p soveltaa lukukohdan vasemmalla puolella olevaan pääte- ja välikemerkkien jonoon produktiota p. Sitä osaa lukukohdan vasemmalla puolella olevasta symbolijonosta, johon produktiota sovelletaan, sanotaan kahvaksi (engl. handle). 2
Merkitään nyt käännetyn johdon askeliin lukukohta pisteellä sekä askeleessa sovellettu operaatio: c + c c shift 1, reduce F c F +c c T +c c E +c c E + F c E + T c E + T F E + T T shift 0, reduce T F shift 0, reduce E T shift 2, reduce F c shift 0, reduce T F shift 2, reduce F c shift 0, reduce T T F shift 0, reduce E E + T shift 0, reduce E T E shift 0, reduce S E S Varsinainen shift/reduce-jäsennys saadaan aikaan, kun pisteen vasen puoli erotetaan pinoksi, oikea puoli syötemerkkijonoksi ja shift-toiminnot eriytetään omiksi askeleikseen (ja vain yksi merkki kerrallaan): pino syöte toiminto c + c c shift c +c c reduce F c F +c c reduce T F T +c c reduce E T E +c c shift E+ c c shift E + c c reduce F c E + F c reduce T F E + T c shift E + T c shift E + T c reduce F c E + T F reduce T T F E + T reduce E E + T E reduce S E S accept Huomaa, että tässä on edelleen kyse samasta johdosta, esitystapa on vain muuttunut. 3
2 LR-jäsentimet LR-jäsennin koostuu pinosta, syötemerkkijonosta sekä tilakoneesta, joka päättää, mitä toimintoa sovelletaan. Pinossa on merkkien sijasta tilojen symboleita, ja tilakoneen nykyinen tila on se tila, joka on pinossa päällimmäisenä. Tilakoneen siirtymät määräytyvät kahden taulukon perusteella. Ensimmäinen tilakoneen taulukko on toimintotaulkko (engl. action table), jossa on rivi kullekin tilalle ja sarake kullekin kieliopin päätesymbolille sekä yksi lisäsarake syötteen lopulle (merkitään jatkossa ). Taulukon kussakin solussa on enintään yksi seuraavista toiminnoista: shift q, reduce A ω tai accept. Merkitään toimintotaulukon rivillä q sarakkeella c olevaa toimintoa action(q, c). Toinen taulukko on hyppytaulukko (engl. goto table). Siin on rivi kullekin tilalle ja sarake kullekin kieliopin välikesymbolille (aloitussymbolia lukuunottamatta). Kukin solu on joko tyhjä tai se sisältää tilan; merkitään hyppytaulukon rivillä q ja sarakkeella A olevaa tilaa goto(q, A). LR-jäsennin toimii seuraavasti: 1. Alusta pino sellaiseksi, että siinä on vain alkutila. 2. Toista seuraavaa: (a) Olkoon pino q 1 q n ja syöte joko cw tai ε (jolloin c = ). (b) Valitse: Jos action(q 1, c) = 3 Konfliktit shift q: muuta pino muotoon qq 1 q n ja muuta syöte muotoon c 2 c m. reduce A ω: muuta pino muotoon goto(q ω, A)q ω q n. accept: Lopeta hyväksyen. puuttuu: Lopeta hyläten. LR-jäsennyksessä voi tulla vastaan kahdenlaisia konfliktitilanteita: shift/reduce Toimintotaulukon samaan soluun kuuluisi sekä shift että reduce. (Vastaavasti shift/accept.) reduce/reduce Toimintotaulukon samaan soluun kuuluisi useampi kuin yksi reduce. (Vastaavasti reduce/accept.) 4
Konflikti tarkoittaa, että käytetty kontrolliyksikön rakennusmenetelmä ei sovellu tarkasteltavan kieliopin jäsennykseen. Usein (mutta ei aina) shift/reducekonfliktit voidaan ratkaista raa asti valitsemalla shift-toiminto ilman, että käytännössä siitä tulee isompaa ongelmaa, mutta reduce/reduce-konflikti pakottaa aina joko kieliopin taikka menetelmän vaihtamiseen. 4 SLR SLR on yksinkertaisin käyttökelpoinen LR-jäsennysmenetelmä. Siinä kieliopista rakennetaan ensiksi deterministinen äärellinen automaatti, ns. kanoninen LR(0)-automaatti, seuraavasti: 1. Jokaisesta kieliopin produktiosta muodostetaan (LR(0)-)asetelmat (engl. (LR(0)) items) varustamalla produktio indeksillä, joka osoittaa jotain produktion oikean puolen merkkiä (taikka produktion loppua). Idea on tuttu Earleyn algoritmista, ja samaan tapaan indeksi esitetään pisteenä. Näin esimerkiksi produktiosta E E + T saadaan asetelmat E E + T, E E +T, E E + T ja E E + T. 2. Kukin LR(0)-asetelma on yksi automaatin tila. 3. Alkutila on asetelma S A, missä S on aloitussymboli ja S A on sen ainoa produktio. 4. Ainoa hyväksyvä tila on asetelma S A. 5. Automaatissa on kaikki seuraavanlaiset siirtymät (α on yksi pääte- tai välikemerkki): (A ω 1 αω 2 ) α (A ω 1 α ω 2 ) (A ω 1 Bω 2 ) ε (B ω) 6. Näin saatu automaatti determinisoidaan osajoukkokonstruktiolla. Kanonisen LR(0)-automaatin tilat (siis asetelmajoukot), jotka eivät ole turhia, lukuunottamatta hylkäystilaa, muodostavat kanonisen LR(0)-asetelmajoukkokokoelman (engl. canonical collection of sets of LR(0) items). 5
Prujun alussa annetusta kieliopista syntyy seuraavanlainen LR(0)-automaatti (hylkäystilan ja siirtymät siihen olen jättänyt merkitsemättä): tilaan kuuluvat asetelmat c + ( ) E T F 0 S E, E T, E E + T, 1 2 3 4 5 T F, T T F, F c, F (E) 1 F c 2 F ( E), E T, E E + T, T F, T T F, F c, F (E) 1 2 6 4 5 3 S E, E E +T 7 4 E T, T T F 8 5 T F 6 F (E ), E E +T 7 9 7 E E+ T, T F, T T F, 1 2 10 5 F c, F (E) 8 T T F, F c, F (E) 1 2 11 9 F (E) 10 E E + T, T T F 8 11 T T F SLR-jäsentimessä on tila kutakin kanonisen LR(0)-automaatin tarpeellista tilaa kohti, hylkäävää -tilaa lukuunottamatta. Toiminto- ja hyppytaulukot konstruoidaan automaatin perusteella seuraavasti: Jos q c q on siirtymä LR(0)-automaatissa ja c on päätemerkki, aseta action(q, c) := shift q. Jos q A q on siirtymä LR(0)-automaatissa ja A on välikesymboli, aseta goto(q, A) = q. Jos tilassa q on asetelma A ω (missä A ei ole aloitussymboli), aseta action(q, c) := reduce A ω kaikilla c FOLLOW(A). 1 Jos tila q on hyväksyvä, aseta action(c, ) = accept. Jos samaan action-soluun olisi tulossa useampia toimintoja, kyse on konfliktista ja SLR-jäsentimen luonti epäonnistuu. 1 Jos tämä tehtäisiin kaikilla päätesymboleilla c, mukaanlukien, tuloksena olisi SLRjäsentimen asemesta LR(0)-jäsennin. 6
Edellä annetusta esimerkkiautomaatista syntyy seuraavanlainen SLR-jäsennin (merkitään toimintoja alkukirjaimellaan ja viitataan kuhunkin produktioon järjestysnumerollaan): action goto c + ( ) E T F 0 s1 s2 3 4 5 1 r5 r5 r5 r5 2 s1 s2 6 4 5 3 s7 a 4 r1 s8 r1 r1 5 r3 r3 r3 r3 6 s7 s9 7 s1 s2 10 5 8 s1 s2 11 9 r6 r6 r6 r6 10 r2 s8 r2 r2 11 r4 r4 r4 r4 Produktioiden järjestysnumerot ovat S E 0 E T 1 E E + T 2 T F 3 T T F 4 F c 5 F (E) 6 Merkkijonon c + c c jäsennys tällä SLR-jäsentimellä etenee seuraavasti (pino kasvaa tässä esityksessä oikealle toisin päin kuin algoritmikuvauksessa joten nykyinen tila on pinon oikeanpuolimmaisin): 7
pino syöte toiminto 0 c + c c shift 1 0 1 +c c reduce F c 0 5 +c c reduce T F 0 4 +c c reduce E T 0 3 +c c shift 7 0 3 7 c c shift 1 0 3 7 1 c reduce F c 0 3 7 5 c reduce T F 0 3 7 10 c shift 8 0 3 7 10 8 c shift 1 0 3 7 10 8 1 reduce F c 0 3 7 10 8 11 reduce T T F 0 3 7 10 reduce E E + F 0 3 accept 5 LR(1) ja LALR SLR:ää yleiskäyttöisempi menetelmä on LR(1). Menetelmän perusrakenne on sama: rakennetaan automaatti, josta sitten johdetaan jäsennystaulukko. Olennainen ero menetelmissä on asetelmien muodossa: LR(1)-asetelma on pari, johon sisältyy LR(0)-asetelma ja päätemerkki (taikka päätemerkin asemesta syötteen päättymisen merkki ). Kyseinen päätemerkki on ns. ennakointimerkki (engl. lookahead symbol), joka deaktivoi asetelman sellaisissa tilanteissa, joissa ennakointimerkki ei voi esiintyä tarkasteltavan produktion tuottaman merkkijonon jälkeen. Kanoninen LR(1)-automaatti rakennetaan samaan tapaan kuin kanoninen LR(0)-automaatti determinisoimalla seuraavasti rakentuva NFA: Kukin LR(1)-asetelma on tila. Alkutila on asetelma S A, (missä S A on aloitussymbolin ainoa produktio). Ainoa hyväksyvä tila on asetelma S A,. Automaatissa on kaikki seuraavanlaiset siirtymät (α on yksi pääte- tai välikemerkki ja c FIRST(ω 2 c)): (A ω 1 αω 2, c) α (A ω 1 α ω 2, c) (A ω 1 Bω 2, c) ε (B ω, c ) 8
9: F -> '(' E ')'. { ')' '*' '+' $end } ')' '+' 6: E -> E. '+' T F -> '(' E. ')' E 2: E ->. T E ->. E '+' T T ->. F T ->. T '*' F F ->. c F ->. '(' E ')' F -> '('. E ')' '(' '(' T 7: E -> E '+'. T T ->. F T ->. T '*' F F ->. c F ->. '(' E ')' 4: E -> T. { ')' '+' $end } T -> T. '*' F '(' '*' '+' F 3: $accept -> E. $end E -> E. '+' T F '(' T c 8: T -> T '*'. F F ->. c F ->. '(' E ')' F 11: T -> T '*' F. { ')' '*' '+' $end } T c '*' 5: T -> F. { ')' '*' '+' $end } E c 10: E -> E '+' T. { ')' '+' $end } T -> T. '*' F F 0: $accept ->. E $end E ->. T E ->. E '+' T T ->. F T ->. T '*' F F ->. c F ->. '(' E ')' c 1: F -> c. { ')' '*' '+' $end } Kuva 1: Berkeley Yaccin tuottama kaavio LR(1)-jäsennin konstruoidaan kanonisesta LR(1)-automaatista samaan tapaan kuin SLR-jäsennin LR(0)-automaatista. Ainoa ero on reduce-toimintojen muodostamisessa: kun SLR-jäsentimessä reduce lisätään kaikille merkeille joukossa FIRST(A), niin LR(1)-jäsentimessä se laitetaan vain kyseessä olevan asetelman ennakointimerkille. Koska LR(1)-jäsennin on usein varsin iso, käytetään käytännön työssä LALR-menetelmää (Look-Ahead LR). Ideana tässä on liittää kuhunkin asetelmaan yhden ennakointimerkin asemesta ennakointimerkkien joukko. Tämä pienentää jäsentimen kokoa merkittävästi, mutta luo joskus reduce/reducekonflikteja. Esimerkiksi LR(1)-tilat {(A c, d), (B c, e)} ja {(A c, e), (B c, d)} eivät konfliktoi, mutta LALR-muunnos synnyttää konfliktisen tilan{(a c, {d, e}), (B c, {d, e})}. Berkeley Yacc (löytyy halava- ja jalava-koneista nimellä byacc) on siitä 9
mukava jäsenningeneraattori, että se generoi haluttaessa kuvallisen esityksen jäsentimen taustalla olevasta kanonisesta automaatista. Kuva 1 on generoitu Yacc-lähdetiedostosta %token c %% E : T E + T ; T : F T F ; F : c ( E ) ; komennoilla 2 byacc g e. y c i r c o Tpdf y. dot > y. pdf Kaikki jäsenningeneraattorit tuottavat pyynnöstä tekstuaalisen esityksen tuotetusta jäsentimestä. Esimerkiksi komennolla byacc v e. y syntyy tiedostoon y.output seuraavanlainen kuvaus: 0 $accept : E $end 1 E : T 2 E + T 3 T : F 4 T F 5 F : c 6 ( E ) s t a t e 0 $accept :. E $end ( 0 ) c s h i f t 1 ( s h i f t 2. e r r o r E goto 3 T goto 4 F goto 5 2 Komento circo kuuluu Graphviz-pakettiin, joka ei valitettavasti ole halavalle eikä jalavalle asennettu. 10
s t a t e 1 F : c. ( 5 ). reduce 5 s t a t e 2 F : (. E ) ( 6 ) c s h i f t 1 ( s h i f t 2. e r r o r E goto 6 T goto 4 F goto 5 s t a t e 3 $accept : E. $end ( 0 ) E : E. + T ( 2 ) $end accept + s h i f t 7. e r r o r s t a t e 4 E : T. ( 1 ) T : T. F ( 4 ) s h i f t 8 $end reduce 1 + reduce 1 ) reduce 1 s t a t e 5 T : F. ( 3 ). reduce 3 s t a t e 6 E : E. + T ( 2 ) F : ( E. ) ( 6 ) + s h i f t 7 ) s h i f t 9 11
. e r r o r s t a t e 7 E : E +. T ( 2 ) c s h i f t 1 ( s h i f t 2. e r r o r T goto 10 F goto 5 s t a t e 8 T : T. F ( 4 ) c s h i f t 1 ( s h i f t 2. e r r o r F goto 11 s t a t e 9 F : ( E ). ( 6 ). reduce 6 s t a t e 10 E : E + T. ( 2 ) T : T. F ( 4 ) s h i f t 8 $end reduce 2 + reduce 2 ) reduce 2 s t a t e 11 T : T F. ( 4 ). reduce 4 7 terminals, 4 nonterminals 7 grammar r u l e s, 12 s t a t e s 12