TIE448 Kääntäjätekniikka, syksy 2009 Antti-Juhani Kaijanaho TIETOTEKNIIKAN LAITOS 29. syyskuuta 2009
Sisällys
Sisällys
Seuraava deadline Vaihe B tiistai 6.10. klo 10 selaaja ja jäsentäjä toimivat
Sisällys
Kertaus: item Item koostuu produktiosta sekä indeksistä sen oikean puolen symbolien väleihin. Indeksin osoittamaa paikkaa merkitään tavallisesti piseteellä. Esimerkkinä produktion E E + T itemit: E E + T E E + T E E + T E E + T Pistettä edeltävä osa on jo luettu, pisteen jälkeistä osaa odotetaan.
Item-joukon sulkeuma Määritelmä Item-joukon I sulkeuma tarkoittaa pienintä joukkoa C(I), jolle pätevät seuraavat väitteet: 1. I C(I) 2. Jos C(I):ssä on item A α Bβ (joillakin A, α, B, β) ja kieliopista löytyy produktio B γ (jollakin γ), niin C(I):stä löytyy item B γ.
Item-joukon sulkeuma algoritmi 1. Syötteenä item-joukko I. 2. Alusta kaksi paikallista muuttujaa: C, joka on itemjoukko, ja Q, joka on itemjono. C on aluksi I, ja Q:ssa on aluksi (mielivaltaisessa järjestyksessä) I:n alkiot. 3. Toista seuraavaa kunnes Q on tyhjä: 3.1 Poista Q:sta jonon ensimmäinen alkio. 3.2 Jos poistamassasi itemissä on pisteen perässä välikesymboli, nimetään ko. välikesymboli A:ksi. Muutoin hypätään seuraavan askeleen yli. 3.3 Jokaiselle kieliopin produktiolle A α tee seuraavaa: Jos item A α ei ole C:ssä, lisää se C:hen sekä Q:n loppuun. 4. Lopputulos on nyt C:ssä.
Funktio GOTO Määritelmä GOTO(I, X) = C { A αx β (A α Xβ) I } Huomioita X voi olla niin pääte- kuin välikesymbolikin. GOTO(I, X) jättää huomiotta kaikki ne I:n itemit, joissa pistettä ei seuraa X. Tulosjoukossa on piste siirretty X:n yli.
Kanoninen LR(0)-automaatti Määritelmä Kanoniselle LR(0)-automaatille pätee seuraavaa: Sen tilat ovat itemjoukkoja. Sen aloitustila on C({S S}), missä S on uusi välikesymboli ja S on kieliopin aloitussymboli. Jos itemjoukko I on automaatin tila, niin jokaiselle pääte- ja välikesymbolille X pätee seuraavaa: Jos GOTO(I, X) ei ole tyhjä, niin GOTO(I, X) on automaatin tila ja automaatissa on X:llä laputettu siirtymä I:stä GOTO(I, X):ään. Automaatissa ei ole muunlaisia tiloja tai tilasiirtymiä.
LR-jäsennysalgoritmi: tietorakenteet Syöte on päätesymbolijono, jota käsitellään vain kahdella operaatiolla: tarkista, mikä symboli on syötteen alussa, ja poista syötteen alusta yksi päätesymboli. Pino sisältää tiloja (jotka yleensä on numeroitu, jolloin pinossa on vain numeroita). Pinoa käsitellään tavanomaisen pinon tapaan.
LR-jäsennysalgoritmi: ohjaustaulukot Siirtymätaulukko on kaksiulotteinen taulukko, jossa rivejä indeksoidaan tiloilla ja sarakkeita välikesymboleilla. Kussakin solussa on tila. Toimintotaulukko on kaksiulotteinen taulukko, jossa rivejä indeksoidaan tiloilla ja sarakkeita päätesymboleilla (ynnä syötteen loppumisella). Kussakin solussa on jokin seuraavista : SHIFT n poista syötteen ensimmäinen merkki ja lisää pinoon n; REDUCE S γ poista pinon päältä γ:n pituuden verran tiloja, hae siirtymätaulukon nykyisen tilan riviltä ja välikesymbolin S sarakkeelta uusi tila ja pistä tuo tila pinoon; ACCEPT lopeta jäsentäminen tähän; ja REJECT keskeytä jäsennys kielioppivirheen takia.
LR-jäsennysalgoritmi 1. Laita pinoon LR-automaatin aloitustila. 2. Olkoon a syötteen ensimmäinen symboli, tai jos syöte on lopussa, syötteen loppumisen merkki. 3. Olkoon s pinon päällimmäinen tila. 4. Tarkastellaan toimintotaulukon riviä s ja saraketta a: Jos siinä on SHIFT s, 4.1 poista syötteestä sen ensimmäinen symboli ja 4.2 laita pinoon s. Jos siinä on REDUCE A γ: 4.1 poista pinosta γ:n pituuden verran tiloja, 4.2 nimeä väliaikaisesti pinosta nyt löytyvä tila s :ksi, 4.3 laita pinoon siirtymätaulukon riviltä s ja sarakkeelta A löytyvä tila, ja 4.4 suorita produktioon A γ liittyvä ohjelmakoodi. Jos siinä on ACCEPT, lopeta. Jos siinä on REJECT, diagnosoi virhe ja lopeta. 5. Palaa kohtaan 2.
LR(0)-ohjaustaulukkojen konstruointi 1 Jos jossakin kanonisen LR(0)-automaatin tilassa I on item A α aβ (missä a on päätesymboli), merkitse toimintotaulukon riville I sarakkeeseen a SHIFT J, missä J on I:stä a:lla laputetun siirtymän kohde. Jos jossakin tilassa I on item A α (missä A ei ole S ), merkitse toimintotaulukon riville I kaikkiin sarakkeisiin REDUCE A α. Jos jossakin tilassa I on item S S, merkitse toimintotaulukon riville I syötteen loppumista merkitsevään sarakkeeseen ACCEPT. Merkitse siirtymätaulukon riville I sarakkeeseen A GOTO(I, A), jos määritelty. Jos jokin solu jää näin tyhjäksi, merkitse siihen REJECT. 1 Tällä kalvolla S tarkoittaa automaatin konstruktiossa luotua uutta välikesymbolia ja S kieliopin aloitussymbolia.
LR(0) konfliktit Jos toimintotaulukkoon tulisi samaan soluun useampi kuin yksi toiminto, on kyseessä konflikti. Tällöin kielioppi ei ole LR(0) ja jäsentimen konstruktio epäonnistuu. LR(0):ssa konflikteja tulee erittäin herkästi riittää jos samassa tilassa on itemeitä sekä muotoa A α että muunlaisia.
SLR(1)-toimintotaulukon konstruointi SLR on Simple LR Muuten samanlainen kuin LR(0), paitsi reduce-toiminnon osalta: Jos jossakin tilassa I on item A α (missä A ei ole S ), merkitse toimintotaulukon riville I REDUCE A α niihin sarakkeisiin, joiden päätesymbolit kuuluvat joukkoon FOLLOW(A). Tavanomainen lausekekielioppi on SLR(1) mutta ei LR(0).
Esimerkki kieliopista joka ei ole SLR(1) S L = R R L R id R L
Sisällys
LR(1)-item Kuten LR(0)-item, eli produktio, jossa on piste oikealla puolella, mutta: lisäksi päätesymboli (tai syötteen loppumista tarkoittava merkintä) eli lookahead-symboli. Notaatio: A α β, a Tulkinta: α on luettu, β:aa odotetaan, ja β:n jälkeen odotetaan a:ta. LR(k):ssa k tarkoittaa lookahead-symbolijonon pituutta; k > 1 on erittäin harvinainen.
LR(1)-itemjoukon sulkeuma Määritelmä Item-joukon I sulkeuma tarkoittaa pienintä joukkoa C(I), jolle pätevät seuraavat väitteet: 1. I C(I) 2. Jos C(I):ssä on item A α Bβ, a (joillakin A, α, B, β), kieliopista löytyy produktio B γ (jollakin γ) ja on olemassa b FIRST(βa), niin C(I):stä löytyy item B γ, b.
LR(1)-itemjoukon sulkeuma algoritmi 1. Syötteenä item-joukko I. 2. Alusta kaksi paikallista muuttujaa: C, joka on itemjoukko, ja Q, joka on itemjono. C on aluksi I, ja Q:ssa on aluksi (mielivaltaisessa järjestyksessä) I:n alkiot. 3. Toista seuraavaa kunnes Q on tyhjä: 3.1 Poista Q:sta jonon ensimmäinen alkio. 3.2 Jos poistamassasi itemissä on pisteen perässä välikesymboli, nimetään ko. välikesymboli A:ksi. Muutoin hypätään seuraavan kolmen askeleen yli. 3.3 Nimetään poistetun itemin lookahead-symboli a:ksi. 3.4 Nimetään poistetun itemin pisteen jälkeisen A:n jälkeinen osa β:ksi. 3.5 Jokaiselle kieliopin produktiolle A α ja jokaiselle b FIRST(βa) tee seuraavaa: Jos item A α, b ei ole C:ssä, lisää se C:hen sekä Q:n loppuun. 4. Lopputulos on nyt C:ssä.
Funktio GOTO Määritelmä GOTO(I, X) = C { A αx β, a (A α Xβ, a) I }
Kanoninen LR(1)-automaatti Määritelmä Kanoniselle LR(1)-automaatille pätee seuraavaa: Sen tilat ovat itemjoukkoja. Sen aloitustila on C({S S, $}), missä S on uusi välikesymboli, S on kieliopin aloitussymboli ja $ tarkoittaa syötteen loppumista. Jos itemjoukko I on automaatin tila, niin jokaiselle pääte- ja välikesymbolille X pätee seuraavaa: Jos GOTO(I, X) ei ole tyhjä, niin GOTO(I, X) on automaatin tila ja automaatissa on X:llä laputettu siirtymä I:stä GOTO(I, X):ään. Automaatissa ei ole muunlaisia tiloja tai tilasiirtymiä.
LR(1)-ohjaustaulukkojen konstruointi 2 Jos jossakin kanonisen LR(1)-automaatin tilassa I on item A α aβ, b (missä a on päätesymboli), merkitse toimintotaulukon riville I sarakkeeseen a SHIFT J, missä J on I:stä a:lla laputetun siirtymän kohde. Jos jossakin tilassa I on item A α, a (missä A ei ole S ), merkitse toimintotaulukon riville I sarakkeeseen a REDUCE A α. Jos jossakin tilassa I on item S S, $, merkitse toimintotaulukon riville I syötteen loppumista merkitsevään sarakkeeseen ACCEPT. Merkitse siirtymätaulukon riville I sarakkeeseen A GOTO(I, A), jos määritelty. Jos jokin solu jää näin tyhjäksi, merkitse siihen REJECT. 2 Tällä kalvolla S tarkoittaa automaatin konstruktiossa luotua uutta välikesymbolia ja S kieliopin aloitussymbolia.
Lookahead LR (LALR) C:n kaltaisen kielen LR(1)-jäsentäjässä on tyypillisesti tuhansia tiloja. Lookahead LR (LALR) on lähes LR(1):n veroinen menetelmä, jolla tilojen määrä tippuu merkittävästi. Ideana yhdistää LR(1)-tilat, joissa on lookahead-symboleja vaille samat itemit tällöin LALR-itemissä on lookahead-symbolijoukko.
LR LALR ei tuota shift/reduce-konfliktia Oletetaan, että LALR-jäsentimessä on shift/reduce-konflikti. Tällöin täytyy olla jossakin tilassa sekä A α, {..., a,...} että B β aγ, {..., b,...}. Tällöin alkuperäisessä LR-jäsentimessä täytyy olla tila, jossa on sekä A α, a että B β aγ, b. Kyseisessä LR-tilassa on shift/reduce-konflikti.
LR LALR voi luoda r/r-konfliktin Tarkastellaan LR(1)-tiloja ja {(A c, d), (B c, e)} {(A c, e), (B c, d)}. Kummassakaan ei ole konfliktia. Näistä syntyy LALR(1)-tila {(A c, {d, e}), (B c, {d, e})}, jossa on reduce/reduce-konflikti.
Sisällys
toiminnot Sekä LL- että LR-jäsennyksessä ohjelmoija voi yleensä liittää produktioon ohjelmakoodia. Ohjelmakoodissa on käytettävissä produktion oikean puolen symbolien semanttiset arvot. Ohjelmakoodin odotetaan laskevan produktion vasemman puolen välikesymbolille semanttisen arvon. LL-jäsentimessä ohjelmakoodi suoritetaan produktion toteuttavan koodin lopuksi, ja laskettu semanttinen arvo palautetaan aliohjelman paluuarvona. LR-jäsentimessä ohjelmakoodi suoritetaan produktion REDUCEn yhteydessä, ja semanttinen arvo liitetään pinossa olevan tilaan lisätiedoksi. Päätesymbolin semanttinen arvo liitetään pinon tilaan toki SHIFTin yhteydessä.
Esimerkki: käsin koodattu prediktiivinen 3 private Expression parseprefixexpression() throws IOException, ParseException { Pos pos = l.getpos(); int op = l.peek(); switch (op) { case + : l.get(); return new UnaryPlusExpression(pos, parseprefixexpression()); case - : l.get(); return new UnaryMinusExpression(pos, parseprefixexpression()); case ~ : l.get(); return new BitwiseNotExpression(pos, parseprefixexpression()); case ( : if (istypefirst(l.peek(1))) { l.get(); Type ty = parsetype(); l.get( ) ); return new CastExpression(pos, ty, parseprefixexpression()); } } /* passthrough */ default: return parsepostfixexpression(); } 3 http://antti-juhani.kaijanaho.fi/darcs/darcsweb/ darcsweb.cgi?r=alkeis2007-compiler;a=headblob;f= /alkeis/parser.java
Esimerkki: CUP multiplicative_expression ::= prefix_expression:e {: RESULT = e; :} multiplicative_expression:e1 ASTERISK prefix_expression:e2 {: RESULT = new MultiplicationExpression(e1,e2); :} multiplicative_expression:e1 SOLIDUS prefix_expression:e2 {: RESULT = new DivisionExpression(e1,e2); :} multiplicative_expression:e1 PERCENT prefix_expression:e2 {: RESULT = new RemainderExpression(e1,e2); :} ;
Määrittelyjen kommunikointi selaajalle Jos kielessä on tarpeen erotella eri nimisanasluokkia sen mukaan, mitä on ohjelmassa määritelty, tulee jäsentimen päivittää selaajan näkemystä nimiluokista määrittelyn tultua jäsennettyä. Tällöin selaajassa on hyvä olla kolme metodia void enterscope(), void leavescope() ja void declaretypename(string) (tai vastaava). enterscope pistää muistiin näkyvyysalueen alkamisen. leavescope peruu kaikki declaretypenamet, jotka on tehty viimeisimmän leavescope-parittoman enterscopen jälkeen. declaretypename lisää annetun nimen tyyppinimien luetteloon.
Toteutus selaajassa Tyyppinimien luettelo koostuu kahdesta tietorakenteesta: hajautustaulusta, jossa on kulloinkin voimassa olevat tyyppinimet, ja pinosta, jonka alkioissa on tieto muutetusta nimestä sekä sen vanhasta statuksesta. enterscope laittaa pinoon jonkinlaisen merkin. leavescope käy pinoa läpi kunnes löytää merkin, ja palauttaa pinossa olevien alkioiden avulla nimille niiden vanhat statukset. declaretypename laittaa pinoon nimen vanhan statuksen ja kirjaa uuden statuksen hajautustauluun. Esim. mallikääntäjän fi.jyu.ties448.eska.util.scopedset.
Toteutus jäsentäjässä Kun on jäsennetty uuden määrittelyalueen alku (esim. lohkon alku), tulee jäsentäjän kutsua selaajan enterscopea. Kun on jäsennetty määrittelyalueen loppu (esim. lohkon loppu), tulee jäsentäjän kutsua selaajan leavescopea. Kun jäsennetty määrittely, tulee jäsentäjän kutsua selaajan declaretypenamea. Huom! Tämä on usein ennen määrittelyn lopullista päättymistä esim. luokkanimi pitää declaretypenamettaa ennen luokan jäsenten (attribuutit, metodit) jäsentämistä!
Esimerkki CUP:ssa block ::= block_head:l stmts:ss CBRACE:r {: RESULT = new BlockStatement(ss, new Pos(l.pos,r.pos)); getca().leavescope(); :} ; block_head ::= OBRACE:it {: getca().enterscope(); RESULT = it; :} ; class_definition ::= class_head:ch extends_opt:ex OBRACE class_body:b CBRACE:r {: RESULT = new ClassDefinition(ch, ex, b, new Pos(ch.pos, r.pos)); :} class_head:d SEMICOLON {: RESULT = d; :} ; class_head ::= CLASS:l IDENTIFIER:id {: getca().declaretypename(id.value); RESULT = new ClassDeclaration(id, new Pos(l.pos, id.pos)); :} CLASS:l TYPENAME:id {: RESULT = new ClassDeclaration(id, new Pos(l.pos, id.pos)); :} ;
Sisällys
Käyttäjä usein odottaa, että kääntäjä diagnosoi muitakin virheitä kuin ensimmäisen vastaantulleen. Jäsentimen tulee tällöin kyetä toipumaan virheistä. Yksinkertainen tapa on paniikkitila: syödään syötettä kunnes löydetään synkronointimerkki (esim. ;), ja poistetaan pinosta kamaa kunnes löytyy jotain, joka osaa sen syödä. Jäsennyksen päätyttyä tarkistetaan, annettiinko virhediagnooseja, ja lopetetaan käännös jos annettiin.
Paniikkitila käsin kirjoitetussa jäsentimessä Kun havaitaan virhe, diagnosoidaan se ja heitetään poikkeus. Jokin toipumiseen soveltuva produktioaliohjelma (esim. statement) nappaa poikkeuksen kiinni, syö syötettä kunnes löytää synkronointimerkin ja sitten jatkaa kuin mitään ei olisi tapahtunut. Hyviä synkronointimerkkejä ovat synkronoivan välikesymbolin FOLLOW-joukon alkiot, sekä tyypilliset synkronoivan välikesymbolin päättävät päätesymbolit (usein puolipiste).
Paniikkitila LR-jäsentimessä Kun havaitaan virhe, poistetaan pinosta tiloja kunnes löytyy sellainen, jossa on SHIFT-toiminto fiktiiviselle ERROR-päätesymbolille, toteutetaan ko. SHIFT koskematta kuitenkaan syötteeseen, ja jatketaan kuin mitään ei olisi tapahtunut. tulee lisätä kielioppiin sopiviin kohtiin ERROR-päätesymboli. Hyvä idea on lisätä statement-nonterminaalille (S) produktio S ERROR ; Eli siis ERROR, ja sen perään synkronointimerkki. tulee myös yleensä tällöin itse tuottaa virhediagnoosi error-produktion ohjelmakoodissa.
Sisällys
Seuraava deadline Vaihe B tiistai 6.10. klo 10 selaaja ja jäsentäjä toimivat