815338A Ohjelmointikielten periaatteet 2014 - 2015. Harjoitus 2 vastaukset Harjoituksen aiheena on BNF-merkinnän käyttö ja sen yhteys rekursiivisesti laskeutuvaan jäsentäjään. Tehtävä 1. Mitkä ilmaukset seuraava kielioppi määrittelee? <sana> ::= <X><Y> <X> ::= a | <X>a <Y> ::= b | b<Y> Vastaus. Tutkitaan ensin välisymboleita <X> ja <Y>. Luonnollisesti X:stä voidaan johtaa a. Kun a on johdettu, rekursiivisen määrittelyn (X:n säännön toinen mahdollisuus) perusteella voidaan johtaa aa, tämän jälkeen aaa jne. Välisymbolin <X> paikalle voidaan siis johtaa merkkijono aa…a, missä a-kirjaimia on vähintään yksi kappale. Vastaavalla tavalla huomataan, että välisymbolin <Y> paikalle voidaan johtaa merkkijono bb...b, missä b-kirjaimia on vähintään yksi kappale. Koska <sana>-välisymboliksi voidaan johtaa ilmaus, jossa <X> ja <Y> korvataan millä tahansa kyseisistä välisymboleista johdettavalla lausekkeella, niin kieliopin <sana> -välisymbolin tilalle voidaan johtaa muotoa aa…abb..b olevat merkkijonot, joissa a ja b-kirjaimia on kumpaakin ainakin yksi kappale. Tehtävä 2. Määritellään kielioppi seuraavasti <lauseke> ::= <A>a<B>b <A> ::= b | <A>b <B> ::= a | a<B> Mitkä seuraavista lausekkeista kuuluvat em. kielioppiin a) bbaabb, b) bbaba, c) bbbaaab, d) bbaaabb? Vastaus. Kuten tehtävässä 1, huomataan, että välisymbolin <A> paikalle voidaan johtaa merkkijono bb...b, missä b-kirjaimia on vähintään yksi kappale. Välisymbolin <B> paikalle voidaan johtaa merkkijono aa…a, missä a-kirjaimia on vähintään yksi kappale. Koska lausekkeessa välisymbolien <A> ja <B> välissä on kirjain a ja lausekkeen lopussa on kirjain b, niin voidaan todeta että lauseke on muotoa bb...ba..ab, missä alussa bkirjaimia on vähintään yksi ja a-kirjaimia vähintään kaksi. Lauseke loppuu siis aina kirjaimiin ab. Näin ollen kohtien a), b) ja d) merkkijonoja ei voi johtaa kielioppisäännöillä. Sen sijaan kohdan c) merkkijono bbbaaab voidaan johtaa, joten se sisältyy määriteltyyn kieleen. Tehtävä 3. Määrittele BNF (tai EBNF) -notaatiolla seuraavat kielen vakioita ilmaisevat välisymbolit (nonterminaalit), joiden määrittely on annettu sanallisesti. <binääriluku> on yhdestä tai useammasta peräkkäisestä numerosta koostuva merkkijono, missä kukin numero voi olla 0 tai 1. <kokonaisluku> on joko etumerkitön tai etumerkillä + tai - varustettu yhdestä tai useammasta peräkkäisestä numerosta koostuva merkkijono, missä kukin numero voi olla 0, ..., 9. <desimaaliluku> on merkkijono, joka koostuu joko 1. kokonaisluvusta, 2. desimaaliosasta tai 3. kokonaisluvusta ja sitä seuraavasta desimaaliosasta. Desimaaliosa alkaa pisteellä (.), jota seuraa etumerkitön kokonaisluku. Ohje: Käytä tarvittaessa muita välisymboleja, joille määritellään erikseen sääntö. Vastaus. Tyypit voidaan määritellä seuraavasti (muutkin tavat mahdollisia): Binääriluvussa on siis ainakin yksi numero, joka voi olla 0 tai 1. Määritellään binääriselle numerolle välisymboli, jonka nimeksi valitaan bitti. Jos luvussa on useampia bittejä, niin loppuosa ensimmäisen bitin jälkeen on aina binääriluku. Näin saadaan rekursiivinen määritelmä: <binääriluku> ::= <bitti> | <bitti><binääriluku> <bitti> ::= 0 | 1 Kokonaisluku voidaan rakentaa alhaaltapäin: ensin numerot, sitten etumerkittömät luvut ja lopuksi luvut joissa voi olla etumerkki. Esitetään välisymbolien määritelmät ylhäältä lähtien: <kokonaisluku> ::= [+|-] <etumerkitön> <etumerkitön> ::= <numero> | <numero><etumerkitön> <numero> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Desimaaliluku voidaan esittää ylläolevia määritelmiä käyttämällä ja määrittelemällä lisäksi desimaaliosa: <desimaaliluku> ::= <kokonaisluku> | <desimaaliosa> | <kokonaisluku><desimaaliosa> <desimaaliosa> ::= .<etumerkitön> Tehtävä 4. C -kielen if-lauseen syntaksi on seuraava: <if_lause> ::= if(<lauseke>) <lause> [else <lause>] Näytä, että näin tulee määriteltyä monikäsitteinen kielioppi, kun otetaan huomioon, että if-lause on lauseen erikoistapaus, joten if-lause voi esiintyä säännön oikealla puolella. Ohje: Näytä, että if-lauseella if(ehto1) if(ehto2) lause2 else lause3 on kaksi erilaista johtopuuta, ts. syntaksista ei voi päätellä kumpaan if -lauseeseen else kuuluu. Tätä sanotaan roikkuvan elsen ongelmaksi (dangling else problem). Miten C kielessä tulkitaan ylläoleva koodi? Vastaus. Lauseelle if(ehto1) if(ehto2) lause2 else lause3 saadaan kaksi erilaista johtoa, kun otetaan huomioon, että välisymboli <lause> voidaan korvata välisymbolilla <if-lause>: <if_lause> -> if(<lauseke>) <lause> -> if(<lauseke>) <if_lause> -> if(ehto1) <if_lause> -> if(ehto1) if(<lauseke>) <lause> else <lause> -> if(ehto1) if(ehto2) <lause> else <lause> -> if(ehto1) if(ehto2) lause2 else <lause> -> if(ehto1) if(ehto2) lause2 else lause3 ja <if_lause> -> if(<lauseke>) <lause> else <lause> -> if(ehto1) <lause> else <lause> -> if(ehto1) <if_lause> else <lause> -> if(ehto1) if(<lauseke>) <lause> else <lause> -> if(ehto1) if(ehto2) <lause> else <lause> -> if(ehto1) if(ehto2) lause2 else <lause> -> if(ehto1) if(ehto2) lause2 else lause3 Näillä johdoilla on erilaiset johtopuut (piirrä ne!). Javassa lause tulkitaan niin, että else viittaa lähimpään if-lauseeseen, ts. esimerkiksi int x = 0; if(x == 0) if( x != 0) x = 1; else x = 2; sijoittaa muuttujaan x arvon 2 (jäisi nollaksi jos else viittaisi ylempään lauseeseen). Tehtävä 5. Seuraava kielioppi määrittelee joukon symboleja a, b ja c käyttäviä yksinkertaisia yhteen- vähennys- ja kertolaskua sisältäviä aritmeettisia lausekkeita. <lauseke> ::= <termi> | <termi> + <termi>| <termi> - <termi> <termi> ::= <tekijä> | <tekijä> * <termi> <tekijä> ::= a | b | c Kirjoita C-kielellä (tai jollain muulla valitsemallasi kielellä) kieliopille rekursiivisesti laskeutuva jäsentäjä, jolle jäsennettävä lauseke syötetään komentoriviltä ohjelman alussa ennen jäsennystä. Ohjelma tulostaa virheilmoituksen, jos lauseke ei ollut syntaksiltaan kieliopin hyväksymää muotoa ja ilmoituksen "Lauseke oikeaa muotoa", mikäli kielioppi hyväksyy sen. Virheestä ilmoittaminen on helpointa tehdä kutsumalla virhetilanteen sattuessa tätä varten kirjoitettua funktiota error(), jolle voi antaa virheilmoituksen parametrina. Ohjelman voi myös lopettaa funktiossa. Huomaa, että nyt kaikki lekseemit ovat yhden merkin mittaisia, joten funktio getToken() voidaan kirjoittaa niin, että se hakee syötteenä saadusta merkkijonosta aina seuraavan merkin. Vastaus. Luennoilla esitettyyn tapaan ohjelma voidaan kirjoittaa niin, että jokaista välisymbolia kohti kirjoitetaan oma (rekursiivinen) metodi, joka jäsentää kyseisen välisymbolin. Lekseemi haetaan globaaliin muuttujaan sym metodissa getToken(). Tässä tapauksessa lekseemi on aina yhden merkin mittainen, joten funktiosta tulee yksinkertainen. Kun havaitaan virhe, kutsutaan virhefunktiota error, joka päättää ohjelman. Tällöin ohjelman runko olisi seuraava: // Globaali muuttuja lekseemille char sym; // Globaali muuttuja: missä kohdassa jäsennettävää merkkijonoa ollaan int curPos = 0; // Globaali muuttuja jossa on jäsennettävä merkkijono char orig_expr[MAX_LEN_EXPR]; // Lekseemin tyyppi eli sananen. Voi olla aluksi 0/1 = loppu/kesken int token; // Hakee seuraavan lekseemin int getToken() { // Lue seuraava merkki jonosta orig_expr muuttujaan sym // ja kasvata osoitinta curPos // Palauta 0 jos jonon lopussa } // Virhefunktio tulostaa ilmoituksen ja lopettaa ohjelman. void error(char *msg) { printf("Virhe: %s",msg); exit(1); } // Jäsentää tekijän void tekija(){ token = getToken(); // Jos jono loppu niin virhe switch(sym) { // a,b tai c: OK break; // Muu -> virhe } } // Jäsentää termin void termi() { // Termissä aina tekijä ensimmäisenä tekija(); // Tekijä OK, jatketaan token = GetToken(); // Jos jono loppu, OK switch(sym) { // * : termi jatkuu termi(); // + - OK break; // Muu -> virhe } } // Jäsentää lausekkeen void lauseke() { // Lausekkeessa aina termi ensimmäisenä. termi(); // Jos ei loppu niin termi hakenut lekseemin switch(sym) { // + tai – tulee uusi termi termi(); // Muu virhe } // Tarkistetaan että lauseke loppu, muuten virhe } int main(int args, char** argv) { // Luetaan jäsennettävä lauseke merkkijonoon orig_expr printf("Anna jasennettava lauseke > "); scanf("%s",orig_expr); // Jäsennetään lauseke lauseke(); // Jos menee läpi tulostetaan että lauseke oikea printf("Lauseke OK."); return 0; } Harjoituksen yhteyteen on linkitetty kaksi versiota ohjelmasta, joille kummallekin syötetään jäsennettävä lauseke komentoriviltä. Lausekkeessa ei voi olla välilyöntejä ja sen maksimipituus on 99 merkkiä. Ensimmäinen versio ei luokittele lekseemejä, vaan getToken palauttaa arvon 1 jos lauseketta ei ole luettu loppuun ja arvon 0, jos ollaan lopussa. Jäsentäminen käyttää lekseemin sisältävää muuttujaa sym. Toisessa versiossa lekseemien tyypeille on määritelty lueteltu tyyppi. Tällöin jäsentämisessä voidaan käyttää haetun lekseemin tyyppiä, jolloin virheen raportointi saadaan tarkemmaksi.
© Copyright 2024