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
Kääntäjän rakenne lähdeohjelma SELAAJA sanasjono JÄSENTÄJÄ VÄLIKOODIN GENEROIJA rakennepuu välikoodi KOHDEKOODIN GENEROIJA kohdeohjelma TARKASTAJA SOKERINPILKKOJA OPTIMOIJA OPTIMOIJA
Jäsentäjän tehtävät selaajan tuottaman sanasjonon muuttaminen ohjelman loogista rakennetta kuvaavaksi tietorakenteeksi ohjelmassa olevien kielioppivirheiden tunnistaminen ja diagnosointi tällä luennolla oletetaan annetuksi jäsennettävä kielioppi
Sisällys
engl. recursive descent parsing Tehdään kustakin välikesymbolista aliohjelma, joka kokeilee kutakin produktiota vuorollaan. Päätesymbolin kohdalla yritetään ottaa se selaimelta. Välikesymbolin kohdalla kutsutaan sitä vastaavaa aliohjelmaa. Jos jäsennys ei onnistu, peruutetaan (backtrack) lähimpään tehtyyn valintaan, jossa ei ole vielä kaikki vaihtoehdot käyty läpi.
/* <statement> ::= if ( <expression> ) <statement> if ( <expression> ) <statement> else <statement> return ; return <expression> ; */ Statement parsestatement(lexer l) throws ParserException { Lexer.State st = l.savestate(); try { l.get(lexer.if); l.get(lexer.oparen); Expression tst = parseexpression(l); l.get(lexer.cparen); Statement thn = parsestatement(l); Statement els = null; if (l.peek() == Lexer.ELSE) { l.get(); els = parsestatement(l); } return new IfStatement(tst, thn, els); } catch (ParserException e) {} l.restorestate(st); try { l.get(lexer.return); Expression e = null; Lexer.State st1 = l.savestate(); try { e = parseexception(l); } catch (ParserException e) { l.restorestate(st1); } l.get(lexer.semicolon); return new ReturnStatement(e); } catch (ParserException e) {} l.restorestate(st); throw ParserException("failed to parse a statement"); }
Pro ja contra + Erittäin helppo koodata käsin kieliopista lähtien. Jos kielioppi on vasenrekursiivinen, algoritmi kaatuu. Algoritmi tekee älyttömästi turhaa työtä kokeillessaan kaikkia vaihtoehtoja.
Vasemman rekursion poisto X α 1 X X Xγ 1...... X α m X X Xγ n = X γ 1 X X α 1...... X γ m X X α m X γ i ja α i eivät ala X:llä
A x A (E) F A F F A F F / A E F E E + F E E F = A x A (E) F AF F F AF F / AF E FE E E + FE E FE
Sama koodina Expression parseexpression(lexer l) throws ParserException { Expression e = parseterm(l); return parseexpressionprime(l, e); } Expression parseexpressionprime(lexer l, Expression e1) throws ParserException { Lexer.State st = l.savestate(); try { l.get(lexer.plus); Expression e2 = parseterm(l); return parseexpressionprime(l, new AdditionExpression(e1,e2)); } catch (ParserException e) {} l.restorestate(st); try { l.get(lexer.minus); Expression e2 = parseterm(l); return parseexpressionprime(l, new AdditionExpression(e1,e2)); } catch (ParserException e) {} l.restorestate(st); return e1; }
Poistetaan häntäkutsuja Expression parseexpression(lexer l) throws ParserException { Expression e1 = parseterm(l); while (true) { Lexer.State st = l.savestate(); try { l.get(lexer.plus); Expression e2 = parseterm(l); e1 = parseexpressionprime(l, new AdditionExpression(e1,e2)); continue; } catch (ParserException e) {} l.restorestate(st); try { l.get(lexer.minus); Expression e2 = parseterm(l); e2 = parseexpressionprime(l, new AdditionExpression(e1,e2)); continue; } catch (ParserException e) {} l.restorestate(st); return e1; } }
Ennustava jäsennys Usein on mahdollista päättää heti, onko jokin produktio mahdollinen. Esim. E + TE ei tule kyseeseen, jos seuraava token ei ole +. Tällöin kokeile-ja-peruuta on täysin älytön idea. Ideaalitilanne on, jossa peruutusta ei tarvita lainkaan. Tällöin kyse on ennustavasta jäsennyksestä (engl. predictive parsing.
Expression parseexpression(lexer l) throws ParserException { Expression e1 = parseterm(l); while (true) { switch (l.peek()) { case Lexer.PLUS: l.get(); Expression e2 = parseterm(l); e1 = parseexpressionprime(l, new AdditionExpression(e1,e2)); continue; case Lexer.MINUS: l.get(); Expression e2 = parseterm(l); e2 = parseexpressionprime(l, new AdditionExpression(e1,e2)); continue; default: return e1; } } }
Milloin tietty produktio voidaan ennustaa? NULLABLE(X) on tosi, jos X:stä voidaan johtaa tyhjä merkkijono. NULLABLE(X) = tosi NULLABLE(X) = NULLABLE(Y i ) jos on produktio X jos on produktio X Y 1 Y i Y n ja jos NULLABLE(Y 1 ) NULLABLE(Y i 1 ) FIRST(γ) on kaikkien niiden päätesymbolien (ynnä syötteen loppu) joukko, jotka voivat aloittaa γ:n. FIRST(a) = {a} FIRST(X) = FIRST(X) FIRST(Y i ) jos a on päätesymboli jos on produktio X Y 1 Y i Y n ja jos NULLABLE(Y 1 ) NULLABLE(Y i 1 ) FOLLOW(X) on kaikkien niiden päätesymbolien (ynnä syötteen loppu) joukko, jotka voivat seurata Xää. FOLLOW(Y i ) = FOLLOW(Y i ) FIRST(Y j ) jos on produktio X Y 1 Y i Y j Y n ja NULLABLE(Y i+1 ) NULLABLE(Y j 1 ) FOLLOW(Y i ) = FOLLOW(Y i ) FOLLOW(X) jos on produktio X Y 1 Y i Y n ja jos NULLABLE(Y i+1 ) NULLABLE(Y n)
Esimerkki E TE E T x E + TE T (E) E TE NULLABLE FIRST FOLLOW E ei x, ( ), eof E kyllä +, ), eof T ei x, ( +,, ), eof
Ennustava jäsennystaulukko rivi jokaiselle välikesymbolille sarake jokaiselle päätesymbolille (ynnä syötteen loppu) Merkitse produktio X γ riville X ja sarakkeeseen t jokaiselle t FIRST(γ), ja jos NULLABLE(γ), myös jokaiselle t FOLLOW(X).
Esimerkki E TE E T x E + TE T (E) E TE NULLABLE FIRST FOLLOW E ei x, ( ), eof E kyllä +, ), eof T ei x, ( +,, ), eof x + ( ) eof E E TE E TE E E +TE E TE E E T T x T (E)
Taulukon tulkinta Tee jokaiselle välikesymbolille aliohjelma. Aliohjelman alussa tee switch case kaikille päätesymboleille (ynnä syötteen päättymiselle). Jos välikesymbolin X ja päätesymbolin t risteyskohta sisältää yhden produktion, niin koodaa ko. produktio X:n aliohjelmaan t:n caseen. sisältää useamman kuin yhden produktion 1, koodaa produktiot X:n aliohjelmaan t:n caseen ja käytä peruutusta valinnan tekemiseen produktioiden välissä. on tyhjä, niin koodaa X:n aliohjelmaan t:n caseen kielioppivirheen diagnosointi. Jos taulukossa ei ole yhtään konfliktia, jäsennin on ennustava. 1 Tällöin taulukossa on konflikti.
LL(1) left-to-right parse, leftmost derivation, 1-token lookahead. Jos kieliopista johdettu ennustava taulukko on konfliktiton, ko. kielioppi on LL(1). Jos kielioppi ei ole LL(1), kannattaa kokeilla vasemman rekursion poistoa ja vasenta tekijöintiä (engl. left factoring). Monet hyödylliset kieliopit eivät ole LL(1). Moniselitteinen kielioppi ei ole koskaan LL(1). On mahdollista yleistää LL(n):ään, jolloin FIRST ja FOLLOW sisältävät n:n mittaisia sanasjonoja. Tämä laajentaa jäsennettävien kielten joukkoa.
Ylhäältä alaspäin -jäsennys LL(1)-jäsennys etenee ylhäältä alaspäin, sillä se aloittaa välikesymbolista, ennustaa tarvittavan produktion, ja jäsentää ko. produktion mukaan, ja näin rakentaa jäsennyspuun juuresta lehtiin.
Ennustava jäsennys: pro ja contra + Helppo koodata kieliopin perusteella käsin. + Tehokas ei peruutusta. Vaadittu LL(1)-kielioppi on usein varsin vaikeaselkoinen. Kieliopin muuttaminen voi johtaa vaikeaselkoisiin virheisiin, kun FIRST- ja FOLLOW-joukkojen muuttumista ei muisteta ottaa kaikkialla huomioon. Tämän poistaa LL-generaattorin käyttäminen (esim. ANTLR). Käsin kirjoitetun prediktiivisen jäsentimen muokkaaminen voi johtaa epäselvyyteen siitä, mitä kieltä se oikeasti jäsentää. Tämänkin poistaa LL-generaattorin käyttäminen (esim. ANTLR).
Sisällys
LR(k)-jäsennys left-to-right parse, rightmost derivation, k-token lookahead jäsentimessä pino ja syöte (jota luetaan k:n sanasen ikkunassa) syötejono sisältää koko syöteen ja pino on tyhjä. kaksi toimintoa: shift: Siirrä seuraava syötesananen pinoon. reduce: Valitse produktio X Y 1 Y n, poista pinosta symbolit Y 1,..., Y n ja lisää pinoon välikesymboli X. Jos jäsennys onnistui, lopussa syötejono on tyhjä ja pinossa on vain yksi symboli (kieliopin aloitussymboli).
LR(k)-jäsennysautomaatti Kuhunkin tilaan liittyy toimintaohje kullekin symbolille (ynnä syötteen loppumiselle): shift n tee shift-operaatio ja siirry tilaan n reduce n tee reduce-operaatio produktiolle n goto n siirry tilaan n koskematta pinoon ja syötteeseen accept jäsennys on valmis error jäsennys epäonnistui Goto-toiminto on mahdollinen vain välikesymbolin kohdalla, muut vain päätesymbolin kohdalla Pinoon pistetään aina symbolin seuraksi nykyinen tila; automaatin tila on aina pinon päällimmäinen tila.
LR-itemit LR(0)-item koostuu produktiosta ja indeksistä sen oikeaan puoleen. Merkitään usein niin, että indeksin paikka kirjoitetaan produktion sisään pisteenä: E F +. E LR(1)- ja LALR(1)-item sisältää lisäksi lookahead-päätesymbolin. LR(k)-item sisältää lookahead-päätesymbolijonon
Konfliktit Jos LR-automaatissa on samassa solussa useampi toimintaohje, on kyseessä konflikti. Tällöin kielioppi ei ole LR. Shift/reduce-konflikti on tilanne, jossa samassa tilassa on sekä shift- että reduce-toimintaohje. Tyypillinen esimerkki on aiemmin esitelty if-else-moniselitteisyys. Yleensä korjattavissa kielioppia muokkaamalla. Usein shift-toiminnon valitseminen tuottaa halutun tuloksen. Reduce/reduce-konflikti on tilanne, jossa samassa tilassa on kaksi eri reduce-toimintaohjetta. Tällöin joko kieliopissa on bugi tai jäsennettävä kieli ei sovellut LR-jäsennykseen. Shift/shift-konfliktia ei esiinny.
Alhaalta ylöspäin -jäsennys LR(k) jäsentää alhaalta ylöspäin, sillä se rakentaa reduce-toiminto kerrallaan jäsennyspuuta lehdistä juureen.
LR:n eri lajit Ilmaisuvoimajärjestyksessä: LR(0) ei lookaheadia SLR lookahead FIRST- ja FOLLOW-joukkojen avulla LALR(1) eli lookahead-lr LR(1)-jäsentimestä tilatehokkaampi ja hieman heikompi versio LR(1) yhden merkin lookahead, automaatti on yleensä iso LR(k), missä k > 1, ja LR(1) ovat yhtä ilmaisuvoimaiset.
LR: pro ja contra + LR(1) käsittää useampia kieliä kuin LL(k). + LR-kieliopit ovat yleensä varsin helppolukuisia. LR-jäsentimen käsin koodaaminen on lähes mahdotonta. LR-konfliktien debuggaus on hankalaa ja vaatii kokemusta.
LARL-jäsenningeneraattorit Yacc, Bison, CUP, Happy jne. Jokaiseen symboliin liittyy semanttinen arvo. Reduce-toiminnon yhteydessä mahdollista suorittaa ohjelmoijan määräämää koodia, joka voi laskea vasemman puolen välikesymbolille semanttisen arvon käyttäen oikean puolen symbolien semanttisia arvoja hyväkseen.
Sisällys
Seuraava deadline Vaihe B tiistai 6.10. klo 10 selaaja ja jäsentäjä toimivat