Esimerkki 47. Kieli {a i b j c k : i = j tai j = k} on luonnostaan moniselitteinen.



Samankaltaiset tiedostot
Vasen johto S AB ab ab esittää jäsennyspuun kasvattamista vasemmalta alkaen:

follow(a) first(α j ) x

Yhteydettömän kieliopin jäsennysongelma

Laskennan mallit (syksy 2010) Harjoitus 8, ratkaisuja

S BAB ABA A aas bba B bbs c

T Syksy 2002 Tietojenkäsittelyteorian perusteet Harjoitus 8 Demonstraatiotehtävien ratkaisut

2. Yhteydettömät kielet

uv n, v 1, ja uv i w A kaikilla

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 10. kesäkuuta 2013

Rekursiivinen Derives on periaatteessa aivan toimiva algoritmi, mutta erittäin tehoton. Jos tarkastellaan esim. kieliopinpätkää

Rajoittamattomat kieliopit

T Syksy 2006 Tietojenkäsittelyteorian perusteet T Harjoitus 7 Demonstraatiotehtävien ratkaisut

Yhteydettömät kieliopit [Sipser luku 2.1]

Ei-yhteydettömät kielet [Sipser luku 2.3]

Olkoon G = (V,Σ,P,S) yhteydetön kielioppi. Välike A V Σ on tyhjentyvä, jos A. NULL := {A V Σ A ε on G:n produktio};

Pinoautomaatit. Pois kontekstittomuudesta

jäsentäminen TIEA241 Automaatit ja kieliopit, syksy 2015 Antti-Juhani Kaijanaho 26. marraskuuta 2015 TIETOTEKNIIKAN LAITOS

Pinoautomaatit. TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 6. kesäkuuta 2013 TIETOTEKNIIKAN LAITOS. Pinoautomaatit.

Testaa: Vertaa pinon merkkijono syötteeseen merkki kerrallaan. Jos löytyy ero, hylkää. Jos pino tyhjenee samaan aikaan, kun syöte loppuu, niin

jäsennyksestä TIEA241 Automaatit ja kieliopit, syksy 2016 Antti-Juhani Kaijanaho 29. syyskuuta 2016 TIETOTEKNIIKAN LAITOS Kontekstittomien kielioppien

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 29. huhtikuuta 2011

Chomskyn hierarkia ja yhteysherkät kieliopit

Täydentäviä muistiinpanoja Turingin koneiden vaihtoehdoista

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 12. kesäkuuta 2013

Esimerkki 2.28: Tarkastellaan edellisen sivun ehdot (1) (3) toteuttavaa pinoautomaattia, jossa päätemerkit ovat a, b ja c ja pinoaakkoset d, e ja $:

Rajoittamattomat kieliopit (Unrestricted Grammars)

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 30. marraskuuta 2015

4. Tehtävässä halutaan todistaa seuraava ongelma ratkeamattomaksi:

Jäsennysalgoritmeja. TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 29. syyskuuta 2009 TIETOTEKNIIKAN LAITOS. Jäsennysalgoritmeja

11.4. Context-free kielet 1 / 17

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 29. toukokuuta 2013

jäsentämisestä TIEA241 Automaatit ja kieliopit, syksy 2015 Antti-Juhani Kaijanaho 27. marraskuuta 2015 TIETOTEKNIIKAN LAITOS

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 2. helmikuuta 2012

on rekursiivisesti numeroituva, mutta ei rekursiivinen.

5.3 Ratkeavia ongelmia

M =(K, Σ, Γ,, s, F ) Σ ={a, b} Γ ={c, d} = {( (s, a, e), (s, cd) ), ( (s, e, e), (f, e) ), (f, e, d), (f, e)

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 3. lokakuuta 2016

Lisää pysähtymisaiheisia ongelmia

Säännöllisten kielten sulkeumaominaisuudet

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 31. maaliskuuta 2011

Jäsennys. TIEA341 Funktio ohjelmointi 1 Syksy 2005

ICS-C2000 Tietojenkäsittelyteoria Kevät 2016

Turingin koneen laajennuksia

Attribuuttikieliopit

ICS-C2000 Tietojenkäsittelyteoria Kevät 2016

ICS-C2000 Tietojenkäsittelyteoria Kevät 2016

Hahmon etsiminen syotteesta (johdatteleva esimerkki)

Rekursiiviset palautukset [HMU 9.3.1]

ICS-C2000 Tietojenkäsittelyteoria. Tähän mennessä: säännölliset kielet. Säännöllisten kielten pumppauslemma M :=

Todistus: Aiemmin esitetyn mukaan jos A ja A ovat rekursiivisesti lueteltavia, niin A on rekursiivinen.

ICS-C2000 Tietojenkäsittelyteoria

(0 1) 010(0 1) Koska kieli on yksinkertainen, muodostetaan sen tunnistava epädeterministinen q 0 q 1 q 2 q3

Ongelma(t): Miten jollakin korkeamman tason ohjelmointikielellä esitetty algoritmi saadaan suoritettua mikro-ohjelmoitavalla tietokoneella ja siinä

Esimerkkejä polynomisista ja ei-polynomisista ongelmista

Osoitamme, että jotkut kielet eivät ole säännöllisiä eli niitä ei voi tunnistaa äärellisellä automaatilla.

TKT20005 Laskennan mallit (syksy 2018) Kurssikoe, malliratkaisut

Tarkastelemme ensin konkreettista esimerkkiä ja johdamme sitten yleisen säännön, joilla voidaan tietyissä tapauksissa todeta kielen ei-säännöllisyys.

Automaatit. Muodolliset kielet

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

vaihtoehtoja TIEA241 Automaatit ja kieliopit, syksy 2016 Antti-Juhani Kaijanaho 13. lokakuuta 2016 TIETOTEKNIIKAN LAITOS

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 19. tammikuuta 2012

A ja B pelaavat sarjan pelejä. Sarjan voittaja on se, joka ensin voittaa n peliä.

Muita vaativuusluokkia

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 16. helmikuuta 2012

6.5 Turingin koneiden pysähtymisongelma Lause 6.9 Kieli. H = {c M w M pysähtyy syötteellä w}

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 16. marraskuuta 2015

Chomskyn hierarkia. tyyppi 0 on juuri esitelty (ja esitellään kohta lisää) tyypit 2 ja 3 kurssilla Ohjelmoinnin ja laskennan perusmallit

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 31. maaliskuuta 2011

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 12. lokakuuta 2016

Laskennan mallit (syksy 2008) 2. kurssikoe , ratkaisuja

Kertausta 1. kurssikokeeseen

Laskennan rajoja. TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 10. joulukuuta 2015 TIETOTEKNIIKAN LAITOS.

Täydentäviä muistiinpanoja laskennan rajoista

811120P Diskreetit rakenteet

8. Kieliopit ja kielet

811120P Diskreetit rakenteet

Säännöllisen kielen tunnistavat Turingin koneet

ICS-C2000 Tietojenkäsittelyteoria

Yhtälönratkaisusta. Johanna Rämö, Helsingin yliopisto. 22. syyskuuta 2014

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 3. joulukuuta 2015

Pinoautomaatit. TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 6. lokakuuta 2016 TIETOTEKNIIKAN LAITOS

TIEA241 Automaatit ja kieliopit, kevät 2011 (IV) Antti-Juhani Kaijanaho. 16. toukokuuta 2011

Täydentäviä muistiinpanoja kontekstittomien kielioppien jäsentämisestä

δ : (Q {q acc, q rej }) (Γ k {, }) Q (Γ k {, }) {L, R}.

Epädeterministisen Turingin koneen N laskentaa syötteellä x on usein hyödyllistä ajatella laskentapuuna

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 9. lokakuuta 2016

Kaikki mitä olet aina halunnut tietää pumppauslemmoista, mutta mitä et ole kehdannut kysyä

Algoritmin määritelmä [Sipser luku 3.3]

Approbatur 3, demo 1, ratkaisut A sanoo: Vähintään yksi meistä on retku. Tehtävänä on päätellä, mitä tyyppiä A ja B ovat.

Pysähtymisongelman ratkeavuus [Sipser luku 4.2]

Tietorakenteet ja algoritmit - syksy

M = (Q, Σ, Γ, δ, q 0, q acc, q rej )

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 20. lokakuuta 2016

Kontekstittomien kielten jäsentäminen Täydentäviä muistiinpanoja TIEA241 Automaatit ja kieliopit, syksy 2016

TIEA241 Automaatit ja kieliopit, kesä Antti-Juhani Kaijanaho. 22. toukokuuta 2013

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Diskreetin matematiikan perusteet Laskuharjoitus 1 / vko 8

Johdatus lukuteoriaan Harjoitus 2 syksy 2008 Eemeli Blåsten. Ratkaisuehdotelma

Algoritmit 2. Luento 13 Ti Timo Männikkö

Transkriptio:

Aritmeettisen lausekkeen jäsennyspuun avulla voidaan helposti laskea lausekkeen arvo, kun muuttujien arvot tunnetaan. Yleisemmin, kääntäjä voi jäsennyspuun avulla generoida koodia lausekkeen evaluoimiseksi. Tätä sovellusta silmällä pitäen edellisen esimerkin yksiselitteinen kielioppi G expr noudattaa koulusta tuttua presedenssisääntöä, jonka mukaan kertolaskut lasketaan ennen yhteenlaskuja. Kieliopilla G expr ei ole tätä ominaisuutta, vaan sille kelpaisi kumpi tahansa laskujärjestys. Kärjistäen: jos kielioppi tai kieli on moniselitteinen, niin on myös sen merkityskin. Siksi yksiselitteisyys on hyve! Jäsennyspuun hyödyntämiseksi pitää tietysti ensinnä osata muodostaa annetulle merkkijonolle jäsennyspuu (eli yhtäpitävästi johto) annetussa kieliopissa, tai todeta, että merkkijono ei kuulu kieleen. Palaamme tähän pian... Esimerkki 47. Kieli {a i b j c k : i = j tai j = k} on luonnostaan moniselitteinen. Kieliopin moniselitteisyys on laskennallisesti ratkeamaton ongelma. Eli sen näyttäminen edellyttää matemaattista todistusta. Kieliopin moniselitteisyyden osoittaminen on helppoa: Riittää keksiä yksikin merkkijono, jolla on useita erilaisia jäsennyspuita. Kielen osoittaminen luonnostaan moniselitteiseksi taas on hankalaa: Onhan osoitettava, ettei kielellä voi olla yhtään yksiselitteistä kielioppia. Huomaa: Luonnostaan moniselitteiset kielet voidaan tunnistaa vain epädeterministisillä pinoautomaateilla. Huomaa: Lisäksi on olemassa myös yksiselitteisiä kieliä, jotka vaativat epädeterministisen pinoautomaatin. Esimerkki 48. Kielelle L = {ww R : w {a, b} } voidaan antaa yksiselitteinen kielioppi S asa bsb ε mutta sitä ei voida tunnistaa deterministisellä automaatilla. (Ongelma: automaatin täytyy arvata, milloin on tultu merkkijonon keskikohtaan.) Esimerkki 49. Kieli L = {a n b m : 0 m n 2m} on yksiselitteinen, mutta epädeterministinen. Yksinkertaisin kielen tuottava kielioppi on S asb aasb ε joka on kuitenkin moniselitteinen. Saman kielen voi kuitenkin kuvata yksiselitteisellä kieliopilla: S asb A ε A aaab aab Kielelle voidaan laatia kuvan 22Esimerkin49 epädeterministinen pinoautomaatti epädeterministinen pinoautomaatti, mutta determinististä automaattia ei pystytä laatimaan. 112

a, ε / A 1 a, ε / A a, ε / A a, ε / ε 2 a, ε / A a, ε / ε b,a/ ε 4 3 5 b,a/ ε b,a/ ε b,a/ ε 6 Kuva 22: Esimerkin49 epädeterministinen pinoautomaatti. 113

Esimerkki 50. Tarkastellaan seuraavaa sääntöä if else-lauseiden jäsentämiseksi: S if B then S else S if B then S i = N Tässä B voi olla mikä tahansa ehtolauseke, i mikä tahansa muuttujannimi ja N mikä tahansa kokonaislukuvakio. Kun sillä jäsennetään merkkijono if x == 1 then if y == 1 then z = 0 else z = 1 niin vastauksena saadaan kaksi erilaista jäsennyspuuta Miten käy, jos muuttujat vastaavat esimerkiksi seuraavia signaaleja: x = palohälytin soi, y = koehälytys, z = hälytä palokunta? S if B then S x==1 if B then S else S y==1 z=0 z=1 S if B then S else S x==1 if B then S z=1 y==1 z=0 Tätä esimerkkiä 50 kutsutaan roikkuvan else-haaran ongelmaksi (englanniksi the dangling else problem : Kumpaan if-ehdoista tämä yksinäinen else-haara pitäisi liittää? Ohjelmointikielen syntaksimäärittelyt ratkaisevat tämän lausumalla else-haara liittyy aina lähimpään sellaiseen edeltävään if-ehtoon jolla ei vielä ole omaa else-haaraa. Ohjelmointikielen kieliopin (eli syntaksin) tulee olla yksiselitteinen, jotta sillä kirjoitettu ohjelma voidaan kääntää yksiselitteisesti toimivaksi ohjelmaksi. Siksi niissä käytetään formaaleja esitystapoja ja tällaisia lausumia. 114

Sama olisi toivottavaa myös ohjelmointikielen merkitysopille (eli semantiikalle), eli siinäkään ei saisi olla asioita joiden käyttäytymistä ei ole määritelty. Myös merkitysopissa voidaan käyttää formaaleja esitystapoja, mutta valitettavasti läheskään kaikille ohjelmointikielille niin ei tehdä. Kärjistäen: Ohjelmointikielen määrittelyn pitäisi poistaa eikä lisätä ohjelmoijan epätietoisuutta siitä, mitä hänen kirjoittamansa ohjelma tarkoittaa! Ohjelmointikielen kääntämisestä Ohjelmointikielen kääntäjän vaiheet periaatteellisella tasolla (kuva 23Ohjelmointikielen kääntäjän vaihejako): 1. Selaaminen (scanning, lexical analysis): jakaa syötteen tekstialkioiksi (token) kuten muuttujanimiin, liukulukuvakioihin, varattuihin sanoihin,... jotta jäsennyksen ei enää tarvitse edetä yksi ASCII-merkki kerrallaan Tekniikka pohjautuu tekstialkioden tunnistamiseen äärellisillä automaateilla. UNIX-työkalu lex jonka GNU-versio on flex. 2. Jäsentäminen (parsing): Muodostaa selauksen tuottamalle tekstialkiojonolle jäsennyspuun...... joka perustuu ohjelmointikielen määrittelyn osana suunniteltuun kontekstittomaan kielioppiin. Tässä on ohjelmointikielen kieliopin yksikäsitteisyysvaatimus: jäsennyspuita tehdään vain yksi! UNIX-työkalu yacc jonka GNU-versio on bison on ns. kääntäjäkääntäjä (englanniksi compiler compiler ): Se lukee sisäänsä kieliopin kuvauksen ja generoi siitä jäsennystä tekeven aliohjelman, joka ohjelmoija voi liittää osaksi sitä kääntäjää, jota hän on nyt toteuttanassa. Selaus ja jäsennys ovat ne vaiheet, joista kääntäjää käyttävä sovellusohjelmoija saa ne virheilmoitukset, jotka alkavat "Syntax error...". 3. Semanttinen analyysi: tyypitys jne. Esimerkiksi kielioppi ilmaisee vain, että se osa ohjelmakoodia, jossa muuttujat esitellään, edeltää sitä osaa, jossa niitä käytetään. Esittelyosaa jäsentäessään kääntäjä kerää esitellyt muuttujat ja niiden tyypit symbolitauluun. Kun se myöhemmin kohtaa jonkin muuttujan kuten i käytön, niin se kysyy tästä taulusta mikä sen tyypiksi on määritelty. Jos muuttujaa ei ole taulussa, niin tulee virheilmoitus"error: undefined variable...". Tyyppi on yksi muuttujan attribuuteista eli ominaisuuksista. Semanttisen analyysin voi ajatella koristelevan jäsennyksen tuottaman jäsennyspuun tällaisilla attribuuteilla ja niiden arvoilla. Esimerkiksi while-silmukan testin tyyppiattribuutiksi pitää tulla totuusarvo, muuten tulee semanttinen virhe kuten "Error: type mismatch...". Kielioppia, johon on yhdistetty attribuuttien määrittelyt ja niiden laskusäännöt, kutsutaan attribuuttikieliopiksi (englanniksi attribute grammar ). 115

Syntax error... Aluksi lähdekoodi ASCII merkkijonona while(i>0)... Lopuksi konekoodina 00 FF 1A 8C 17 1F 05 5E AA C0 7F 99... selaaja (scanner) koodin generointi tyyppi: kokonaisluku jonona pidempiä yksiköitä while i varattu sana: muuttujan nimi: operaattori: kokonaislukuvakio:... komento while komento while lauseke > i 0 > 0 täydennettynä jäsennyspuuna komento tyyppi: totuusarvo jäsentäjä (parser) attribuuttien laskenta jäsennyspuuna komento while komento while lauseke > i 0 komento Error: undefined variable... Error: type mismatch...... Kuva 23: Ohjelmointikielen kääntäjän vaihejako. Itse asiassa yacc-työkalu lukeekin sisään tällaisen attribuuttikieliopin ja tuottaa siitä sellaisen jäsennysaliohjelman, joka samalla laskee nämä attribuuttiarvot. 4. Koodin tuottaminen ja optimointi. Suoritetaan käymällä läpi tätä jäsennyspuuta, joka on koristeltu kaikilla eri koodin tuottamiseen tarvittavilla attribuuteilla. Tässä vaiheessa ei enää tule virheilmoituksia. Käytännössä jäsennys ja muut vaiheet limittyvät ajallisesti: Esimerkiksi jäsentäjä pyytää selaajalta anna minulle seuraava tekstialkio. Vastaavasti jäsentäjäkään ei yleensä tuota koko jäsennyspuuta kerralla muistiin, vaan ainoastaan sen osan, jota semanttinen analyysi tällä hetkellä tarvitsee. Ohjelmointikielen jäsentämiseen on useita tekniikoita. Lähtökohtana on tyypillisesti ohjelmointikielen määrittelyssä eli spesifikaatiossa annettu kontekstiton kielioppi. Tehokkaat jäsennysmenetelmät edellyttävät, että kielioppi on jossain rajoitetussa muodossa. Näistä tärkeimmät ovat LL(k) ja LR(k). Ohjelmointikelten syntaktinen käsittely (eli vaiheet 1 ja 2) on hallittu jo pitkään, ja siihen on laajalti levinneitä apuvälineitä kuten lex ja yacc. Ohjelmointikielten kehitys ja tutkimus painottuukin nykyään erityisesti niiden tyyppijärjestelmiin (eli vaiheeseen 3). Esimerkiksi funktionaalisten ja olio-ohjelmointikielten tyyppijärjestelmät voivat olla hyvinkin monipuolisia ja -mutkaisia... 116

Tuotetun konekoodin optimointi (vaiheessa 4) taas on kiinnostavaa erityisesti mikroprosessoreiden ja tietokonelaitteistojen valmistajille. Esimerkiksi nykyaikainen mikroprosessori sisältää useita ytimiä jotka voivat laskea rinnakkain jos vain käännetty ohjelmakoodi osaa hyödyntää tätä mahdollisuutta... 5.5 CYK-algoritmi Mille tahansa kontekstittomalla kieliopilla G kysymys päteekö w L(G)? voidaan ratkaista ajassa O( w 3 ) CYK-algoritmilla (Cocke, Younger, Kasami). CYK-algoritmi olettaa, että kielioppi G on Chomskyn normaalimuodossa. Mikä tahansa kontekstiton kielioppi voidaan muuttaa tähän muotoon... Edellä ei siis ole laskettu mukaan tähän muunnokseen kuluvaa aikaa sehän tehdään vain kerran kieliopille G, ja sen jälkeen CYK-algoritmia voidaan käyttää monille eri w Σ. CYK-algoritmia ei kuitenkaan käytetä ohjelmointikielille, koska niiden kieliopit G voidaan suunnitella kieltä määriteltäessä siten, että on nopeampiakin, jopa lineaarisia eli O( w ) menetelmiä. Chomskyn normaalimuoto Kielioppeja algoritmisesti käsiteltäessä on hyvä, jos ne ovat rakenteeltaan siistejä. Tarkastellaan kielioppia S A B C D E F AB a CC DD EE FF ε Kieliopin tuottama kieli on yksinkertaisesti {a}. Johdot ovat kuitenkin hyvin pitkiä, mikä on erilaisten algoritmien kannalta ongelmallista. Esitämme seuraavaksi, miten yhteydettämälle kieliopille voidaan löytää Chomskyn normaalimuoto (englanniksi Chomsky Normal Form eli CNF). (Huomaa: Logiikassa lyhenne CNF tarkoittaakin puolestaan loogisen kaavan konjunktiivista (englanniksi Conjunctive) normaalimuotoa.) Chomskyn normaalimuoto tarjoaa hyviä esimerkkejä yhteydettömien kielioppien yksinkertaistamisessa käytettävistä päättelyistä ja algoritmeista. Määritelmä 14. Kontekstiton kielioppi G = (V, Σ, P, S) on Chomskyn normaalimuodossa, jos: Välikkeistä enintään lähtösymboli S on tyhjentyvä (nullable), eli sellainen joka voi tuottaa tyhjän merkkijonon, eli S ε. G 117

Muut produktiot ovat muotoa A BC tai A a, joissa A, B ja C ovat välikkeitä ja a on päätemerkki. Lisäksi vaaditaan yksinkertaisuuden vuoksi, että lähtösymboli S ei esiinny minkään produktion oikealla puolella. Esimerkki 51. Kielioppi S A B AB ε BA a b on Chomskyn normaalimuodossa. Normaalimuodosta seuraa erityisesti, että jos S w ja w ε, niin johdon pituus on tasan 2 w 1 askelta. Tyhjän merkkijonon ainoa johto taas on S ε ja sen pituus on 1 askel. Muunnos Chomskyn normaalimuotoon Mikä tahansa kontekstiton kielioppi voidaan muuntaa Chomskyn normaalimuotoon seuraavalla menetelmällä: 1. Poistetaan lähtösymboli S produktioiden oikealta puolelta. 2. Poistetaan muut ε-produktiot, eli ne säännöt muotoa A ε joissa A S. 3. Poistetaan yksikköproduktiot, eli säännöt muotoa A B. 4. Pilkotaan lyhyemmiksi liian pitkät produktiot, eli ne säännöt muotoa A X 1 X 2 X 3...X k joissa on k > 2 symbolia oikealla puolella. Produktioiden oikealla puolella olevien lähtösymbolien poistaminen Lause 13. Mikä tahansa kontekstiton kieli voidaan tuottaa kontekstittomalla kieliopilla jossa lähtösymboli ei esiinny minkään säännön oikealla puolella. Todistus: Olkoon annettu kontekstiton kielioppi G = (V, Σ, P, S). Muodostetaan siitä uusi kielioppi G = (V, Σ, P, S ) seuraavasti: Olkoon nyt V = V {S } jossa S ole vielä käytetty kieliopissa G. / V, eli valitaan jokin uusi symboli S jota ei Sääntöjoukko P saadaan lisäämällä sääntöjoukkoon P uusi sääntö S S tälle uudelle symbolille S. Jos ε L(G), niin lisätään myös sääntö S ε. Tälle uudelle lähtösymbolille S ei tule muita sääntöjä, joten se ei esiinny minkään säännön oikealla puolella. 118

Tyhjentyvien välikkeiden käsittely Kieliopin G välike A on tyhjentyvä (tai nollautuva ), jos siitä voi tuottaa tyhjän merkkijonon, eli jos A ε. G Kaksi kielioppia G ja G ovat ekvivalentit jos ne tuottavat saman kielen, eli jos L(G) = L(G ). Lause 14. Mistä tahansa kontekstittomasta kieliopista G voidaan muodostaa ekvivalentti kielioppi G, jossa enintään lähtösymboli on tyhjentyvä. Todistus: Kielioppi G rakennetaan 3 vaiheessa. 1. Ensiksi lasketaan kaikkien tyhjentyvien välikkeiden joukko. Tämä tehdään lukemalla jokainen kielioppisääntö A X 1 X 2 X 3...X k päättelysääntönä jos kaikki sen oikean puolen symbolit X 1, X 2, X 3,...,X k nollautuvat niin myös sen vasemman puolen välike A on nollautuva ja laskemalla mitkä kaikki välikkeet voidaan näin päätellä nollautuviksi. Erityisesti sääntö A ε tekee vasemmasta puolestaan A heti nollautuvan, koska nythän k = 0. Jos taas yksikin oikean puolen symboli X i on päätemerkki, niin tämä sääntö ei mitenkään tehdä vasemmasta puolestaan nollautuvaa. Oikealla tietorakennevalinnalla tämä vaihe vie oleellisesti vain lineaarisen ajan kieliopin G kokoon nähden. 1 nollautuvat 2 while kieliopissa G on sääntö A X 1 X 2 X 3...X k jonka vasen puoli A nollautuvat vaikka jokainen sen oikean puolen symboli X 1, X 2, X 3,...,X k nollautuvat 3 do nollautuvat nollautuvat {A} 2. Nyt nollautuvat välikkeet tiedetään, joten niiden esiintymät voidaan poistaa. Kun sääntö on muotoa A αbβ jossa B nollautuvat, niin lisätään myös sääntö A αβ jossa tämä B on nollattu. Toistetaan tätä sekä kieliopin G alkuperäisiin että näin syntyviin uusiin sääntöihin, kunnes uusia sääntöjä ei enää synny. Lopputuloksena säännöstä A X 1 X 2 X 3...X k syntyy kaikki säännöt A α jossa α on mikä tahansa seuraavan äärellisen kielen merkkijono: (X 1 1 )(X 2 2 )(X 3 3 )...(X k k ) jossa jokainen i = { {ε} jos X i nollautuvat jos X i nollautuvat = onko X i nollautuva vaiko ei? 119

Valitettavasti kieliopin koko räjähtää eksponentiaalisesti koska yhdestä tällaisesta säännöstä syntyy yhteensä uutta sääntöä. 2 ( 1 + 2 + 3 + + k ) 1 3. Lopuksi voidaan poistaa kaikki säännöt muotoa A ε koska niiden vaikutukset on lisätty uusina sääntöinä edellisessä vaiheessa 2. Jos poistettavana on myös sääntö S ε nykyiselle lähtösymbolille S, niin silloin lisätäänkin uusi lähtösymboli S ja sille säännöt S ε S. Esimerkki 52. Poistetaan ε-produktiot kieliopista: Nyt nollautuvat = {B}. Siis S A BSB A aa aa B bb ε. S BSB tuottaa säännöt S S SB BS BSB B bb tuottaa säännöt B b bb. Lisätään vielä uusi alkusymboli S, ja otetaan mukaan kaikki vanhat säännöt lukuunottamatta ε-sääntöjä. Kieliopiksi tulee S S A aa aa S S SB BS BSB A B b bb. Esimerkki 53. Poistetaan ε-produktiot seuraavasta kieliopista: S A B A aba ε (nollautuvat = {A, B, S}) B bab ε S A B ε A aba aa ε B bab bb ε S S ε S A B A aba aa B bab bb 120

Yksikköproduktioiden poistaminen Produktio muotoa A B, jossa A ja B ovat välikkeitä, on yksikköproduktio (unit production). Lause 15. Mistä tahansa kontekstittomasta kieliopista G voidaan muodostaa ekvivalentti kielioppi G, jossa ei ole yksikköproduktioita. Todistus: Olkoon kielioppi taas G = (V, Σ, P, S) ja sen välikkeet siis N = V \Σ. Tehdään siitä kielioppi G 2 vaiheessa. 1. Ensiksi selvitetään jokaiselle välikkeelle A N ne välikkeet jotka voidaan tuottaa välikkeestä A pelkillä yksikköproduktioilla. Ajatellaan sellaista suunnattua verkkoa H, jonka solmuina ovat kaikki välikkeet A N ja kaarina kaikki yksikköproduktiot A B. TRA II -kurssilla käsitellään miten lasketaan tämän verkon H transitiivinen sulkeuma H jossa on kaari A 0 A n täsmälleen silloin kun verkossa H on polku A 0 A 1 A 2 A n (8) jollakin n 0. Erityisesti verkossa H on jokaisesta solmusta A kaari A A takaisin itseensä polku, jonka pituus on n = 0 kaarta. Tämä H voidaan laskea ajassa O((sen solmujen lukumäärä) 3 ). 2. Sitten oikaistaan yksikköproduktiopolut kuten (8): Silloin kun verkossa H on kaari A i A j ja kieliopissa G on produktio A j α joka ei ole yksikköproduktio, niin lisää sääntö A i α oikaisemaan polku A i A i+1 A i+2 A j α. Näiden oikaisujen jälkeen yksikköproduktiot eivät enää ole tarpeen, joten ne voidaan jättää pois. Esimerkki 54. Jatketaan aiemman esimerkin 52 lopputuloksesta. S S A aa aa S S SB BS BSB A B b bb. Nyt verkko H koostuu kaarista S S, S A ja S A (itseensä palaavien kaarten X X lisäksi). Siis muuttujalle S tulee omien alkuperäisten lisäksi kaikki muuttujien S ja A muut kuin yksikköproduktiot, ja myös S samoin: S SB BS BSB aa aa. S SB BS BSB aa aa. Ottamalla vielä kaikki vanhat muut kuin yksikkösäännöt saadaan tuloskielioppi: S SB BS BSB aa aa A aa aa S SB BS BSB aa aa B b bb. 121

Esimerkki 55. Poistetaan yksikköproduktiot edellä esimerkissä 53 saadusta kieliopista S S ε A aba aa S A B B bab bb. Verkko H on solmusta pääsee solmuihin S S, A ja B sekä S itse S A ja B sekä S itse A vain A itse B vain B itse Korvaamalla yksikköproduktiot edellä esitetyllä tavalla saadaan tuloskielioppi S aba aa bab bb ε A aba aa S aba aa bab bb B bab bb. Liian pitkien produktioiden lyhentäminen Chomskyn normaalimuodossa säännön oikea puoli saa olla pituutta k =... A X 1 X 2 X 3...X k 2 mutta silloin sen kummankin symbolin pitää olla välikkeitä, eli X 1, X 2 N. 1 mutta silloin sen ainoan symbolin pitää olla päätesymboli X 1 Σ. Edellä lauseessa 15 huolehdittiin, ettei tämän ainoan symbolin X 1 tarvitse olla välike. 0 mutta silloin koko säännön pitää olla S ε jossa S on koko kieliopin alkusymboli. Tästä on jo huolehdittu edellä lauseessa 14. Jos sääntö on liian pitkä eli k > 2 niin sitä voidaan lyhentää yhdellä symbolilla seuraavasti: 1. Olkoon U jokin kokonaan uusi välikesymboli jota ei ole aikaisemmin käytetty koko tässä kieliopissa. 2. Korvataan tämä liian pitkä sääntö sääntöparilla A X 1 X 2 X 3...X k 2 U U X k 1 X k. Tätä toistamalla voidaan ylipitkät säännöt lyhentää vaadittuun pituuteen k = 2. Lopuksi voidaan jokaiselle päätemerkille a Σ lisätä oma uusi välike C a ja sille sääntö C a a. Sen jälkeen päätemerkin a esiintymät muiden sääntöjen oikealla puolella voidaan korvata tällä uudella välikkeellä C a. Siis esimerkiksi sääntö A ab muuntuu muotoon A C a B jne. Olemme vihdoin päässeet Chomskyn normaalimuotoon! 122

Esimerkki 56. Jatketaan edelleen aiempaa esimerkkiä 54: S SB BS BSB aa aa A aa aa Kielioppi tulee muotoon S SB BS BA 1 C a A C a C a A 1 SB S SB BS BSB aa aa B b bb. A C a A C a C a B C b C b B C a a C b b. S SB BS BA 2 C a A C a C a A 2 SB Tätä voisi vielä yksinkertaistaa (koska uudet välikkeet A 1 ja A 2 ovat samat). Muistetaan vielä että alunperin lähdettiin liikkeelle selvästi pienemmästä kieliopista: S BSB A A aa aa B bb ε. Tehtävä 44. Muunna Chomskyn normaalimuotoon kielioppi S B C Tehtävä 45. Olkoon annettu kielioppi: S C A B abcd bbb b c ASB B cc ε CaAC a abc A Muunna kielioppi Chomskyn normaalimuotoon: 1. Poista lähtösymboli sääntöjen oikealta puolelta; 2. Poista ε-säännöt; 3. Poista tuloksesta yksikkösäännöt; 4. Poista tuloksesta ylipitkät säännöt. Ratkaisu: S AX 1 C a X 2 CX 3 C a X 4 CX 5 C a A a S C A B X 1 SB AX 1 C a X 2 CX 3 C a X 4 CX 5 C a A a C c C c X 2 C b C c X 3 C a X 4 X 4 AC X 5 C a A C a a C b b C c c CX 3 C a X 4 CX 5 C a A a C a X 2 CX 3 C a X 4 CX 5 C a A a 123

Huomaa: Välikkeet olisi voinut nimetä toisinkin. Ratkaisussa on myös yhdistetty joitakin välikkeitä, joiden säännöt olivat samoja.) Cocke Younger Kasami-algoritmi Annettuina: Kontekstiton kielioppi G, merkkijono w. Kysymys: Päteekö w L(G) vaiko ei?. Yksinkertaistettu kysymys: Jos G muunnetaan ensin Chomskyn normaalimuotoon G, niin miten silloin vastauksen voisi laskea? Yksinkertaistettu vastaus: Jos w = ε niin vastaa onko kieliopissa G sääntöä S ε vaiko ei. Jos taas w ε niin ryhdy käymään läpi kaikkia kieliopin G niitä johtoja, joiden pituus (eli askelten lukumäärä) on 2 w 1. Vastaa onko w jonkin niistä tuotos vaiko ei. Chomskyn normaalimuodossa jäsennyspuut ovat aitoja binääripuita, eli jokaisella sisäsolmulla on 2 lasta. Aidossa w -lehtisessä binääripuussa taas on w 1 sisäsolmua. Tästä saatiin tuo 2 w 1. Ongelma: Näitä johtoja voi olla pituuteen w verrattuna eksponentiaalisen paljon joten ainakaan tämä menetelmä ei ole käytännöllinen. Ratkaisu: Lähdetään kehittämään jotakin tehokkaampaa... Hajoita ja hallitse Hajoita ja hallitse (englanniksi divide and conquer ) on eräs yleinen algoritminsuunnittelumenetelmä. Pienen syötteen vastaus on yleensä helppo laskea. Suuri syöte käsitellään seuraavasti: 1. Jaetaan se jotenkin pienempiin syötteisiin. 2. Lasketaan niiden vastaukset rekursiivisesti tällä samalla algoritmilla. 3. Yhdistetään nämä pienten syötteiden vastaukset jotenkin vastaukseksi suurelle syötteelle. ASA-kurssi kertoo lisää tällaisista yleisistä algoritmisten ongelmien ratkaisuperiaatteista. Nyt ongelmamme on yleisesti annettuna välike A ja päätemerkkijono x Σ + vastaa voiko tästä välikkeestä A generoida tämän merkkijonon x vaiko ei? Erityisesti meitä kiinnostaa lopputulos jossa A = S ja x = w. 124

Pääohjelma: 1 if w = ε 2 then return onko kieliopissa sääntöä S ε vaiko ei sen alkusymbolille S 3 else return Johto(S, w) Johto(A, x): 1 if x = 1 2 then return onko kieliopissa sääntöä A x vaiko ei 3 else for each kieliopin sääntö A BC 4 do for each katkaisukohta 1 l < x 5 do y x:n l ensimmäistä merkkiä 6 z x:n loput merkit 7 if Johto(B, y) and Johto(C, z) 8 then return true 9 return false Kuva 24: Hajoita ja hallitse -jäsennysalgoritmi. Jos A x jossa x > 1 niin voimme erottaa siinä johdossa ensimmäisen ja loput askeleet, eli A BC x. Edelleen, tämän merkkijonon x täytyy jakautua alkuosaan y ja loppuosaan z joilla B y C z. Nämä osat eivät ole tyhjiä, koska kyseessä on Chomskyn normaalimuoto. Siten tässä ovat ne pienemmät kysymyksemme nämä y ja z ovat aidosti lyhyempiä kuin x. Jos taas x = 1, niin silloin vastaus voidaan katsoa suoraan kieliopista, koska se on Chomskyn normaalimuodossa. Näin saadaan rekursiivinen jäsennysalgoritmi kuvassa 24Hajoita ja hallitse -jäsennysalgoritmi. Tämäkin algoritmi toimii, mutta sekin on yhä eksponentiaalinen. Syynä on tällä kertaa se, että se laskee samoja välituloksia yhä uudelleen ja uudelleen: Jos kielioppi on esimerkiksi S AB CA... A CB... niin silloin kutsu Johto(S,abcde) tuottaa testit. Johto(A, abc) and Johto(B, de) Johto(C, a) and Johto(A, bcde) Johto(C, bc) and Johto(B, de). ja josta vuorostaan 125

Dynaaminen ohjelmointi Tätä rekursiivista algoritmia voi siis tehostaa taulukoimalla nämä välitulokset jotta niitä ei tarvitse laskea yhä uudelleen ja uudelleen. Tätä tapaa tehostaa hajoita ja hallitse -algoritmeja kutsutaan puolestaan dynaamiseksi ohjelmoinniksi (englanniksi dynamic programming ). Olkoon syötemerkkijono w lueteltuna merkki kerrallaan w 1 w 2 w 3...w n. Muodostetaan nyt 2-ulotteinen taulukko siten, että R i,j = {A N : A w i w i+1 w i+2...w j } eli taulukkopaikan R i,j sisältönä ovat täsmälleen ne välikkeet A N, joista voi tuottaa sen osan syötemerkkijonoa w, joka alkaa sen i. merkistä ja päättyy sen j. merkkiin. Eli ne A joilla kutsu Johto(A, w i w i+1 w i+2...w j ) = true. Tämä taulukko R on itse asiassa kolmiomatriisi: w 1 w 2 w 3 w 4 w 5 R 1,1 R 1,2 R 2,2 R 1,3 R 2,3 R 3,3 R 1,4 R 2,4 R 3,4 R 4,4 R 1,5 R 2,5 R 3,5 R 4,5 R 5,5 Taulukon R sarakkeet vastaavat osien alkukohtia syötemerkkijonossa w. Taulukon R jokainen diagonaali vastaa tietyn mittaisia osia: diagonaali R 1,1, R 2,2, R 3,3,... vastaa 1-merkkisiä diagonaali R 1,2, R 2,3, R 3,4,... vastaa 2-merkkisiä diagonaali R 1,3, R 2,4, R 3,5,... vastaa 3-merkkisiä,... diagonaali R 1,1+d, R 2,2+d, R 3,3+d,... d + 1-merkkisiä,... Ensin taulukoidaan tulokset kaikille 1-merkkisille osille: 1 for i 1, 2, 3,...,n 2 do R i,i {A: A w i P } 3 for k 1, 2, 3,...,n 1 do Aiemmin on taulukoitu tulokset kaikille k-merkkisille osille, joten nyt taulukoidaan tulokset kaikille k + 1-merkkisille: 4 for i 1, 2, 3,...,n k 5 do j i + k 6 R i,j 7 for l i, i + 1, i + 2,...,j 1 do Kun alkuosa x = w i w i+1 w i+2...w l ja loppuosa y = w l+1 w l+2 w l+3...w j : 8 R i,j R i,j {A: A BC P, B R i,l, C R l+1,j } 9 return onko lähtösymboli S R 1,n vaiko ei. 126

Kootut selitykset Esim. paikkaan R i,j tulee sellaiset välikkeet A, joilla A w i w i+1...w j. Tämän johdon täytyy alkaa askeleella A BC, missä välikkeestä B: voidaan johtaa osan w i...w j jokin alkuosa, esim. B w i w i+1...w l, ja välikkeestä C voidaan johtaa tämän osan loppuosa C w l+1 w l+2...w j. Nyt B löytyy valmiina taulukon paikasta R i,l ja C paikasta R l+1,j, jos ne ylipäänsä ovat olemassa. Mahdollisia taulukon paikkoja on kuitenkin useita, sillä kaikki yli kahden pituiset merkkijonot voidaan jakaa usealla tapaa kahteen osaan. Siis l voidaan valita monella tapaa (l = i...j 1). Esimerkiksi laskettaessa R 3,7 tarkastellaan kaikkia pareja (R 3,3, R 4,7 ), (R 3,4, R 5,7 ), (R 3,5, R 6,7 ), (R 3,6, R 7,7 ) : Siten w 3...w 7 saadaan (jos saadaan) jakamalla se kahteen osaan, kuten w 3...w 4 ja w 5...w 7, ja tutkimalla saadaanko ensimmäinen osa jostain symbolista B R 3,4 ja jälkimmäinen osa jostain symbolista C R 5,7, ja onko kieliopissa sääntöä A BC; jos on, niin lisätään A paikkaan R 3,7. Algoritmi täyttää taulukkonsa O(n 3 ) askeleessa kun emme laske mukaan kieliopin normalisointiin ja käsittelyyn kuluvaa aikaa. Olemme siis saaneet alun perin eksponentiaalisesta ja siten epäkäytännöllisestä algoritmista polynomisen ja siten käytännöllisen. Esimerkki 57. Sovelletaan CYK-algoritmia Chomskyn normaalimuotoiseen kielioppiin G: S AB BC A BA a B CC b C AB a Onko w = baaba L(G)? Nyt S R 1,5, joten w L(G). i 1 : b 2 : a 3 : a 4 : b 5 : a B S,A A, C B A, C B S, C B S,A, C S,A, C B S,A A, C Apukeino CYK-algoritmin simuloimiseen 127

1. kierros 2. kierros a0 a1 a2 a3 a4 a0 a1 a2 a3 a4 ei mittanauhoja 2:n pituinen mittanauha: 3. kierros a0 a1 a2 a3 a4 3:n pituiset mittanauhat: 4. kierros a0 a1 a2 a3 a4 4:n pituiset mittanauhat: 5. kierros a0 a1 a2 a3 a4 5:n pituiset mittanauhat: Esimerkki 58. Sovelletaan CYK-algoritmia Chomskyn normaalimuotoiseen kielioppiin G: Onko ababab L(G)? Siis ababab L(G). Johto ja jäsennyspuu taulukosta S AB BC A BA a B CC b C AB a i 1 : a 2 : b 3 : a 4 : b 5 : a 6 : b A, C S,C B B A, S A, C B S S, C B A, S B A, S A, C S,C B S, C S,C B Jos merkkijono w kuuluu kieleen, niin vastaava johto ja jäsennyspuu voidaan lukea taulukosta, jos siihen liitetään sopivat lisätiedot. Tarkastellaan esimerkkinä kielioppia G: S AB BC A BA a B CC b C AB a 128

Onko w = baaba L(G)? Ideana on liittää jokaiseen taulukossa olevaan välikkeeseen se sääntö ja ne taulukkopaikat joiden tuloksena se lisättiin taulukkoon. Esimerkiksi S R 1,n lisätään säännön S BC sekä taulukkopaikkojen B R 1,1 ja C R 2,5 tuloksena. Toisin sanoen, säännöstä otetaan kopio koristeltuina taulukkoindeksein: S 1,5 B 1,1 C 2,5. Näistä lisämerkinnöistä voidaan jäljittää vastaava johto: S BC bc bab bab bacc baabc baabc baabc baaba. Myös jäsennyspuun voi jäljittää näistä merkinnöistä. Puu ilmaantuu selvemmin näkymiin kääntämällä taulukkoa: 129

S b B A C B a A C B a C b a Jos kielioppi on moniselitteinen, niin jäsennyspuita voi olla useita. Sovelluksesta riippuen taulukkoon voidaan joko kerätä ne kaikki tai säilyttää vain jokin niistä. Tässä esimerkissä on toinenkin johto: S AB BAB bab bab bacc baabc baabc baabc baaba Tehtävä 46. Tarkastellaan kielioppia: S AB BC A BA a B CC b C AB a 130

Kuuluuko merkkijono w = abba kieleen? Jos, niin mikä on vastaava johto? Tehtävä 47. Tarkastellaan kielioppia: S AC AD a A a B b C AC a D BD a Kuuluvatko merkkijonot baaa, abaaa ja abba kieliopin tuottamaan kieleen? 5.6 Tehokkaammat jäsennysmenetelmät Palataan hetkeksi rekursiiviseen hajoita ja hallitse -jäsennysalgoritmiimme kuvassa 24Hajoita ja hallitse -jäsennysalgoritmi jota tehostimme dynaamisen ohjelmoinnin taulukoinnilla. Kysymys: Voisiko sitä tehostaa taulukoinnin sijasta jotenkin muuten, jos kielioppi olisikin sopivasti rajoitettu? Erityisesti: Voimmeko jotenkin valita suoraan jonkin jaon x = yz joka johtaa vastaukseen true jos sellainen jako on olemassa? Vastaus: Kyllä voimme, kunhan kielioppi on siis sopiva. Huomaa: Tämän sopivan kieliopin ei tarvitse enää olla Chomskyn normaalimuodossa. Esimerkki 59. Oikealle lineaarisessa kieliopissa riittää aina seuraavan merkin tarkastelu, eli jakokohta l = 1, koska kieli on säännöllinen. 5.6.1 LL(1)-kielioppi Laajennetaan edellisen esimerkin 59 intuitiota, ja tarkastellaan sellaisia kielioppeja, joissa seuraava sovellettava sääntö on aina yksikäsitteisesti määrätty, kun nykyinen välike ja seuraava syötemerkki tunnetaan. Näitä kielioppeja ja niillä jäsentyviä kieliä kutsutaan LL(1)-kieliopeiksi ja -kieliksi: Left to right scan, producing a Left parse with 1 symbol lookahead. Siis ne lukevat syötemerkkijonon vasemmalta oikealle eli kirjoitusjärjestyksessä. Tämä on tärkeää esimerkiksi ohjelmointikielten kääntäjissä: lähdekooditiedosto luetaan yhden kerran alusta loppuun. (Esimerkiksi CYK ei toiminut niin.) Ne tuottavat aina vasemmanpuoleisimman johdon. Ne käyttävät 1 kurkistussymbolia eli katsovat vain seuraavaa syötemerkkiä. Yleisemmin voi määritellä LL(k)-kieliopit ja -kielet, joissa katsotaan k > 0 syötemerkkiä eteenpäin, eli ylläpidetään k merkin syötepuskuria. LL(1)-kieliopeille voidaan laatia yksinkertainen rekursiivinen jäsennin. Siksi niitä suositaan, kun jäsennin pitää kirjoittaa käsin. 131

LL(1) riittää useimmille ohjelmointikielissä esiintyville rakenteille. Tämä jäsennin on tehokas: se toimii lineaarisessa ajassa O( w ) syötemerkkijono w pituuden suhteen. LL(1)-kielet ovat determinististen kielten osajoukko, joten voitaisiin käyttää rekursion sijasta myös determinististä pinoautomaattia. LL(1)-kielioppeihin kuuluvat siis ainakin sellaiset kieliopit, joissa jokaisen välikkeen A säännöt ovat muotoa A a 1 α 1 a 2 α 2 a 3 α 3... a k α k jossa jokainen haara i alkaa eri päätemerkillä a i Σ (siis a i a j aina kun i j) koska oikea haara on se, jonka päätemerkki on seuraavana syötteessä. Esimerkki 60. Tarkastellaan seuraavaa kielioppia G: E T + E T E T T a (E) Välike T on OK: seuraava syötemerkki ratkaisee, kumpaa sen säännöistä pitää käyttää. Mutta välike E ei, joten tekijöidään se: E TE E +E E ε T a (E) Esimerkiksi lauseen a (a + a) vasen johto voidaan nyt muodostaa seuraavan syötemerkin ohjaamana: E TE ae a E a TE a (E)E a (TE )E a (ae )E a (a + E)E LL(1)-kielioppien yleinen muoto a (a + TE )E a (a + ae )E a (a + a)e a (a + a). LL(1)-kielioppien yleisessä muodossa sallitaan myös produktioita, joiden oikeat puolet alkavatkin välikkeellä eikä päätemerkillä, sekä tyhjentyviä elli nollautuvia välikkeitä A, joilla siis A ε mutta kumpiakin näistä vain rajoitetusti. Esimerkiksi kielen a b c d tuottava kielioppi: S Ab Cd A aa ε C cc ε Kielioppi on LL(1)-muotoa, vaikka ensimmäiseksi sovellettavaa produktiota ei voikaan päätellä pelkästään alkusymbolin S produktioiden perusteella. Kuitenkin, jos merkkijono alkaa merkeillä a tai b, niin on sovellettava sääntöä S Aa, jos taas merkillä c tai d, niin sääntöä S Cd. Siis sovellettava sääntö on kuin onkin yksikäsitteisesti määrätty seuraavan merkin perusteella. Siis tarvittaisiin testi tutkimaan onko kielioppi LL(1)-muotoa. 132

Kielioppien muokkaaminen LL(1)-muotoon Kaikkia kontekstittomia kielioppeja ei voi muuntaa LL(1)-muotoon. Joskus kieli on LL(1)-luokassa, mutta sen kuvaava kielioppi ei ole oikeassa muodossa. Tällaiset melkein LL(1)-kieliopit voi muokata oikeaan muotoon seuraavilla operaatioilla: 1. vasen tekijöinti 2. vasemman rekursion poisto. Vasen tekijöinti Kielioppi, jossa on säännöt A αβ 1 αβ 2 jossa α ε, β 1 β 2 ei voi olla LL(1)-muotoinen, koska nämä haarat alkavat yhteisellä epätyhjällä osalla α. Otetaan käyttöön uusi välike A ja korvataan nämä produktiot produktioilla A αa A β 1 β 2, jossa α on jonojen αβ 1 ja αβ 2 pisin yhteinen alkuosa. Toisin sanoen ensin jäsennetään niiden pisin yhteinen alkuosa α sitten vastaa tutkitaan, jatkuuko syöte haaralla β 1 vaiko β 2. Vertaa matematiikassa ax + ay = a(x + y). Esimerkki 61. Kielioppi muutetaan muotoon N DN D D 0 1... 9 N DN N N ε D 0 1... 9 Välittömän vasemman rekursion poisto Kielioppi on vasemmalle rekursiivinen, jos jollakin välikkeellä A ja merkkijonolla γ on A + Aγ. Jos ajatellaan rekursiivista jäsentäjää, niin sen aliohjelma A kiertäisi kehää lukematta syötettään. 133

Vasemmalle rekursiivinen kielioppi ei voi täyttää LL(1)-ehtoa. Välitön vasen rekursio, siis suorat johdot A Aγ, voidaan välttää korvaamalla produktiot jossa β ε, produktioilla A Aβ α A αa A βa ε. Yleisempi vasemman rekursion poisto sivuutetaan. Esimerkki 62. Esimerkiksi kielioppi G 2 on välittömästi vasemmalle rekursiivinen: Siitä saadaan kielioppi G 2: N ND D D 0 1... 9 N DN N DN ε D 0 1... 9 Saman tuottaisi tosin myös kielioppi G 3 : N 0N 1N... 9N 0 1... 9 Muotoa A A olevat produktiot voidaan yksinkertaisesti jättää pois. Välikkeen A johdot ovat muotoa A Aβ Aββ Aβββ... αββ...β eli αβ. Lopulta on siis valittava sääntö A α tai rekursio ei pääty ikinä. Jokainen kontekstiton kielioppi voidaan teoriassa muuntaa vasemman rekursion välttävään Greibachin normaalimuotoon. Siinä säännöt ovat muotoa A ab 1 B 2 B 3...B k jossa ensin tulee päätemerkki a Σ sitten välikkeitä B 1, B 2, B 3,...,B k N (jotka voivat myös puuttua, eli k 0). Erikoistapauksena lähtösymbolille sallitaan myös S ε jos ε kuuluu kieleen. 134

Yleinen LL(1)-ehto Esitetään nyt yleinen LL(1)-ehto. Siinä käytetään seuraavia kahta apukäsitettä: first(α) = kaikki ne päätemerkit a Σ joilla on johto muotoa α a... Siis ne päätemerkit, jotka voivat aloittaa jonkun sellaisen merkkijonon, joka voidaan johtaa tästä α V. Lisäksi jos α ε niin myös ε first(α). follow(a) = kaikki ne päätemerkit a Σ jotka voivat seurata tätä välikettä A N jossakin johdossa. Siis joilla on johto muotoa S...Aa... Olkoon sitten A N kieliopin G mielivaltainen välike ja sen kaikki säännöt. A α 1 α 2 α 3... α k Nyt ensimmäinen osa LL(1)-ehtoa vaatii, että näiden sääntöjen oikeat puolet alkavat eri tavoin, eli että first(α i ) first(α j ) = (9) aina kun i j. Nimittäin jos olisi jokin x first(α i ) first(α j ) niin kumpaa säännöistä i vaiko j pitäisi käyttää tällä x? Tästä ensimmäisestä LL(1)-ehdosta (9) seuraa erityisesti, että korkeintaan yksi välikkeen A säännöistä voi tuottaa tyhjän merkkijonon ε eli tehdä välikkeestä A tyhjentyvän (eli nollautuvan). Toinen osa LL(1)-ehtoa koskee vain tyhjentyviä välikkeitä: Jos välike A N on tyhjentyvä, niin siltä vaaditaan ehdon (9) lisäksi, että follow(a) first(α j ) = (10) kaikilla 1 j k. Nimittäin jos olisi jokin y follow(a) first(α j ) niin kumpaa pitäisi tehdä tällä y: luoda tyhjä A vaiko epätyhjä säännöllä j? Kielioppi G on yleisessä LL(1)-muodossa, jos sen kaikki välikkeet ja säännöt täyttävät molemmat ehdot (9) ja (10). Kun kielioppi G on tätä yleistä LL(1)-muotoa, niin sille voidaan laatia rekursiivisesti etenevä jäsentäjä seuraavin periaattein: Pidetään yllä muuttujassa next seuraavaa syötemerkkiä. 135

error(...) tarkoittaa lopeta koko rekursiivinen jäsennys virheilmoitukseen... Käytännön ohjelmoinnissa se voisi vaikkapa nostaa poikkeuksen (exception). Tehdään tässä esimerkissä sellainen jäsentäjä, joka palauttaa arvonaan vastaavan jäsennyspuun. Jokaiselle päätesymbolille a Σ kirjoitetaan oma aliohjelma: a: 1 if next a 2 then error(tässä kohdassa olisi pitänyt olla a) 3 next lue seuraava syötemerkki 4 return uusi lapseton solmu nimeltään a Jokaiselle välikkeelle A N kirjoitetaan oma aliohjelma. Jos A ei ole tyhjentyvä, niin tämä aliohjelma on: A: 1 if next first(α 1 ) then haara(α 1 ) 2 if next first(α 2 ) then haara(α 2 ) 3 if next first(α 3 ) then haara(α 3 ). error(tässä kohdassa olisi pitänyt olla A) Huomaa, että nämä first-joukot ovat vakioita, jäsentäjä ei siis laske niitä. Niiden arvot on jo laskettu LL(1)-ehtoa (9) testattaessa. Jokainen haara(x 1 X 2 X 3...X m ) on oma ohjelmanpätkänsä 1 y 1 X 1 2 y 2 X 2 3 y 3 X 3. y m X m return uusi solmu nimeltään A lapsinaan y 1, y 2, y 3,...,y m Jos välike A on tyhjentyvä, niin sillä on tasan yksi sellainen α e jolla ε first(α e ). Täydennetään tätä α e vastaava edellisen A-aliohjelman if-ehto muotoon if next (first(α e ) \ {ε}) follow(a) then... joka sallii sen käytön myös tyhjän A tuottamiseen. Jälleen tämä follow(a) on etukäteen laskettu vakio, joten ehdon koko joukko voidaan laskea etukäteen. 136

Koko jäsentäjän pääohjelmaksi tulee 1 next lue syötteen ensimmäinen merkki 2 τ S eli kutsutaan lähtösymbolia vastaavaa aliohjelmaa 3 if next syötteen loppumerkki EOF ( End-Of-File ) 4 then error(tässä piti olla EOF) 5 return näin rakennettu koko syötteen jäsennyspuu τ Jokaiselle välikkeelle A N määritellään first(a) = first(α 1 ) first(α 2 ) first(α 3 )... first(α k ) (11) eli sen first-joukko koostuu kaikista sen sääntöjen oikeiden puolten α i firstjoukoista. Tällaisen oikean puolen α V first-joukko lasketaan puolestaan seuraavasti: Jos α = ε, niin first(α) = {ε}. Jos α on muotoa b... jollakin päätemerkillä b Σ, niin first(α) = {b}. Jos α on muotoa Bβ, jossa välike B ei ole tyhjentyvä, niin first(α) = first(b) joka taas lasketaan kuten yhtälössä (11). Jos α on muotoa Bβ, jossa välike B on tyhjentyvä, niin eli edetään eteenpäin jonossa α. first(α) = first(b) \ {ε} first(β) Kaiken vasemman rekursion poisto takaa, ettei tämä ole kehämääritelmä. Välikkeiden follow-joukot voidaan puolestaan laskea toistamalla seuraavia sääntöjä, kunnes mikään joukko ei enää kasva: Lisää EOF lähtösymbolin S joukkoon follow(s). Jos kieliopissa on jokin sääntö muotoa A αbβ, niin lisää joukkoon follow(b) kaikki joukon first(β) päätesymbolit. (Eli kaikki muut sen alkiot, mutta ei mahdollista tyhjää merkkijonoa ε). Jos kieliopissa on jokin sääntö muotoa A αbβ jossa ε first(β) niin lisää joukkoon follow(b) kaikki joukon follow(a) alkiot. Esimerkki 63. Esimerkin 60 tekijöidyssä kieliopissa tarvitaan LL(1)-jäsentäjää varten seuraavat joukot: first(t) = {a, (} first(e ) = {+,, ε} first(e) = first(t) follow(e ) = follow(e) = {EOF, )}. Näiden perusteella voidaan kirjoittaa jäsentäjä edellä kuvattuun tapaan. 137

Lyhennetään koodia kirjoittamalla yksi yhteinen aliohjelma kaikille päätemerkeille b { +,, (, ),a}: Terminaali(b): 1 if next b 2 then error(tässä kohdassa olisi pitänyt olla b) 3 next lue seuraava syötemerkki 4 return uusi lapseton solmu nimeltään b Pääohjelmaksi tulee: 1 next lue ensimmäinen syötemerkki 2 τ E 3 if next EOF 4 then error(tässä kohdassa olisi pitänyt olla EOF) 5 return τ Välikkeen E aliohjelmaksi tulee: E: 1 if next { (, a} then y 1 T y 2 E return uusi solmu nimeltään E ja lapsinaan y 1, y 2 2 error(tässä kohdassa olisi pitänyt olla E) Välikkeen E aliohjelmaksi tulee: E : 1 if next { + } then y 1 Terminaali( + ) y 2 E return uusi solmu nimeltään E ja lapsinaan y 1, y 2 2 if next { } then y 1 Terminaali( ) y 2 E return uusi solmu nimeltään E ja lapsinaan y 1, y 2 3 if next {EOF, ) } then return uusi lapseton solmu nimeltään E 4 error(tässä kohdassa olisi pitänyt olla E ) Välikkeen T aliohjelmaksi tulee: T: 1 if next {a} then y 1 Terminaali(a) return uusi solmu nimeltään T ja lapsenaan y 1 2 if next { ( } then y 1 Terminaali( ( ) y 2 E y 3 Terminaali( ) ) return uusi solmu nimeltään T ja lapsinaan y 1, y 2, y 3 3 error(tässä kohdassa olisi pitänyt olla E ) 138

Tätä systemaattisesti kirjoitettua jäsennintä voi selvästi vielä parannella paikallisin muutoksin: esimerkiksi aliohjelman T rivillä 2 tarkastetaan kahdesti, että next on (. Tehdään siis parempi C-pseudokoodilla. void E() { tulosta(e TE ) T(); E (); } void E () { if (next == + ) { tulosta(e +E) next = getnext(); E(); } else if (next == - ) { tulosta(e -E) next = getnext(); E(); } else tulosta(e ε) } void T() { if (next == a ) { tulosta(t a) next = getnext(); } else if (next == ( ) { tulosta(t (E)) next = getnext(); E(); if (next ) ) error(sulkeva sulku puuttuu); next = getnext(); } else error(t ei voi alkaa merkillä next); } Pääohjelma käynnistää ja päättää jäsennyksen: next = getnext(); E(); if (next EOF) error(tässä piti olla EOF). 139

Katsotaan esimerkki 64 sen toiminnasta. Sitten korvataan sen tulosteet yksinkertaisella koodingeneroinnilla. Esimerkki 64. Syötejonon a-(a+a) jäsennys tulostaa: E TE T a E -E E TE T (E) E TE T a E +E E TE T a E ε E ε Tulostus vastaa vasenta johtoa: E TE ae a E a TE a (E)E a (TE )E a (ae )E a (a + E)E a (a + TE )E a (a + ae )E a (a + a)e a (a + a). Oikeassa ohjelmassa tulosta-komennot voivat tehdä jotain hyödyllisempää (kuten laskea lausekkeen arvoa, generoida koodia,...). // Lelukääntäjä: tuottaa konekoodia edellisen kieliopin // mukaisten lausekkeiden arvon laskemiseksi; tulos rekisteriin // r1... EI ole testattu, vastuu lukijalla: void Ep() { if(next == + ) { next = getnext(); T(); printf("pop r1\npop r2\nadd r1, r2\npush r1\n"); Ep(); } else if(next == - ) { next = getnext(); T(); printf("pop r2\npop r1\nsub r1, r2\npush r1\n"); Ep(); } } void T() { if(numero_tai_muuttuja(next)) { printf("push % } else if(next == ( ) { next = getnext(); T(); Ep(); if(next!= ) ) printf("virhe: piti olla loppusulku\n"); next = getnext(); } else printf("virhe: T ei voi alkaa merkillä % } int main() { next = getnext(); T(); Ep(); printf("pop r1\n"); return 0; } 140

Edellisessä koodissa välike E on oleellisesti poistettu, ja se on korvattu sääntöjen oikealla puolella suoraan johdolla TE. Kielioppi generoi edelleen saman kielen: S TE E +TE TE ε T a (TE ) Lähtömuuttujasymboli S vastaa siis pääohjelmaa (main). Konekielikäskymme: push x laita x pinoon pop x poista pinon päällimmäinen, Tulos ja laita x:ään add r1,r2r1 r1 + r2 sub r1,r2r1 r1 r2 on siis lopuksi rekisterissä r1. Generoitu konekieli ei tosin ole kovin tehokasta... Tätä ei kysytä tentissä! Se on esimerkkinä oikeasta jäsentämisestä ja kääntämisestä tosin ilman sellaisia käytännön kysymyksiä kuin jäsennysvirheiden käsittely, jne. Lelu-ohjelmamme tulostus syötteellä (x + y) (a + b) : push x push y pop r1 pop r2 add r1, r2 push r1 push a push b pop r1 pop r2 add r1, r2 push r1 pop r2 pop r1 sub r1, r2 push r1 pop r1 5.6.2 LR-kieliopeista Simuloidaankin merkkijonon oikeaa johtoa rekursiivisesti. Saadaan LR(1)-kieliopit ja -kielet: left to right scan, producing right parse with 1 symbol lookahead. Yleisemmin, LR(k)-kielissä seuraavat k merkkiä määrittävät seuraavan johtoaskeleen. LR(0) = ns. yksinkertainen LR (Simple LR, SLR). LR(1) = deterministiset kielet, joten tasot k > 1 ovat enää teoreettisesti kiinnostavia. LR-jäsennys sisältää LL-jäsennyksen sillä lim LL(k) = LR(1). k Intutiivisesti, odotamme jäsentäessämme mahdollisimman pitkään emmekä heti kokeile sääntöä, eli teemmekin oikean emmekä vasenta johtoa. Nämä ovat tärkeitä työkaluja kääntäjien laatimisessa automaattisesti. Esimerkiksi yacc-työkalu tuottaa LALR- eli lookahead LR -jäsentimen, joka on hieman yksinkertaisempi kuin täysi LR(1)-jäsennin. 141

LR-jäsennyksen intuitio on parantaa kuvan 20 Generoi-ja-testaa -algoritmi generoija-testaa -pinoautomaattiamme. Saimme sen mielivaltaisesta kontekstittomasta kieliopista hyvin yksinkertaisella käännöksellä, mutta niinpä siinä olikin varsin paljon epädeterministisiä valintoja. LR-jäsennyksessä tehdäänkin tämä käännös huomattavasti huolellisemmin, ja sillä pyritään pääsemään eroon näistä valinnoista. Tämän käännöksen huolellisuus antaa nämä eri luokat kuten SLR, LALR tai täysi LR(1). Jos niistä kaikista päästään eroon, niin on saatu se etsitty jäsennysalgoritmi deterministinen pinoautomaattihan on tehokas simuloida. Jos ei päästä, niin usein voidaan ratkoa loput valinnat käsin: Esimerkiksi esimerkin 50 roikkuvan else-haaran ongelmassa pinoautomaatti ei else-syötemerkin nähdessään tiedä kumpaa sen pitäisi tehdä: soveltaa kielioppisääntöä pinossa jo oleviin alkioihin vai viedä tämäkin else-merkki pinoon? Silloin else-haara kuuluu lähimpään if-ehtoon -heuristiikan mukaan näistä jälkimmäinen on se mitä pinoautomaatin pitää tehdä. Sivuutamme yksityiskohdat. Kysymys: Jos kerran jo LL(1) riittää useimpiin ohjelmointikielissä esiintyviin syntaktisiin konstruktioihin, niin miksi meillä silti on myös LR-jäsennys? Vastaus: Oikeasti haluamme käyttää attribuuttikielioppeja, eli sellaisia kontekstittomia kielioppeja, joihin olemme liittäneet attribuuttien käsittelysäännöt. Kun kielioppi muunnetaan LL(1)-ehdon täyttävään muotoon (vasemman rekursion poistolla jne.) niin lopulta sen rakenne voi olla hyvinkin kaukana alkuperäisestä kieliopista. Siten siihen lopputulokseen voi olla varsin hankalaa ja epäintuitiivista liittää sopivia attribuuttien käsittelysääntöjä. LR-jäsennyksessä kielioppia ei tarvitse muokata niin radikaalisti, joten attribuuttien käsittelysäännötkin pysyvät yksinkertaisempina. Yhteenveto kontekstittomien kielten jäsennyksestä Sama asia: kuuluuko annettu merkkijono kieleen? Peruskysymys kaikkien formaalien kielten kohdalla (joita on siis muitakin kuin säännölliset ja kontekstittomat). Säännöllisten kielten yhteydessä vastaavaan kysymykseen vastattiin äärellisen automaatin avulla. Tämä on sekä teoriassa että käytännössä erinomainen ratkaisu. Nyt olisimme voineet käyttää pinoautomaattia (ja käytimmekin); mutta tämä on teoreettinen työkalu. Käytännössä epädeterminististä pinoautomaattia ei voi toteuttaa tietokoneella. Siis käytämme CYK-algoritmia. 142

Erikoistapaukset voidaan jäsentää / tunnistaa helpommin: oikealle lineaariset kieliopit vastaavat säännöllisiä kieliä: tunnistetaan siis äärellisellä automaatilla (tässä tapauksessa CYKin käyttö olisi liioittelua). Monet käytännössä tärkeät erityisesti ohjelmointikielille määritellyt - kieliopit ovat tyyppiä LL(k) tai LR(k). Näille on olemassa yksinkertaisia ja tehokkaita rekursioon perustuvia jäsennysmenetelmiä. Muitakin algoritmeja on. Työkaluja: Bison, Yacc: syötteenä (rajoitettu) kontekstiton kielioppi, tuloksena jäsentimen lähdekoodi... 5.7 Kontekstittomien kielten rajoituksista Jäsennyspuita tarkastelemalla voi osoittaa että vaikkapa kieli L = {a n b n c n : n N} ei ole kontekstiton. Tämä perustuu samatapaiseen pumppausideaan kuin epäsäännöllisyyttä osoitettaessa: Jos s on kielen L merkkijono, sillä on jäsennyspuu sopivassa kielen L kieliopissa G. Jos lisäksi s on kovin pitkä, niin sen jäsennyspuussa on oltava ainakin yksi pitkä haara. Kun jäsennyspuun haara on riittävän pitkä, niin ainakin yhden muuttujan A on pakko esiintyä ainakin kaksi kertaa. Muuttujan A esiintyminen omana jälkeläisenään jäsennyspuussa tarkoittaa, että A vax joillakin v,x Σ. Voimme pumpata tätä johtoa kuten kuvassa 25Toistuvan välikkeen pumppaus: A vax vvaxx vvvaxxx... Vertaa säännöllisten kielten pumppauslemmaan (lause 7): tilat vs. muuttujat silmukka automaatissa vs. johto A vax. Kontekstittomien kielten pumppauslemma Saman tapainen kuin säännöllisten kielten pumppauslemma. Nyt kuitenkin pumpataan kahta osamerkkijonoa v ja x samaan tahtiin. Hyvin karkea pinoautomaatti-intuitio: v = pyöritään silmukassa, jossa pushataan merkkejä pinoon x = pyöritään silmukassa, jossa popataan pinosta se, mitä v-silmukka sinne vei. Lause 16. Jos L on kontekstiton kieli, niin sille on olemassa pumppauspituus p N, jolle seuraava pätee: Jos s L ja s p, niin voidaan kirjoittaa s = uvwxy, jossa 1. uv i wx i y L kaikilla i N, 143

S S u v w A A x y u v v. A A A A A x x y v w x Kuva 25: Toistuvan välikkeen pumppaus. 2. vx > 0 ja 3. vwx p. Todistus: Sivuutetaan. Kuten jo on todettu, kontekstittomille kielille pätee joitain samantapaisia sulkeumaominaisuuksia kuin säännöllisille kielille: Lause 17. Olkoot L 1 ja L 2 kontekstittomia kieliä. Tällöin myös 1. L 1 L 2 (kielten yhdiste) 2. L 1 L 2 (kielten katenaatio) 3. (L 1 ) (kielen sulkeuma) 4. (L 1 ) R (kielen käänteiskieli) ovat kontekstittomia. Kontekstittomat kielet eivät kuitenkaan ole suljettuja leikkauksen ja komplementin suhteen. Niiden pumppauslemmalla eli lauseella 16 voitaisiin todistaa, että kieli ei ole kontekstiton. {a n b n c n : n N} Todistus: (Luonnos.) Kun valitaan merkkijonoksi s = a p b p c p niin sen osassa vwx ei voi olla kaikkia kolmea merkkiä. Siten sen osien v ja x pumppaaminen ei voi lisätä jokaista kolmea merkkiä kuten pitäisi. 144

Kielet L 1 = { a n b n c k : n, k N } L 2 = { a k b n c n : n, k N } ja ovat kontekstittomia, mutta niiden leikkaus L 1 L 2 = {a n b n c n : n N} ei siis olekaan kontekstiton. Todistimme juuri: Lause 18. Kontekstittomien kielten luokka ei ole suljettu leikkauksen suhteen. Tähän mennessä tapahtunut Kurssi lähti liikkeelle yksinkertaisista laskennan malleista: mitä niillä voidaan esittää, millaisia ilmiöitä havaitaan. Laskennan formalisointi perustuu joukko-oppiin ja logiikkaan, ja se mahdollistaa matemaattisen täsmälliset päättelyt. edellyttää idealisointia (mielivaltaisen pitkät syötteet jne.) tärkeä erityisseikka: epädeterminismi. Proseduraalisuus ja deklaratiivisuus Saman asian määrittely proseduraalisesti automaatilla tai deklaratiivisesti kieliopilla. Tärkeät ekvivalenssitulokset: kieli A on tunnistettavissa äärellisellä automaatilla kieli A voidaan esittää säännöllisenä lausekkeena. kieli A on tunnistettavissa pinoautomaatilla kieli A voidaan tuottaa kontekstittomalla kieliopilla. Niiden avulla siirrytään tarpeen mukaan proseduraalisesta deklaratiiviseen esitykseen tai toisin päin. Ylärajoja laskentaongelmien vaikeudelle: esimerkiksi, mikä tahansa säännöllinen kieli voidaan tuottaa kontekstittomalla kieliopilla. Todistukset olivat tyypillisesti konstruktiivisia, esimerkiksi kontekstiton kielioppi käännettiin pinoautomaatiksi. Simulaatio oli erikoistapaus konstruktiosta, epädeterministisestä deterministiseksi äärelliseksi automaatiksi. 145

Alarajoja ongelmien vaikeudelle: esimerkiksi kieli {0 n 1 n : n N} on vaikeampi kuin mihin äärellinen automaatti pystyy. Lähtökohtana oli formalisoitu laskentamalli. Todistetaan voimakas aputulos (kuten pumppauslemma) laskentamallin määritelmän perusteella. Sovelletaan aputulosta esimerkkitapauksiin (usein epäsuoran todistuksen kautta), laskentamallin yksityiskohdista ei enää tarvitse välittää. Teknisesti vaikeita (myös opelle). Hierarkia: Esimerkiksi säännölliset kielet ovat yhteydettömien kielten aito aliluokka. Simulaatio: kaikki säännölliset kielet ovat yhteydettömiä. Erottelu: kieli {0 n 1 n n N} on kontekstiton (yläraja), mutta ei säännöllinen (alaraja). Siirryttäessä tarkastelemaan vahvempia laskennan malleja ja kieliluokkia edellä esitellyt ilmiöt toistuvat tekniset yksityiskohdat monimutkaistuvat entisestään tulokset ovat entistäkin kiinnostavampia, koska esimerkiksi Turingin kone on malli oikealle tietokoneelle (tai mille tahansa nykytietämyksen valossa mahdolliselle laskentalaitteelle). Tällaisia asioita käsitellään tarkemmin LAT-kurssilla. 146

6 Laskennan filosofiaa Nyt etenemme kontekstittomien kielten tuolle puolen... Tietojenkäsittelytieteilijän yleissivistykseen kuuluvia asioita: Turingin kone, ja siihen liittyen ratkeavuus vs. ratkeamattomuus, sekä ongelmien vaikeusluokat, erityisesti P? = NP-ongelma. Emme mene yksityiskohtiin, mutta selvitämme mistä on karkeasti ottaen kysymys. Tästä eteenpäin asiat perustellaan (jos perustellaan lainkaan) käsien heiluttelulla matemaattisen todistelun sijasta. Siis: tasolla laskennan filosofia, varsinainen laskennan teoria jää kurssille LAT. Asioita jotka on Hyvä Tietää, sillä näihin voi törmätä mitä erilaisimmissa yhteyksissä eli hyödyllistä yleistietoa. Lisämotivaatio: termejä ratkeamaton ja NP-täydellinen tai NP-kova käytetään usein täysin väärin, ja tähän syyllistyvät myös monet oppineet. Siis erityisesti: kaikki NP-täydelliset ongelmat ovat kyllä ratkeavia, vaikka joku muuta väittäisikin. 6.1 Turingin kone Turingin kone on alkuaan matemaattisen logiikan tarpeisiin kehitelty laskennan malli. Tarkoituksena oli vangita mahdollisimman laajasti, millaisia asioita voidaan (periaatteessa) laskea mekaanisesti. Malli on sittemmin osoittautunut sopivaksi myös oikeiden tietokoneiden ymmärtämiseen. Turingin koneiden keksijä Alan M. Turing oli keskeinen henkilö II maailmansodan aikana liittoutuneiden purkaessa saksalaisten Enigma-salakirjoituskoneella koodattuja viestejä ja sodan jälkeen erään ensimmäisen oikean tietokoneen (ACEn) suunnittelussa Englannissa. Turingin kone (Turing machine, TM) on automaatti, jossa on periaatteessa rajoittamaton määrä muistia. Toisin kuin pinoautomaatti, Turingin kone saa käsitellä tätä muistiaan täysin vapaasti. Siten yksinkertaisin tapa päästä pinoautomaateista Turingin koneisiin olisi vaihtaa pinon tilalla apumuistina toimiva nauha, jota kone saa käsitellä vapaasti, kuten kuvassa 26Pinoautomaatista Turingin koneeksi. Mutta kaikkein yksinkertaisin Turingin kone on yksinauhainen Turingin kone, jossa 147