Ensimmäinen ohjelmointikieli

Samankaltaiset tiedostot
TIES542 kevät 2009 Lausekkeista ja vähän muustakin

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

Luku 3. Syntaktisia kysymyksiä. 3.1 Lausekkeet

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

Ohjelmointikielten periaatteet Syksy Antti-Juhani Kaijanaho

Java-kielen perusteet

TIES542 kevät 2009 Suoraviivaohjelmat

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

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

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

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

FORMAALI SYSTEEMI (in Nutshell): aakkosto: alkeismerkkien joukko kieliopin määräämä syntaksi: sallittujen merkkijonojen rakenne, formaali kuvaus

Syntaksi. TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 22. syyskuuta 2009 TIETOTEKNIIKAN LAITOS. Syntaksi. Aluksi.

11.4. Context-free kielet 1 / 17

2.4 Normaalimuoto, pohja ja laskentajärjestys 2.4. NORMAALIMUOTO, POHJA JA LASKENTAJÄRJESTYS 13

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Java-kielen perusteet

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

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

Yhteydettömät kieliopit [Sipser luku 2.1]

TIEA241 Automaatit ja kieliopit, kevät Antti-Juhani Kaijanaho. 12. tammikuuta 2012

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

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

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

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Jäsennys. TIEA341 Funktio ohjelmointi 1 Syksy 2005

Attribuuttikieliopit

3. Muuttujat ja operaatiot 3.1

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

Täydentäviä muistiinpanoja Turingin koneiden vaihtoehdoista

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Säännölliset kielet. Sisällys. Säännölliset kielet. Säännölliset operaattorit. Säännölliset kielet

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

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

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

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

TIES542 kevät 2009 Tyyppiteorian alkeet

Tietojenkäsittelyteorian alkeet, osa 2

Johdatus Ohjelmointiin

Pinoautomaatit. Pois kontekstittomuudesta

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

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

Ohjelmoinnin perusteet Y Python

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

Rekursiiviset tyypit

TIEA341 Funktio-ohjelmointi 1, kevät 2008

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 19. syyskuuta 2016

Tietotekniikan valintakoe

TIES542 kevät 2009 Tyyppijärjestelmän laajennoksia

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

Algebralliset tietotyypit ym. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIES542 kevät 2009 Denotaatio

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

Tyyppejä ja vähän muutakin. TIEA341 Funktio ohjelmointi 1 Syksy 2005

TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 27. lokakuuta 2009

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

Haskell ohjelmointikielen tyyppijärjestelmä

8. Kieliopit ja kielet

3.5. TYYPIT 43. g(x) muuten. että tämä funktio todella kuuluu funktioalueeseen.

Ohjelmoinnin perusteet Y Python

Ydin-Haskell Tiivismoniste

2. Yhteydettömät kielet

MS-A0402 Diskreetin matematiikan perusteet

Alityypitys. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

Automaatit. Muodolliset kielet

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Opiskelijan pikaopas STACK-tehtäviin. Lassi Korhonen, Oulun yliopisto

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

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

lausekkeiden tapauksessa. Jotkin ohjelmointikielet on määritelty sellaisiksi,

7/20: Paketti kasassa ensimmäistä kertaa

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

Ohjelmoinnin perusteet Y Python

Taulukot. Jukka Harju, Jukka Juslin

Se mistä tilasta aloitetaan, merkitään tyhjästä tulevalla nuolella. Yllä olevassa esimerkissä aloitustila on A.

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Datatähti 2019 loppu

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

Tietueet. Tietueiden määrittely

S BAB ABA A aas bba B bbs c

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

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

7. Näytölle tulostaminen 7.1

ELM GROUP 04. Teemu Laakso Henrik Talarmo

Palautetta viime luennosta

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

ITKP102 Ohjelmointi 1 (6 op)

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Sisältö. 22. Taulukot. Yleistä. Yleistä

Säännöllisten kielten sulkeumaominaisuudet

TIEA341 Funktio-ohjelmointi 1, kevät 2008

5.5 Jäsenninkombinaattoreista

Pythonin Kertaus. Cse-a1130. Tietotekniikka Sovelluksissa. Versio 0.01b

Oliot ja tyypit. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho. Jyväskylän yliopisto Tietotekniikan laitos

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

Java-kielen perusteet

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

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

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

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

Transkriptio:

Ensimmäinen ohjelmointikieli ALKEIS-suora Antti-Juhani Kaijanaho 15. tammikuuta 2007 Kaksi tärkeintä tekijää, joiden takia konekielinen ohjelmointi on vaikeaa, ovat rekisterien hallinta sekä aritmeettisten lausekkeiden litistäminen. Tässä monisteessa tarkastellaan näiden ongelmien ratkaisemista, ensin suoraviivaohjelmien ja sitten while-ohjelmien kannalta. Suoraviivaohjelmilla tarkoitetaan ohjelmia, joissa ei ole lainkaan silmukoita tai muunlaisia hyppyoperaatioita. Suoraviivaohjelmien hyödyllisyys sinänsä on toki varsin vähäistä, mutta jokainen imperatiivinen ohjelma koostuu joukosta hyppyoperaatioin tai silmukoin yhdistetyistä pienistä suoraviivaohjelmista. Luvussa esitellään kieli ALKEIS-suora, joka on yksinkertaisin ALKEIS-kieli. Sillä voi ilmaista suoraviivaohjelmia, jotka koostuvat peräkkäistetyistä sijoituslauseista, tulostuslauseista ja syötteenlukulauseista. 1.1 Arvo Arvot ovat abstrakteja, matemaattisia käsitteitä. Arvoja ovat esimerkiksi eri kokonaisluvut, merkkijonot ja muut sellaiset. Ne ovat ajattomia ja paikattomia: ei ole mielekästä pohtia, milloin nolla syntyi tai milloin sana "kaamos" lakkaa olemasta. Arvon lukumäärääkään ei voida mielekkäästi laskea (kuinka monta ykköstä on?). Arvot eivät myöskään muutu (oho, ykkönen onkin nyt kakkonen vai onko?). On tärkeää käsittää, että arvot eivät näy missään. Tietokoneohjelman lähdekoodissa esiintyvä merkkijono 42 ei ole arvo 42; se edustaa tai merkitsee (engl. denote) sitä. Myöskään ohjelman ruudulle tulostama Hei, Antti-Juhani ei ole merkkijonoarvo vaan se edustaa sitä. Tästä huolimatta arvoilla on merkittävä rooli tietokoneohjelmien teoriassa ja ymmärtämisessä. 1 Suoraviivaohjelman elementit Suoraviivaohjelmoinnin perusperiaate on seuraavanlainen: ohjelma koostuu lauseista 1 (engl. statements), jotka yhdistetään peräkkäistyksellä (engl. sequencing). Tärkein lause on sijoituslause (engl. assignment statement), joka asettaa muuttujalle (engl. variable) jonkin uuden arvon (engl. value), jonka se saa laskemalla jonkin tietyn lausekkeen (engl. expression) arvon senhetkisessä ympäristössä (engl. environment). Käykäämme nyt edellisessä kappaleessa esille tulleet käsitteet yksitellen läpi. 1 Huomaa: käskyt (engl. instructions) kuuluvat konekieliohjelmointiin. Korkean tason kielissä puhutaan lauseista. 1.2 Muuttuja Muuttuja on paikka, jossa voi säilyttää yhtä ja vain yhtä arvoa. Imperatiivisessa ohjelmoinnissa on oleellista, että muuttujan sisältämä arvo voidaan korvata toisella. Toisin kuin arvo, muuttuja on ajassa ja paikassa kiinni: muuttuja syntyy joskus ja kuolee joskus, ja se sijaitsee aina jossain paikassa. Muuttuja on rekisterin ja muistialueen abstraktio. Elinaikanaan muuttuja on sidottu (engl. bound) johonkin tiettyyn rekisteriin tai muistialueeseen. Rekisterillä on tunnus, muistialueella on osoite ja pituus, kaikki tavallisesti etumerkittömiä kokonaislukuja. Muuttujalla sen sijaan on yleensä nimi (nimettömät muuttujat jätämme toistaiseksi huomiotta) ja tyyppi, joista voidaan johtaa, mihin muistialueeseen muuttuja on sidottu. 1

Muuttujalla on monia ominaisuuksia (engl. attributes). Tärkeimmät niistä on jo mainittu: nimi, tyyppi, arvo, muuttujan muistialueen osoite ja muuttujan muistialueen pituus. Nämä jakautuvat siististi kahteen eri lokeroon: muuttujan ominaisuus voi olla joko staattinen (engl. static) tai dynaaminen (engl. dynamic). Dynaaminen ominaisuus on sellainen, joka vaihtelee eri suorituskertojen välillä; staattinen ominaisuus on sama suorituskerroista riippumatta. Dynaamisia ominaisuuksia ovat arvo sekä muuttujan muistialueen osoite. Staattisia ominaisuuksia ovat nimi ja tyyppi. Muuttujan muistialueen pituus on yleensä staattinen ominaisuus, sillä se on tavallisesti määritettävissä muuttujan tyypistä, mutta aina näin ei ole. Usein muuttuja samastetaan nimeensä. Tämä on yleensä järkevää, mutta tarkkana pitää olla, ettei synny sekaannuksia, kun sama nimi voi eri yhteyksissä tarkoittaa eri muuttujaa. 1.3 Ympäristö Jokaisella lauseella on ympäristö, joka yhdistää muuttujien nimet niihin muuttujien ominaisuuksiin, jotka ovat kyseisen lauseen kohdalla voimassa. Kuten muuttujien ominaisuudet, myös ympäristöt luokitellaan staattisiin ja dynaamisiin: staattiset ympäristöt sisältävät staattisia (pysyviä) ominaisuuksia ja dynaamiset ympäristöt sisältävät dynaamisia (muuttuvia) ominaisuuksia. 1.4 Lauseke Lauseke on ohjelmointikielen ilmaisu, joka edustaa arvoa. Yksi tärkeimmistä piirteistä, joka erottaa (symbolisen) konekielen korkean tason ohjelmointikielistä on tuki mielivaltaisen monimutkaisille lausekkeille, joita ohjelmoijan ei itse tarvitse jakaa yksittäisiin operaatioihin, joiden välitulokset hänen pitäisi tallentaa jonnekin. Maailman vanhimman korkean tason ohjelmointikielen nimi on FORTRAN, formula translator, syystä. Sallittujen lausekkeiden joukko riippuu kielestä. Käytännössä kaikki ohjelmointikielet tukevat ainakin tavallisia aritmeettisia lausekkeita: 1. Lukuvakio on aritmeettinen lauseke. 2. Muuttuja, joka on nykyisessä ympäristösssä sidottu lukuarvoon, on aritmeettinen lauseke. 3. Jos e ja e ovat aritmeettisia lausekkeita, niin e + e, e e, e e ja e/e ovat aritmeettisia lausekkeita. 4. Jos e on aritmeettinen lauseke, niin e ja (e) ovat aritmeettisia lausekkeita. Yllä esitetyt lauseketyypit voidaan jaotella primäärilausekkeisiin (engl. primary expressions), unaarilausekkeisiin (engl. unary expressions) sekä binäärilausekkeisiin (engl. binary expressions). Primäärilausekkeita ovat lukuvakiot ja muuttujat lausekkeina. Unaarilausekkeet jäsentyvät siten, että niissä on ensin jokin operaattori (engl. operator) ja sitten alilauseke (ns. operandi (engl. operand)); unaarilausekkeet ovat muotoa e, missä operaattori on ja operandi on e 2. Binäärilausekkeet alkavat alilausekkeella (vasen operandi), jonka jälkeen tulee operaattori (+,, tai /) ja lopuksi toinen alilauseke (oikea operandi). Sulkulausekkeen (e) luokittelu ei ole aivan yksinkertainen asia. Periaatteessa se samastetaan alilausekkeeseensa e, joten voitaisiin ehkä ajatella, että ne luokitellaan samalla tavalla, jolloin sulkulausekkeen luokka riippuu alilausekkeensa luokasta. Toisaalta, kun lausekkeille tehdään muodollinen kielioppi, sijoittuu sulkulauseke siellä samaan kategoriaan primäärilausekkeiden kanssa. Lauseketta, jossa binäärinen operaattori voi sijaita operandiensa välissä, sanotaan infixlausekkeeksi. Ongelmana tällaisissa lausekkeissa on, että ei ole aina aivan selvää, miten lauseke pitäisi ymmärtää. Esimerkki tällaisesta moniselitteisestä (engl. ambiguous) lausekkeesta on 1 + 2 3: pitääkö se ymmärtää samoin kuin (1 + 2) 3 vai kenties samoin kuin 1 + (2 3)? Jo muinaiset matemaatikot tiesivät tähän ratkaisun: pitää määritellä, mikä on eri operaattoreiden presedenssi (engl. precedence) ja assosiatiivisuus (engl. associativity). Jos tarkasteltavana on lauseke muotoa e e e, missä operaattorilla on korkeampi presedenssi kuin operaattorilla, sen sovitaan tarkoittavan (e e ) e ; jos taas operaattorilla on matalampi presedenssi kuin operaattorilla, sen sovitaan tarkoittavan e (e e ). Jos operaattoreilla ja on sama presedenssi, niin lausekkeen tulkinta riippuu niiden assosiatiivisuudesta. Jos molemmat assosioituvat vasemmalle (engl. associate 2 Oikeastaan nämä ovat prefiksilausekkeita; unaarilausekkeita ovat myös postfiksilausekkeet e. 2

(unaarinen) / + Taulukko 1: Tavanomaisten aritmeettisten operaattoreiden normaali presedenssirelaatio to the left), lauseke tulkitaan (e e ) e, ja jos molemmat assosioituvat oikealle (engl. associate to the right), lauseke tulkitaan e (e e ). Jos ne assosioituvat eri suuntaan tai ainakaan toinen ei assosioidu lainkaan, lausekkeen todetaan olevan kielen sääntöjen vastainen. Jos sama operaattori voi esiintyä sekä unaarisena että binäärisenä (esimerkiksi ), tulee sen unaarinen ja binäärinen versio pitää erillään; niillä on yleensä eri presedenssi. Presedenssi- ja assosiointisäännöt vaihtelevat kielestä toiseen. Yleensä lienee järkevää, että matematiikasta tutut aritmeettiset operaattorit (esimerkiksi yhteen-, vähennys-, kerto- ja vähennyslaskuoperaattorit sekä vastalukuoperaattori) noudattavat matematiikasta tuttua presedenssiä: vastalukuoperaattorilla (unaarinen ) on korkeampi presedenssi kuin kerto- ja jakolaskuoperaattoreilla, joilla puolestaan on korkeampi presedenssi kuin yhteen- ja vähennyslaskuoperaattoreilla. Kerto- ja jakolaskuoperaattoreilla on sama presedenssi, ja samoin yhteen- ja vähennyslaskuoperaattoreilla on sama presedenssi. Kaikki nämä operaattorit assosioituvat vasemmalle paitsi vastalukuoperaattori, joka assosioituu oikealle. Presedenssi muodostaa operaattoreiden välille osittaisjärjestyksen 3. Yleensä kuitenkin presedenssirelaatio on täydellinen järjestys ja se esitetään tavallisesti taulukkona, jossa korkeamman presedenssin omaavat operaattorit ovat korkeammalla kuin matalamman presedenssin omaavat operaattorit. Edellä esitetyt tavanomaiset presedenssisäännöt on esitetty taulukossa 1. Taulukossa 2 on esitetty Javan presedenssitaulukko. Kannattaa huomata, että presedenssi ja assosiatiivisuus eivät määrittele laskujärjestystä vaan jäsennyksen, vaikka toisin usein ajatellaankin. Esi- 3 Muistakaamme, että osittaisjärjestys (engl. partial order) on relaatio, joka on refleksiivinen (a a pätee aina), antisymmetrinen (jos a b ja b a pätevät, niin a = b pätee) ja transitiivinen (jos a b ja b c pätevät, niin a c pätee). Täydellinen järjestys (engl. total order) vaatii lisäksi totaalisuuden (a b tai b a pätee aina). ++ -- ++ -- + - ~! * / % + - << >> >>> < > <= >= instanceof ==!= & ^ &&?: = += -= *= /= %= &= ^= = <<= >>= >>>= Taulukko 2: Javan operaattoreiden presedenssi merkiksi lausekkeessa 2 + 3 + 4 5 voidaan laskea ensin 2 + 3 siitä huolimatta, että koulussa opetettiin, että kertolasku lasketaan ensin. Vaikka infix-lausekkeet ovatkin kaikista tutuimpia, eivät ne ole ainoat mahdollisuudet. Voidaan esimerkiksi käyttää ns. puolalaisia (engl. Polish) eli prefix-lausekkeita, joissa operaattori tulee aina ensin ja vasta sitten operandit. Tämän esitystavan etu on, että sulkuja tai presedenssi- ja assosiatiivisuussääntöjä ei tarvita, jos operaattorien operandimäärä on kiinteä (eli jos sama operaattori ei ole sekä unaarinen että binäärinen): + 2 3 4 5 tarkoittaa yksiselitteisesti samaa kuin infixlauseke (2 3) + (4 5). Vastaavasti voidaan käyttää käänteisesti puolalaisia (engl. reverse Polish) eli postfix-lausekkeita, joissa operaattori tulee operandien jälkeen. Tuo sama lauseke olisi postfixlausekkeena 2 3 4 5 +. 4 Lausekkeita onkin paras ajatella puina, jotka vain kirjoitetaan näkyviin infix-, prefix- tai postfixtyylillä. Näissä puissa operaattorit ovat sisäsolmuja ja operandit ovat operaattorinsa alipuita. Puun lehdet muodostuvat muuttujista ja lukuvakioista. Tällaista puuta sanotaan lausekkeen rakennepuuksi (engl. structural tree); eräs sellainen on kuvattu kuvassa 1. 4 Postfix-lausekkeet ovat käytössä Forth- ja Postscriptkielissä. Prefix-lausekkeiden muunnelma, ns. Cambridgenpuolalainen lauseketyyppi, jossa operaattorit tulevat aina operandien edellä mutta lausekkeiden ympärillä on aina sulut, on käytössä Lisp-sukuisissa kielissä. 3

/ + 6 7 8 2 3 Kuva 1: Lausekkeen (2 + 3) 6 7/8 rakennepuu 1.5 Sijoituslause Sijoituslause on imperatiivisen ohjelmoinnin toinen kulmakivi. Se on yleensä muotoa x:=e (tarkka ilmiasu vaihtelee kielittäin), ja sen tehtävänä on laskea lauseke e nykyisessä ympäristössä ja muuttaa sitten ympäristöä siten, että muuttujan x arvoksi tulee lausekkeen e arvo. 1.6 Peräkkäistys Peräkkäistys on toinen imperatiivsen ohjelmoinnin kulmakivi. Se on yleensä hieman piilossa, sillä se esiintyy yleensä ohjelmissa vain muunlaisten kontrollirakenteiden käyttämättä jättämisenä. Peräkkäistyksen tehtävänä on suorittaa jono lauseita peräjälkeen: s 1 ;... ; s n tarkoittaa, että s 1 suoritetaan tämän peräkkäistyslauseen alussa voimassa olevassa ympäristössä ja yleisesti s i suoritetaan lauseen s i 1 muokkaamassa ympäristössä; peräkkäistyslauseen lopussa voimassa on se ympäristö, joka on voimassa s n :n lopussa. 1.7 Tyyppi Tyyppi (engl. type) on muuttujan tai lausekkeen ominaisuus. Sillä on kolme tehtävää: se kertoo, minkä joukon alkio muuttujan tai lausekkeen arvo on; lisäksi se kertoo, miten se tavujono, joka muodostaa sen muistialueen, johon muuttuja on sidottu, tulkitaan arvoksi; lausekkeen tyyppi kertoo myös, mitkä operaatiot ovat kyseiselle lausekkeelle sallittuja. Olioihin kohdistuu operaatioita. Operaatiot olettavat, että kohteena oleva olio on jotain tiettyä tyyppiä. Tyyppivirheellä (type error) tarkoitetaan sitä, että jonkin suoritettavan operaation kohteena on olio, jonka tyyppi ei ole se, mitä operaatio olettaa. Huomaamatta jäävät tyyppivirheet johtavat ohjelman sekoamiseen: koska olio on eri tyyppiä kuin oletetaan, sen tulkinta arvoksi on täysin päätön ja tuloksena on roskaa. Mikäli ohjelmointikieli (eli oikeasti sen määrittely) vaatii toteutukseltaan, että se diagnosoi (ilmoittaa käyttäjälle) kaikki tyyppivirheet, kieli on vahvasti tyypitetty (strongly typed). Muussa tapauksessa kieli on heikoisti tyypitetty (weakly typed). Joitakin kieliä voidaan myös verrata keskenään sen mukaan, mitä tyyppivirheitä ne vaativat diagnosoitavaksi. Esimerkiksi C ja C++ ovat heikosti tyypitettyjä (molemmissa on mahdollista kirjoittaa tyyppivirheellinen ohjelma, jonka tyyppivirhettä ei toteutus huomaa), mutta C++ on paljon vahvemmin tyypitetty kuin C. Java, Haskell ja Scheme ovat esimerkkejä vahvasti tyypitetyistä kielistä. Kielen toteutus voi diagnosoida tyyppivirheen joko ennen suoritusta tai suoritusaikana. Ensin mainitussa tapauksessa on kyse staattisesta tyyppitarkastuksesta (static typechecking), jälkimmäisessä dynaamisesta tyyppitarkastuksesta (dynamic typechecking). Staattiseen tyyppitarkastukseen palataan myöhemmin; nyt tarkastellaan dynaamista tyyppitarkastusta. Mikäli dynaamista tyyppitarkastusta halutaan, tulee olion sisällä olla tieto siitä, mitä tyyppiä se on. Oliokielissä (joissa tämä tieto tarvitaan jo metodien dynaamisen sidonnan toteuttamiseen) tämä toteutetaan jo aiemmin mainitulla luokkaoliolla, jonka osoite tallennetaan jokaisen tätä tyyppiä olevan olion alkuun. Tyyppitarkastus voidaan tehdä tarkastamalla, mihin luokkaolioon olion alussa on osoitin. Vastaava tekniikka toimii myös muissa kielissä. Dynaamisissa funktiokielissä (mm. Lisp), 4

joissa tyyppejä on varsin vähän, on tapana käyttää oliona konesanaa, josta varataan muutama bitti tyyppitunnisteeksi (isommat oliot laatikoidaan). Joissakin kielissä on hyvin rikas tyyppijärjestelmä; palaamme näihin myöhemmin applikatiivisen ohjelmoinnin yhteydessä. Seuraavat tyypit ja niiden muunnelmat ovat yleisiä imperatiivisissa kielissä (konkreettisuuden vuoksi käytän niistä C-kielen tyylisiä nimityksiä): int Kokonaisluku väliltä 2 n 1,..., 2 n 1 1, joka esitetään kahden komplementtina yhdessä konesanassa. (Tässä n on konesanan pituus bitteinä.) unsigned int Kokonaisluku väliltä 0,..., 2 n 1, joka esitetään etumerkittömänä yhdessä konesanassa. (Tässä n on konesanan pituus bitteinä.) byte Kokonaisluku väliltä 2 n 1,..., 2 n 1 1, joka esitetään kahden komplementtina yhdessä tavussa. (Tässä n on tavun pituus bitteinä, tavallisesti 8, jolloin lukualue on 128,..., 127.) unsigned byte Kokonaisluku väliltä 0,..., 2 n 1, joka esitetään etumerkittömänä yhdessä tavussa. (Tässä n on tavun pituus bitteinä, tavallisesti 8, jolloin lukualue on 0,..., 255.) float Liukuluku, joka esitetään tavallisesti IEEE 754 -standardin yksinkertaisen tarkkuuden liukulukuna neljän tavun tavujonossa. double Liukuluku, joka esitetään tavallisesti IEEE 754 -standardin kaksinkertaisen tarkkuuden liukulukuna kahdeksan tavun tavujonossa. T[] Taulukko, jonka alkiot ovat tyyppiä T. Useimmat tyyppijärjestelmät laskevat näistä intin, unsigned intin, byten sekä unsigned byten kokonaislukutyypeiksi (engl. integral types) (siltä osin kuin tukevat näitä tyyppejä). Kaikki tyyppijärjestelmät sallivat aritmeettisten lausekkeiden rakentamisen niin, että jokaisen alilausekkeen tyyppi on sama; tällöin kokonaisen aritmeettisen lausekkeen tyyppi on tuo tyyppi. Vastaavasti aritmeettisissa lausekkeissa on yleensä sallittu liukulukutyyppien (float ja double) käyttäminen, jos kaikilla alilausekkeilla on sama tyyppi. Tiukka tyyppijärjestelmä kieltää tyyppien sekakäytön. Monet varsinkin vanhemmat imperatiiviset ohjelmointikielet määrittelevät kaikenlaisia automaattisia tyypinmuunnoksia (engl. coercions) näiden tyyppien välille: aina silloin, kun tiukka tyyppijärjestelmä olisi kieltämässä jonkin lausekkeen, muunnoksia käyttävä tyyppijärjestelmä pyrkii korjaamaan lausekkeen tyypityksen lisäämällä siihen muunnosoperaatioita. Esimerkiksi C- kielessä [6] byte 5 voidaan muuttaa tarvittaessa inttyypiksi, float voidaan muuttaa doubleksi ja mikä tahansa unsigned -tyyppi voidaan muuttaa vastaavaksi etumerkilliseksi tyypiksi, ja näitä muunnoksia voidaan tarvittaessa ketjuttaa. C-kielen vuoden 1999 versiossa myös bool voidaan muuttaa tarvittaessa unsigned byteksi. Automaattiset tyypinmuunnokset ovat käteviä, mutta niillä on myös kääntöpuolensa. Tyyppijärjestelmän yksi tehtävä on suojata ohjelmoijaa virheiltä, jotka johtuvat tyyppien sekoittamisesta; jos kieli tekee automaattisia tyypinmuunnoksia, se todennäköisesti tekee niitä myös tilanteissa, joissa ohjelmoija ei niitä odota. Erityisen ikävä voi olla esimerkiksi kokonaislukutyypin muuttuminen yllättäen liukuluvuksi, mikä saattaa vähentää laskentatarkkuutta, tai tämän muunnoksen tapahtumatta jääminen silloin, kun ohjelmoija sitä odottaa (esimerkiksi C-kielessä jakolasku 1 / 2 antaa tulokseksi 0, koska molemmat operandit ovat kokonaislukutyyppisiä; useampikin ohjelmoija on odottanut tuossa tilanteessa tapahtuvan automaattisen muunnoksen liukuluvuksi, jolloin tulos olisi 0.5). 1.8 Taulukoista Taulukoiden (engl. arrays) hyväksyminen kieleen vaatii hieman tarkennusta edellä esitettyihin käsitteisiin. Taulukkoja tukevan kielen sallittuihin lausekkeisiin pitää totta kai lisätä indeksointi a[i]; tässä vaaditaan, että a:n tyyppi on T[] jollekin tyypille T ja että i:n tyyppi on jokin kokonaislukutyyppi. Tällöin indeksointilausekkeen tyyppi on T ja sen arvo on se arvo, joka on tallennettuna taulukon a kohtaan i. Taulukoiden kanssa nousee ensimmäistä kertaa esille ohjelmointikielen turvallisuuden ongelma. 5 C-kielessä oikeasti byte on nimeltään signed char ja unsigned byte on nimeltään unsigned char. 5

Taulukot ovat äärellisiä, joten herää kysymys, mitä tapahtuu, jos taulukon koko on 5, taulukon indeksit alkavat nollasta ja indeksinä käytetään vaikkapa 42:ta. Yksinkertaisinta olisi toki unohtaa koko ongelma ja jättää asiasta huolehtiminen kokonaan ohjelmoijan vastuulle. Näin toimivat ainakin C ja C++. Jos ohjelmoija kuitenkin vahingossa (tai tahallaan!) indeksoi taulukon ulkopuolelle, vähintäänkin lausekkeen arvo on arvaamaton; hyvällä onnella käyttöjärjestelmä tai prosessori huomaa tämän kielletyn indeksoinnin ja lopettaa ohjelman. Vastuullisempaa olisi luultavasti se, että kielen toteutus tarkistaa indeksoinnin kunnollisuuden juuri ennen itse indeksointioperaatiota eli tekee rajatarkastuksen (engl. bounds check); tämä tosin hidastaa ohjelman suoritusta. Tehtävä 1 Pascal-kielessä taulukon indeksirajat kuuluvat taulukon tyyppiin: VAR a : ARRAY [5..10] OF INTEGER määrittelee muuttujan a, joka on kokonaislukuja sisältävä taulukko, jossa on kuusi alkiota ja indeksi i on sallittu, jos 5 i 10 pätee. Indeksirajojen täytyy tietenkin olla vakioita, koska ne ovat osa a:n tyyppiä. Mitä hyötyjä ja mitä haittoja tästä on? Jos taulukon on tarkoitus olla hyödyllinen osa imperatiivista kieltä, pitää olla mahdollista päivittää taulukon yksittäisen alkion arvoa. Tämä tarkoittaa, että indeksointi pitää sallia myös sijoituslauseen vasemmalla puolella muuttujan paikalla. Yksinkertaisinta varmaankin olisi sallia sijoituslause, joka on muotoa a[i]:=e, missä a ja i ovat kuten yllä ja e on sopivantyyppinen lauseke. Yleisemmin kuitenkin voi olla järkevää sanoa, että sijoituslause on e:=e, missä e ja e ovat samantyyppisiä lausekkeita. Ei kuitenkaan ole järkevää sanoa, että e :n arvo sijoitetaan e:n arvoon. Christopher Strachey [10] ehdotti 1960-luvulla arvon käsitteen jakamista kahtia: lausekkeella on kaksi arvoa, vasen arvo (engl. left value) ja oikea arvo (engl. right value) 6. Lausekkeen oikea arvo on se, mitä on edellä kutsuttu pelkäksi arvoksi. Muuttujalausekkeen vasen arvo kuvaa sen muistialueen alkuosoitetta, joka on tuohon muuttujaan sidottu, ja yleisemmin lausekkeen vasen arvo kertoo, mihin muistialueeseen lausekkeen oikea arvo on tallennettu. Nämä nimet tulevat tietenkin siitä, että 6 Nämä englanninkieliset termit lyhennetään nykyisin yleensä lvalue ja rvalue. lausekkeen vasenta arvoa käytetään, kun lauseke esiintyy sijoituslauseen vasemmalla puolella, ja oikeaa arvoa käytetään, kun lauseke esiintyy sijoituslauseen oikealla puolella. Edellä esitetty vasemman arvon määritelmä on kuitenkin turhan salliva. Jos jokaisella lausekkeella olisi vasen arvo, niin se olisi myös lukuvakiolla 42. Jos näin on, niin sijoituslauseen 42:=2 tulisi olla sallittu. Mitä se tekisi? Itse asiassa joissakin FORTRAN-kielten toteutuksissa on todella ollut mahdollista muuttaa lukuvakion arvoa niin, että tuollaisen sijoituslauseen jälkeen jokainen viittaus lukuvakioon 42 tarkoittaisikin 2:a. Tämä ei ole yleensä kuitenkaan järkevää, joten tavallisesti sovitaan, että kaikilla lausekkeilla ei ole vasenta arvoa. Tarkastelemistamme lausekkeista vasen arvo on vain muuttujalausekkeella sekä indeksointilausekkeella a[i], jos a:lla on vasen arvo. Kun taulukkon alkioon voi sijoittaa, on indeksoinnin rajatarkastus yllättävänkin tärkeää. Esimerkiksi jos taulukkoon luetaan verkosta dataa, saattaa rajatarkastuksen puuttuminen tehdä ohjelmointivirheestä tietoturvaongelman. Kannattaa huomata, että koska T[] on tyyppi, jos T on tyyppi, niin taulukot voivat koostua taulukoista. 2 Abstrakti syntaksi Kielen määritteleminen lähtee sen pohtimisesta, mikä on sen abstrakti syntaksi (engl. abstract syntax): minkälaisia konstruktioita kielessä on, kun jätetään huomiotta sellaiset yksityiskohdat kuten mikä tarkkaan ottaen kelpaa muuttujan nimeksi taikka mikä on operaattoreiden presedenssi. Abstraktin syntaksin tasolla kielellä kirjoitettu ohjelma hahmotetaan enemmänkin tietorakenteina, jotka kuvaavat ohjelmaa, kuin merkkijonona. Tästä saatiin jo aiemmin esimerkki: abstraktin syntaksin tasolla lauseke hahmotetaan rakennepuunaan. Abstrakti syntaksi voidaan määritellä käyttäen kontekstittomista kieliopeista tuttua merkintätapaa. Esimerkiksi ALKEIS-suoran abstrakti syntaksi on kuvassa 2. Tässä välikesymboleina käytetään kreikkalaisia kirjaimia, jotka on valittu niin, että ne muistuttavat sitä, mitä ne edustavat (esimerkiksi ε, epsilon on lauseke eli expression). Metasyntaktiset symbolit ovat: 6

π Programs π ::=var δ begin γ end δ Declarations ohjelma: muuttujien esittelyt ja sitten lause δ ::=ι : τ muuttujan esittelyssä on nimi ja tyyppi δ 1 ; δ 2... tai kaksi esittelyä peräkkäin τ Types τ ::=int unsigned int sana kokonaisluvuksi tulkittuna byte unsigned byte tavu kokonaisluvuksi tulkittuna float double liukuluku τ[ν] taulukko γ Statements γ ::=ε 1 := ε 2 sijoituslause write ε kirjoituslause read ε lukulause γ 1 ; γ 2 peräkkäistys ε Expressions ε ::=ι lauseke voi olla... muuttuja ν... lukuvakio ε... vastalukuoperaatio ε 1 ε 2... kertolasku ε 1 / ε 2... jakolasku ε 1 % ε 2... jakojäännös ε 1 + ε 2... yhteenlasku ε 1 ε 2... vähennyslasku ε 1 [ε 2 ]... indeksointi Kuva 2: ALKEIS-suoran abstrakti kielioppi 7

::= erottaa produktion vasemman ja oikean puolen. erottaa produktion vaihtoehtoiset oikeat puolet. Muut symbolit (mukaan lukien lihavoidut sanat) ovat päätesymboleita. Välikesymboleilta näyttävät ι Ids (muuttujannimet, identifiers) ja ν Constants (lukuvakiot, numeric constants) ovat oikeasti päätesymboleita, jotka edustavat mielivaltaista muuttujannimeä (ι) ja mielivaltaista lukuvakiota (ν). Abstrakti syntaksi eroaa tavanomaisesta (konkreetista) syntaksista siten, että abstrakti syntaksi on tavallisesti merkkijonokieliopiksi tulkittuna hyvin moniselitteinen. Tämä ei kuitenkaan haittaa, sillä ajatuksena on, että abstrakti syntaksi katsotaan puukieliopiksi (engl. tree grammar) eli että se kuvaa sallittuja puita sallittujen merkkijonojen asemesta. Puukielioppina abstrakti syntaksi on hyvinkin yksiselitteinen, kunhan seuraava ehto pätee [9, s. 2]: Kunkin välikesymbolin produktioilla on kullakin oma päätesymbolijono. Toisin sanoen, ei ole olemassa kahta saman välikesymbolin produktiota siten, että kun näistä produktioista poistetaan kaikki välikesymbolit, jäljelle jäävien päätesymbolien muodostamat jonot eivät ole sama jono. Puukieliopiksi abstrakti syntaksi tulkitaan siten, että kukin produktio kertoo, miten puu voidaan konstruoida: juuri laputetaan sillä päätesymbolien jonolla, joka jää jäljelle, kun produktion oikeasta puolesta poistetaan välikesymbolit, ja kutakin produktion oikean puolen välikesymbolia vastaa kyseisen välikesymbolin puukieliopin mukainen alipuu. Alipuut pidetään järjestyksessä. Abstraktia syntaksia käytetään ahkerasti erityisesti kielten teoreettisessa tarkastelussa. Silloin tarvitaan tapa kirjoittaa abstraktin syntaksin puut merkkijonoiksi jollakin tavalla, sillä puupiirrokset ovat ikävän tilaavieviä ja kömpelöitä teoreetikkojen kannalta. Käytännössä tässä toimitaan niin, että abstrakti syntaksi tulkitaan merkkijonokieliopiksi ja sulkeita sekä epämuodollisia presedenssija assosiatiivisuussopimuksia käytetään ratkaisemaan moniselitteisyysongelmat. Merkkijonoon saatetaan jättää alaindeksoituja välikesymboleja ilmaisemaan paikkoja, johon voi laittaa minkä tahansa merkkijonon (tai puun), joka sopii kyseisen välikesymboliin. Tämän ei kuitenkaan ole tarkoitus olla kielen varsinainen konkreetti kielioppi, vaan kyseessä on puhtaasti sopimus, joka mahdollistaa abstraktin syntaksin puiden kirjoittamisen tiiviisti ja luettavasti. Abstrakti kielioppi on keskeinen käsite ohjelmointikielten teoriassa, mutta se on merkittävä myös käytännössä, sillä kielenkäsittelimien kuten kääntäjien tai tulkkien kannattaa yleensä käsitellä ohjelmia ainakin osan aikaa juuri rakennepuina (ja sallitut rakennepuut määrittelee juuri abstrakti kielioppi). Rakennepuu on suhteellisen yksinkertaista esittää tietorakenteena kieltä käsittelevässä ohjelmassa. Jos esimerkiksi toteutuskielenä on Java, on selkeintä tehdä jokaisesta välikesymbolista abstrakti luokka tai rajapinta ja periä siitä final-luokka jokaiselle produktiolle. Produktioluokalle annetaan attribuutti jokaista produktion oikealla puolella esiintyvää välikesymbolia kohti. Ohjelma rakentuu sitten tämän luokkarakenteen ympärille jommalla kummalla seuraavista strategioista: 1. Jokaista rakennepuulle tehtävää operaatiota kohti laitetaan välikesymboliluokkiin tai - rajapintoihin metodi. Tästä on se etu, että saman kielikonstruktion kaikki toiminnallisuus on yhdistetty samaan luokkaan. Kielen muuttaminen on tässä rakenteessa helppoa, mutta uuden operaation lisääminen vaatii kaikkien luokkien muuttamista. 2. Luokkarakenne varustetaan Visitor-suunnittelumallin [3] tukimetodeilla ja -rajapinnoilla. Kukin operaatio toteutetaan sitten luokkana, joka toteuttaa rakennepuuluokaston visitorrajapinnan. Tämän hyöty on, että uusia operaatioita voidaan luoda toisistaan riippumatta ja koskematta rakennepuuluokastoon. Vastaavasti kielen muuttaminen on vaivalloista. Valinnan tueksi sopii siis nyrkkisääntö, että visitor-malli sopii paremmin vakiintuneen ja varsin hitaasti muuttuvan ohjelmointikielen kyvykkääseen kääntäjään ja että metodimalli sopii paremmin uuden, nopeasti muuttuvan ohjelmointikielen yksinkertaiseen prototyyppikääntäjään. Toki molempia menetelmiä voi käyttää samassakin 8

kääntäjässä tarpeen mukaan: esimerkiksi kääntäjä, joka jakautuu kahteen vaiheeseen, jotka kommunikoivat erityisen välikielen (engl. intermediate language) avulla, kannattanee toteuttaa niin, että lähdekielen prosessointi tehdään metodimallilla ja välikielen käsittely (esimerkiksi optimoinnit) tehdään visitor-mallilla. Näin siksi, että välikielen voisi ajatella muuttuvan hitaammin kuin nuoren ohjelmointikielen. 3 Konkreetti kielioppi Kielen määrittely ja tutkiminen onnistuu aivan hyvin abstraktin kieliopin pohjalta. Käytännön käyttöön on kuitenkin kiinnitettävä jokin konkreetti kielioppi (engl. concrete syntax), joka kuvaa, mitkä merkkijonot ovat sallittuja kielellä kirjoitettuja ohjelmia ja samalla määrää, miten merkkijonot kuvautuvat abstraktin kieliopin mukaisiksi rakennepuiksi. Tapana on määritellä konkreetti kielioppi kahdessa osassa: ensin määritellään kielen sanasrakenne (engl. lexical structure) ja sitten käyttäen sanasrakenteen sanasia päätesymboleina, kontekstiton kielioppi, johon synteettisten attribuuttien avulla liitetään rakennepuiden kokoamisohje. On olemassa ohjelmointikieliä, jotka eivät ilmaise ohjelmia merkkijonoina. Tällaisia ovat esimerkiksi Prograph ja Fenfiren Clangit [7]. Tällaiset kielet käyttävät jotain aivan muuta ohjelmien konkreettiseen esittämiseen. Useimmat tällaiset, eitekstuaaliset kielet voidaan kuvata kuitenkin abstraktin kielioppinsa ja semantiikkansa osalta samaan tapaan kuin muutkin kielet. Emme puutu näihin kieliin. 3.1 Merkit Ohjelman teksti kirjoitetaan merkkijonona. Ohjelmointikielet määrittelevät joukon merkkejä, joita voidaan käyttää ohjelmien kirjoittamiseen. Tavallisesti rajoitutaan ECMA-6 IRV -merkistöön [2] (tunnetaan paremmin nimellä ASCII). Tällöin käytettävissä ei ole mm. ääkkösiä. Monet kielet sallivat jonkin tuon merkistön laajennuksen käyttämisen merkkijonovakioissa, mutta esimerkiksi muuttujannimet ja avainsanat ovat rajoitettuja ASCII:hin. Joistakin kielistä on olemassa kaksi eri versiota, jotka eroavat toisistaan merkistön puolesta. Ensimmäinen tällainen kieli oli Algol, josta määriteltiin referenssiversio, joka oli suunniteltu ohjelmien painamista varten (esim. lehteen), ja kukin toteuttaja määritteli siitä kovoversion, jolla ohjelmat voitiin kirjoittaa koneelle. Esimerkiksi referenssiversiossa saatettiin lihavoida avainsanat, kun hardwareversiossa avainsanat saatettiin aloittaa pisteellä. Jotkin uudemmat ohjelmointikielet ovat ottaneet käyttöön ISO 10646 -merkistön (tunnetaan paremmin nimellä Unicode), jolla voidaan periaatteessa ilmaista mikä tahansa käytössä oleva merkki (ja joitakin historiallisia merkkejä): ääkkösten lisäksi Unicodella voidaan kirjoittaa kiinaa ja jopa matematiikkaa. Useimmat tällaiset kielet sallivat koko Unicode-repertuaarin käyttämisen merkkijonoissa ja myös osan Unicode-merkistöstä käyttämisen muuttujannimenä. Silti useimmat rajoittuvat Unicoden ASCII-osajoukkoon avainsanojensa ja muun sellaisen osalta. 3.2 Sanaset Sanasiksi (token) sanotaan kielen pienimpiä merkityksellisiä rakenneosia. Tyypillisissä ohjelmointikielissä sanaset jakautuvat nimiin (identifiers), avainsanoihin (keywords), operaattoreihin, välimerkkeihin (punctuation) ja literaaleihin. Monissa kielissä on vielä lisäksi erillisenä sanasluokkana tyypinnimet (type names). Nimet ovat tavallisesti epätyhjiä merkkijonoja, jotka alkavat kirjaimella ja muodostuvat tavallisesti kirjaimista ja numeroista. Jotkut kielet sallivat nimissä myös joitakin muita merkkejä (alaviiva ja dollarinmerkki ovat yleisiä). Joissakin kielissä kirjainten koko merkitsee: auto ja Auto ovat joissakin kielissä eri nimiä, joissakin kielissä sama nimi. Yleensä nimeksi katsotaan pisin merkkijono, joka voidaan tulkita nimeksi (esimerkiksi merkkijonossa auto kissa on tavallisesti tulkittuna kaksi nimeä, auto ja kissa). Se, mikä lasketaan kirjaimeksi ja mikä ei (ja, jos kielessä ei kirjainkoolla ole merkitystä, mitkä kirjaimet katsotaan toistensa kokovariaatioiksi), vaihtelee. Useimmat kielet lähtevät englannin kielen säännöistä: kirjaimia ovat englannin kielen aakkoset. Jotkin uudemmat kielet hyväksyvät 9

kirjaimiksi myös ISO 8859-1 -merkistön sisältämät kirjaimet, myös ääkköset. Unicode-standardi [1] määrittelee nimet ohjelmointikielestä riippumatta käyttäen Unicoden merkkiominaisuuksia. Sen perusidea on yllämainittu: aloitetaan kirjaimella ja jatketaan kirjaimilla sekä numeroilla. Nyt tosin käsitteet kirjain ja numero ovat reilusti laajempia kuin aiemmin: näin määriteltynä esimerkiksi π ja φωω ovat sallittuja nimiä. Useimmat Unicodea käyttävät ohjelmointikielet käyttävät jotain muunnelmaa Unicoden nimimääritelmästä. Avainsanat ovat tavallisesti nimien näköisiä sanasia, joilla on jokin erityinen vakiomerkitys, joka on kiinnitetty kielen suunnitteluvaiheessa. Tavallisesti avainsanat ovat ns. varattuja sanoja, jolloin niitä ei voi käyttää missään muussa merkityksessä, mutta esimerkiksi Schemessä ei ole varattuja sanoja lainkaan. Mikäli avainsanat eivät ole varattuja sanoja, monimutkaistuu kielen toteuttaminen huomattavasti, ja joskus sillä kirjoitettujen ohjelmien luettavuus kärsii. Vai mitä sanoisitte seuraavasta PL/I -pätkästä: IF IF = THEN THEN = ELSE; ELSE ELSE = END; END Toisaalta varattujen sanojen ongelmana on se, että kielen laajentaminen on vaikeaa. Moni uusi ominaisuus tarvitsee oman avainsanansa, ja jos sitä varten luodaan uusi varattu sana, jokin aiemmin täysin toimiva ohjelma menee rikki (ei käänny enää uudessa versiossa). Tästä syntyy helposti kiusaus käyttää jo olemassaolevia avainsanoja uudestaan eri merkityksessä. Esimerkiksi C:n avainsanalla static on kaksi toisistaan täysin poikkeavaa merkitystä. Operaattorit muodostuvat tavallisesti erikoismerkeistä kuten +, <= ja &&. Niiden tehtävänä on toimia algebrallisissa lausekkeissa kahden tai useamman alilausekkeen välissä merkitsemässä näiden välistä operaatiota. Joskus myös avainsana voi toimia operaattorina. Useimmissa kielissä operaattorit on ennalta, kieltä suunnitellessa määritelty: uusia ei ohjelmoija voi ottaa käyttöön. Joissakin kielissä, kuten Haskellissa, operaattoriksi kelpaa mikä tahansa tietyistä merkeistä koostettu merkkijono, ja näiden operaattoreiden merkitys voidaan määrätä ohjelmassa. Välimerkkejä ovat sulkeet, aaltosulkeet, hakasulkeet, pilkut, puolipisteet ja muut sellaiset merkit, jotka eivät kuulu mihinkään muuhun kategoriaan mutta joilla on kuitenkin erityinen, kielen suunnittelun aikana kiinnitetty merkitys. Varsin yleistä on, että joissakin kielissä osaa välimerkkien tehtävistä hoitaa jokunen avainsana ja päinvastoin (esimerkiksi vertaa Pascalin begin- ja end -avainsanaa C:n aaltosulkeisiin). Literaalit ovat ohjelmaan kirjoitettuja vakioita, jotka edustavat tiettyjä vakioarvoja. Useimmat ohjelmointikielet tukevat merkkijonoliteraaleja ("..."), kokonaislukuliteraaleja (12345), liukulukuliteraaleja (3.0e2) ja merkkiliteraaleja (... ). Literaalien rakenne on varsin samanlainen eri kielissä. Monissa kielissä (kuten C) on erikseen sanaskategoria tyypinnimille. Tämä johtuu siitä, että näiden kielten konkreetin kieliopin toteuttaminen olemassaolevalla, tehokkaalla jäsennysteknologogialla olisi muuten hankalaa. Joissakin kielissä (kuten Haskell) tyypinnimet on tunnistettavissa eroon tavallisista nimistä jollakin nimen muotoon liittyvällä piirteellä: esimerkiksi Haskellissa tyypinnimet alkavat isolla alkukirjaimella ja muut nimet pienellä alkukirjaimella. Monissa kielissä (esimerkiksi kaikki C-sukuiset) tyyppinimien tunnistamiseen tarvitaan yhteistyötä leksikaalisen ja konkreetin kieliopin tasojen välillä: kun konkreetissa kieliopissa havaitaan, että nyt määritellään uusi tyypinnimi, tämä tyypinnimi kerrotaan leksikaaliselle tasolle. Sanasten välissä on toisinaan tyhjää. Useimmissa kielissä tyhjämerkit (whitespace) jätetään huomiotta paitsi silloin, kun sitä tarvitaan erottamaan eri sanaset toisistaan. Tällaiset kielet ovat vapaamuotoisia (free-form). Aivan ohjelmoinnin alkuaikoina, esimerkiksi Fortranissa, oli välttämätöntä sijoitella ohjelman eri osat tiettyihin kohtiin, jolloin välilläkin oli väliä. Joissakin nykykielissä (esim. Python ja Haskell), tyhjämerkeillä osoitetaan ohjelman rakenne niin, että mitään erityisiä välimerkkejä sitä osoittamaan ei käytetä. Ohjelmointikielten leksikaalinen taso on yleensä mahdollista määritellä säännöllisenä kielenä (regular language) ja useimpien kielten ohjelma on mahdollista jakaa sanasiin käyttämällä sopivaa äärellistä automaattia. Usein kielen määrittelydokumentissa kyseinen säännöllinen kieli esitetään kuitenkin kontekstittoman kieliopin tapaan joukkona produktioita. 10

3.3 Muodolliset kieliopit Ohjelmointikielten konkreetti kielioppi määritellään yleensä täsmällisesti käyttämällä yhteydettömien kielioppien (context-free grammars) käsitteistöä. Konkreetti kielioppi ilmaistaan yleensä käyttäen John Backusin 1950- ja 1960-lukujen vaihteessa kehittämää merkintätapaa, jota Peter Naur kehitti edelleen ja käytti Algol 60:n määrittelydokumentissa, nimittäin BNF:ää (Backus Naur form, ei Backus normal form [8]) tai jotain sen muunnelmaa. BNF:llä ilmaistuna yksinkertaisten aritmeettisten lausekkeiden kielioppi kirjoitetaan seuraavasti: Expression ::= Term Expression + Term Expression Term Term ::= Factor Term Factor Term / Factor Factor ::= Literal ( Expression ) ISO ja IEC standardoivat vuonna 1996 laajennetun version BNF:stä [5], EBNF:n. EBNF laajentaa BNF:ää lisäämällä siihen tuen valinnaisuuden, toiston, ryhmittelyn, määräkertaisen toiston ja poikkeustapausten ilmaisemiseen. EBNF sallii välikesymbolin koostua useammasta sanasta. Lisäksi EBNF sallii kommenttien lisäämisen kieliopin kuvaukseen. Internet-protokollien määrittelyissä käytetty BNF:n muunnelma (Augmented BNF eli ABNF) on myös standardoitu: RFC 2234 7 on syntaktisen metakielen ehdotettu Internet-standardi (Proposed Standard). 3.4 ALKEIS-suoran konkreetti kielioppi ALKEIS-suoran konkreetti kielioppi määritellään kahdessa osassa käyttäen perusformalismina C- sukuisten kielten määrittelyssä käytettyä metasyntaktista kieltä, joka kuvataan alempana. Formaalin määrittelyn lisäksi annetaan rajoitteita ja tarkennuksia suomen kielellä. 7 http://www.rfc-editor.org/rfc/rfc2234.txt Käytetyn formaalin notaation pohjalla on tuttu ja turvallinen kontekstittomien kielioppien teoria. Kielioppimääritykset voidaan hahmottaa joukoksi produktioita. Pääsääntöisesti kukin produktio esitetään yhdellä rivillä. Produktion vasen ja oikea puoli erotetaan toisistaan kaksoispisteellä, ja jos, kuten usein käy, sama välikesymboli on useamman peräkkäisen produktion vasemmalla puolella, jätetään se ja erottava kaksoispiste pois kaikilla muilla paitsi ensimmäisellä rivillä. Välikesymbolit kirjoitetaan kursiivilla ja päätesymbolit tasalevyisellä kirjasimella. Tyhjää produktiota ei käytetä, sen sijaan pääte- tai välikesymbolin valinnaisuus ilmaistaan kirjoittamalla sen perään alaineksiksi opt. Mikäli jonkin välikesymbolin kaikki produktiot koostuvat pelkästään yhdestä päätesymbolista, tämä voidaan lyhentää kirjoittamalla erottavan kaksoispisteen perään one of, jolloin nämä kaikki päätesymbolit luetellaan seuraavilla riveillä. ALKEIS-suoran konkreetti kielioppi jaetaan leksikaaliseen ja syntaktiseen osaan. Leksikaalisen osan määrittelyn pohjaksi annetaan kuvan 3 kielioppisäännöt ALKEIS-suoralla kirjoitettu ohjelma esitetään merkkijonona. Jäsentämistä varten se esikäsitellään seuraavasti, tuloksena jono sanasia: 1. Mikäli merkkijono on tyhjä, esikäsittely päättyy. 2. Merkkijonon alusta poistetaan kaikki tyhjämerkit (välilyönnit, tabulaattorit ja rivinvaihdot). 3. Jos merkkijonon alussa on nyt merkki #, se ja kaikki sen perässä tulevat merkit ensimmäiseen rivinvaihtoon asti poistetaan, ja sitten palataan kohtaan 1. 4. Merkkijonon alusta etsitään ensin pisin mahdollinen osajono, joka kelpaa joksikin seuraavista: punctuation, raw-identifier, integralconstant, unsigned-constant tai floating-constant. Tuo osajono laitetaan muistiin ja poistetaan merkkijonon alusta. Mikäli tällaista ei löytynyt, merkkijono ei ole kelvollinen ALKEISsuora-ohjelma. 5. Mikäli löytynyt osajono kelpaa punctuationiksi, se laitetaan sanajonon perään ja palataan kohtaan 1. 11

punctuation: raw-identifier: id-rest: id-start: id-cont: letter: digit: integral-constant: unsigned-constant: digits: unsigned-suffix: floating-constant: exponent: keyword: one of : < ; [ ] + - * % ( ) id-start id-rest opt id-cont id-rest id-cont letter _ id-start digit one of A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z one of 0 1 2 3 4 5 6 7 8 9 - opt digits digits unsigned-suffix digit digits digit one of u U - opt digits. digits opt exponent opt - opt digits one of begin byte double end float int read unsigned var write Kuva 3: ALKEIS-suoran leksikaalinen kielioppi 12

6. Mikäli löytynyt osajono kelpaa integralcontantiksi, sanajonon perään laitetaan integral-constant ja itse osajono pannaan muistiin kyseisen sanasen semanttiseksi arvoksi. Sitten palataan kohtaan 1. 7. Mikäli löytynyt osajono kelpaa unsignedcontantiksi, sanajonon perään laitetaan unsigned-constant ja itse osajono pannaan muistiin kyseisen sanasen semanttiseksi arvoksi. Sitten palataan kohtaan 1. 8. Mikäli löytynyt osajono kelpaa floatingcontantiksi, sanajonon perään laitetaan floating-constant ja itse osajono pannaan muistiin kyseisen sanasen semanttiseksi arvoksi. Sitten palataan kohtaan 1. 9. Nyt löytynyt osajono kelpaa raw-identifieriksi. Jos se kelpaa myös keywordiksi, se laitetaan sanajonon perään ja palataan kohtaan 1. 10. Sanajonon perään laitetaan identifier ja itse osajono pannaan muistiin kyseisen sanasen semanttiseksi arvoksi. Sitten palataan kohtaan 1. Varsinainen syntaktinen osa jäsentää ohjelman sanasjonon pohjalta. Syntaktisessa osassa sanaset ovat päätesymboleita, ja välikesymboleilta näyttävät identifier, floating-constant ja integralconstant käsitetäänkin päätesymboleiksi. ALKEISsuoran syntaktinen konkreetti kielioppi esitetään kuvassa 4. Kunkin produktion jälkeen on esitetty, miten kyseisestä produktiosta muodostetaan rakennepuu; näissä kaavoissa käytetään ilmaisua $ i tarkoittamaan produktion oikean puolen i:nnen symbolin rakennepuuta. Esimerkistä näkee hyvin, kuinka konkreetti syntaksi on lähinnä sotkettu versio abstraktista syntaksista. Sotkun syynä on se, että abstrakti syntaksi on lähes aina moniselitteinen merkkijonokielioppina. Konkreetin syntaksin tärkein tehtävä onkin poistaa tuo moniselitteisyys. Usein konkreetti syntaksi tuo lisäksi ohjelman kirjoittamista helpottavia kielioppimakeisia (engl. syntactic sugar); AL- KEIS-suorassa ei näitä ole. Merkittävin muutos on nähtävissä lausekkeiden kohdalla. Konkreetissa kieliopissa lausekeproduktiot on purettu usean apuvälikesymbolin avulla osiin. Tämän tarkoituksena on ilmaista presedenssi- ja assosiatiivisuussäännöt suoraan kieliopissa niin, että niitä ei tarvitse erikseen määritellä. Viitteet [1] Addison-Wesley, Reading, MA. The Unicode Standard, Version 4.0, 2003. [2] ECMA. 7-Bit coded Character Set, 1991. Standard ECMA 6. WWW: http://www.ecma.ch/ ecma1/stand/ecma-006.htm. [3] Erich Gamma, Richard Helm, Ralph Johnson, ja John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison- Wesley, Boston, 1995. [4] IEEE. IEEE standard for binary floating-point arithmetic, 1985. ANSI/IEEE Std 754-1985. [5] International Organization for Standardization. Information lechnology Syntactic metalanguage Extended BNF, 1996. ISO/IEC 14977:1996(E). [6] International Organization for Standardization. Programming languages C, 1999. ISO/IEC 9899:1999. [7] Antti-Juhani Kaijanaho ja Benjamin Fallenstein. Totally different structural programming: Programming languages in ZigZag. WWW: http://www.mit.jyu.fi/antkaij/plinzz.pdf, elokuu 2001. An invited talk presented at the First International ZigZag Conference, part of ACM Hypertext Conference 2001 in $Arhus, Denmark on August 14, 2001. [8] Donald E. Knuth. Backus normal form vs. backus naur form. Communications of the ACM, 7(12), joulukuu 1962. Letter to the editor. [9] John C. Reynolds. Theories of Programming Languages. Cambridge University Press, Cambridge, 1998. [10] Christopher Strachey. Fundamental concepts in programming languages. Higher-Order and Symbolic Computation, 13:11 49, 2000. Perustuu Stracheyn vuonna 1967 pitämiin luentoihin. 13

program: var declarations begin statements end var $ 2 begin $ 4 end declarations: declaration $ 1 declarations ; declaration $ 1 ; $ 3 declaration: identifier : type $ 1 : $ 3 type: int int unsigned int unsigned int byte byte unsigned byte unsigned byte float float double double type [ unsigned-constant ] $ 1 [$ 3 ] statements: statement-list ; opt $ 1 statement-list: statement $ 1 statement-list ; statement $ 1 ; $ 3 statement: expression <- expression $ 1 :=$ 3 read expression read $ 2 write expression write $ 2 expression: multiplicative-expression $ 1 expression + multiplicative-expression $ 1 + $ 3 expression - multiplicative-expression $ 1 $ 3 multiplicative-expression: unary-expression $ 1 multiplicative-expression * unary-expression $ 1 $ 3 multiplicative-expression / unary-expression $ 1 / $ 3 multiplicative-expression % unary-expression $ 1 % $ 3 unary-expression: postfix-expression $ 1 - postfix-expression $ 2 postfix-expression: primary-expression $ 1 postfix-expression [ expression ] $ 2 [$ 4 ] primary-expression: identifier ι unsigned-constant ν N integral-constant ν Z floating-constant ν R ( expression ) $ 2 Kuva 4: ALKEIS-suoran syntaktinen konkreetti kielioppi 14