Röda on uusi ohjelmointikieleni! Se perustuu syntaksiltaan ja ominaisuuksiltaan sh-skripteihin.
Röda vaati toimiakseen Nept-kirjaston.
Neptin ja Rödan kääntäminen Gradlella:
$ git clone --recursive https://github.com/fergusq/roda.git
$ cd roda
roda $ gradle fatJar
Rödan mukana tulee interaktiivinen tulkki, jota voi käyttää valitsimella -i
.
$ java -jar röda.jar -i
Röda-funktiolla on parametrit, sekä sisään- ja ulostulo, jotka ovat jonoja. Funktio ei varsinaisesti
"palauta" mitään arvoa, vaan funktion paluuarvo voidaan ajatella listana kaikista sen ulostulovirran
arvoista. Funktio voi lukea sisääntuloaan pull
-komennolla ja työntää ulostuloon arvoja push
-komennolla.
main
-funktion sisään- ja ulostulo on kytketty standardisyötteeseen ja -tulosteeseen.
Alla on yksinkertainen duplicate-funktio, joka lukee yhden arvon ja palauttaa kaksi arvoa. Koska sitä
kutsutaan main
-funktiosta, lukee se syötettä suoraan standardisyötteestä.
duplicate {
pull value
push value
push value
}
main {
push "Syötä tekstiä: "
duplicate
}
Funktioita voi putkittaa |
-operaattorilla, jolloin niiden sisään- ja ulostulovirrat kytketään toisiinsa.
Seuraava ohjelma tulostaa kahtena tiedoston "tieto.txt" ensimmäisen rivin:
readLines "tieto.txt" | duplicate
Jos kaikki rivit haluttaisiin kahdentaa, olisi tehtävä uusi versio duplicate-funktiosta, joka lukee kaiken mahdollisen syötteen:
duplicate {
for value do
push value
push value
done
}
for
-silmukka lukee sisääntulovirrasta arvoja, kunnes niitä ei enää ole.
Joissakin kohdissa on oltava rivinvaihto.
- Lauseet erotellaan toisistaan rivinvaihdoilla.
- Nimettömän funktion parametrilistan jälkeen on oltava rivinvaihto.
Rivinvaihtojen tilalla voi käyttää ;
-merkkiä ja toisinpäin.
Röda käyttää C-tyylisiä /* ... */
-kommentteja.
Röda-ohjelma on joukko määritelmiä, usein funktioita:
funktio1 parametrit {
}
funktio2 parametrit {
}
Funktiolla voi ottaa vaihtelevan määrän argumentteja, jolloin viimeiselle parametrille pitää antaa
...
-määrite:
duplicate_files files... {
for file in files do
readLines file | duplicate
done
}
Funktio voi ottaa myös muuttujaviittauksen, jolloin ko. parametrille pitää antaa &
-määrite. Seuraava funktio
lukee kaksi arvoa, tekee niistä listan ja asettaa sen muuttujaan.
pull_twice &variable {
pull value1
pull value2
variable = [value1, value2]
}
Parametrille voi määritellä tyypin, joka tarkistetaan aina funktiota kutsuttaessa. Viittausparametreille ei voi kuitenkaan vielä määritellä tyyppiä.
kappale(sisältö : string) {
push "<p>"..sisältö.."</p>"
}
Funktion viimeisille parametreille voi antaa oletusarvon, jota käytetään, jos parametria vastaavaa argumenttia ei ole annettu. Oletusarvoparametreja vastaavat argumentit täytyy antaa nimettyinä argumentteina.
korostus sisältö, väri="#ff0000" {
push "<span style='color:"..väri..";'>"..sisältö.."</span>"
}
Oletusarvoparametreja voi määritellä myös ...
-merkkien jälkeen.
Muuttujaparametrien lisäksi funktiolla voi olla tyyppiparametreja, joille pitää antaa funktiokutsussa arvot muiden parametrien tapaan:
init_list<<T>> &variable {
variable := new list<<T>>
}
Ohjelman ylätasolla voi funktioiden lisäksi esiintyä tietueitamäärityksiä. Tietueet ovat tapa säilöä useita arvoja yhteen olioon. Ne ovat vahvasti tyypitettyjä ja siksi turvallisempia kuin tyypittämättömät listat ja hajautuskartat.
Tietuemääritys koostuu nimestä ja joukosta kenttiä:
record Perhe {
nimi : string
osoite : string
jäsenet : list
}
Kun tietue luodaan, sen kentät ovat oletuksena määrittelemättömiä. Niihin on asetettava arvo ennen, kuin niitä voi kunnolla käyttää.
perhe := new Perhe
perhe.nimi = "Harakka"
perhe.osoite = "Lumipolku 41 A 7"
perhe.jäsenet = ["Miete", "Joona", "Linn"]
Jos haluaa, osalle kentistä voi antaa oletusarvoja. Oletusarvolausekkeet suoritetaan uudestaan aina, kun uusi tietue luodaan. Niiden sisään- ja ulostulovirrat ovat kiinni ja niiden näkyvyysalue on sama kuin ylätasolla.
record Perhe {
nimi : string
osoite : string
jäsenet : list = []
}
Tietueilla voi olla tyyppiparametreja, joiden avulla tietyn kentän tyypin voi päättää oliota luodessa:
record LinkattuLista<<T>> {
arvo : T
linkki : LinkattuLista<<T>>
}
Funktioiden sisällä on lauseita, jotka koostuvat yhdestä tai useammasta putkitetusta komennosta. Putki yhdistää komentojen ulos- ja sisääntulot toisiinsa. Lauseen ensimmäisen ja viimeisen komennon sisään- ja ulostulo on kytketty isäntäfunktion sisään- ja ulostuloon.
komento1 argumentit | komento2 argumentit | komento3 argumentit
Komento voi olla joko funktiokutsu, muuttujakomento tai ohjausrakenne.
Funktiokutsu koostuu funktiosta ja argumenteista, jotka erotellaan pilkuilla. Myös muitakin arvoja kuin funktioita voidaan kutsua ikään, kuin ne olisivat funktioita. Nämä erityistapaukset on alempana.
Funktion argumentteina mahdollisesti olevat funktiokutsut on kytketty isäntäfunktion virtaan, eikä putkeen. Vain kutsuttava funktio putkittuu.
Kutsuttava funktio saa näkyvyysalueekseen sen näkyvyysalueen, jossa se on määritelty.
Argumentit annetaan funktiolle siinä järjestyksessä, missä ne ovat kutsussa. Jos funktion viimeisessä
parametrissa on valitsin ...
, asetetaan kaikki yli menevät argumentit siihen listana. Lista voi olla
myös tyhjä.
tulosta_perheenjäsenet sukunimi, etunimet... {
for etunimi in etunimet do
push etunimi, " ", sukunimi, "\n"
done
}
main {
tulosta_perheenjäsenet "Luoto", "Einari", "Ville", "Jenni"
}
Jos argumentin edessä on tähti *
, oletetaan, että se on lista. Tällöin listan alkiot annetaan
argumentteina funktiolle, eikä itse listaa.
väli := [1, 10]
seq *väli /* sama kuin seq 1, 10 */
Tätä ominaisuutta on mahdollista käyttää yhdessä ...
-määrittimen kanssa,
jos halutaan antaa arvot olemassa olevasta listasta.
sisarukset := ["Joonas", "Amelie"]
tulosta_perheenjäsenet "Mikkola", *sisarukset
Jos funktion joillakin parametreilla on oletusarvo, pitää näitä parametreja vastaavat argumentit nimetä.
korosta("kissa", väri="#2388ff")
Jos funktiolle on määritelty tyyppiparametreja, sille on annettava kutsun yhteydessä vastaava määrä tyyppiargumentteja:
init_list<<string>> sisarukset
sisarukset += "Joonas"
sisarukset += "Amelie"
Listan "kutsuminen" työntää kaikki listan alkiot ulostulovirtaan:
["rivi1\n", "rivi2\n", "rivi3\n"] | writeStrings tiedosto
Listan kutsumiseen perustuu [
- ja ]
-merkkien käyttö ehtolauseissa.
Uuden muuttujan voi luoda operaattorilla :=
:
tiedosto := "tieto.txt"
ikä := 73
tytöt := ["Annamari", "Reetta", "Vilma"]
Muuttujalle voi asettaa uuden arvon operaattorilla =
:
ikä = 74
tytöt[1] = "Liisa"
tytöt[2:4] = ["Kaisa", "Elina"]
Listaan voi lisätä arvon operaattorilla +=
:
tytöt += "Maija"
Merkkijonon perään voi lisätä tekstiä operaattorilla .=
:
nimi := etunimi
nimi .= " "..sukunimi
Lukua voi kasvattaa tai vähentää operaattoreilla ++
ja --
:
ikä ++
voimat --
Muuttujan voi tuhota käyttämällä komentoa undefine
.
Normaalisti muuttujia ei kuitenkaan tarvitse tuhota erikseen.
undefine nimi
Muuttujan tuhoaminen ei poista muuttujaa varmasti, sillä se saattaa olla määritelty jollakin toisella ohjelman tasolla.
nimi := "Lissu"
{
nimi := "Emilia"
push nimi, "\n" /* tulostaa Emilian */
undefine nimi
push nimi, "\n" /* tulostaa Lissun */
}
push nimi /* tulostaa Lissun */
Muuttujan voi tuhota kokonaan käyttäen silmukkaa ja ?
-operaattoria, joka kertoo, onko muuttuja olemassa.
while nimi? do
undefine nimi
done
Seuraavaksi vielä kaikki muuttujaoperaattorit taulukossa:
Operaattori | Esimerkki | Selitys |
---|---|---|
:= |
nimi := "Liisa" |
Luo uuden muuttujan nykyiseen muuttujaympäristöön. |
= |
nimi = "Maija" |
Ylikirjoittaa aiemmin luodun muuttujan arvon. |
? |
nimi? |
Työntää ulostulovirtaan totuusarvon true tai false riippuen siitä, onko muuttuja olemassa |
+= |
tytöt += "Nea" |
Lisää listaan elementin. |
.= |
tytöt .= ["Annabella", "Linn"] |
Yhdistää listaan toisen listan. |
.= |
nimi .= sukunimi |
Lisää tekstin merkkijonon loppuun. |
~= |
nimi ~= "ae", "ä" |
Tekee annetut korvaukset merkkijonoon, toimii kuten funktio replace . |
+= , -= , *= , /= |
pisteet *= 2 |
Suorittaa laskutoimituksen lukumuuttujalla. |
++ , -- |
varallisuus -- |
Kasvattaa tai vähentää lukumuuttujan arvoa. |
del |
del tytöt[2:4] |
Poistaa listasta alkion tai alkioita. |
Ohjausrakenteita ovat if
, unless
, while
, until
, for
, break
, continue
, try
ja return
.
if
, unless
, while
ja until
suorittavat annetun lauseen ja olettavat sen palauttavan joko arvon true
tai arvon false
.
Muut arvot tulkitaan aina samoin kuin true
. Jos lause palauttaa useita arvoja, pitää niiden kaikkien olla true
, jotta ehto toteutuisi.
Sisäänrakennetuista funktioista vain true
, false
, test
, random
ja file
(ks. alempana) palauttavat totuusarvon.
if [ ikä < 18 ] do
push "Olet liian nuori!\n"
done
while [ not ( vastaus =~ "kyllä|ei" ) ] do
push "Vastaa kyllä tai ei: "
pull vastaus
done
for
käy läpi annetun listan kaikki arvot:
tytöt := [["Annamari", 1996], ["Reetta", 1992], ["Vilma", 1999]]
for tyttö in tytöt do
push "Hänen nimensä on "..tyttö[0].." ja hän on syntynyt vuonna "..tyttö[1].."\n"
done
Jos for
ille ei anna listaa, lukee se arvoja syötteestä:
["Isabella", "Meeri", "Taina"] | for tyttö do
push tyttö.." on paikalla.\n"
done
for if
-rakenteen avulla voi käydä läpi vain osan arvoista:
määrä := 0
summa := 0
for tyttö in tytöt if [ tyttö.ikä > 14 ]; do
määrä ++
summa += tyttö.pituus
done
push "Yli neljätoistavuotiaiden tyttöjen pituuksien keskiarvo: "..(summa/määrä).."\n"
if
ille, while
lle ja for
ille on olemassa myös ns. suffiksimuoto:
push tyttö.nimi.." on paikalla.\n" for tyttö in tytöt
push tyttö.nimi.." ei ole kiireinen.\n" for tyttö in tytöt if [ tyttö.kiire = 0 ]
hinta /= 2 if push alennus
tyttö = haeSeuraava() while tarkista tyttö
Suffiksit ovat laskujärjestyksessä korkeammalla kuin putket. Sulkuja {}
voi käyttää tämän kiertämiseksi:
haeViestit() | split(:s, "\\b") | { haeTytöt | push tyttö for tyttö if [ tyttö.nimi = sana ] } for sana | for tyttö do
push tyttö.." mainittiin keskustelussa.\n"
done
break
ia ja continue
a voi käyttää silmukasta poistumiseen tai vuoron yli hyppäämiseen.
try
suorittaa annetun komennon tai lohkon ja ohittaa hiljaisesti kaikki vastaan tulleet virheet.
while true do
try do
hae viestit
käsittele viestit
done
done
Suorituksen keskeyttänyt virhe asetetaan catch
-osion muuttujaan, mikäli se on määritelty.
try do
lähetäViesti
catch virhe
errprint "Viestiä ei voitu lähettää!"
errprint virhe.message
errprint kohta for kohta in virhe.stack
done
return
työntää sille annetut argumentit ulostulovirtaan ja lopettaa nykyisen funktion suorittamisen.
haeSyntymävuodellaYksiTyttö vuosi {
for tyttö in tytöt do
if [ tyttö[1] = vuosi ] do
return tyttö
done
done
}
Funktion argumentit (ja kutsuttava funktio itsekin) ovat lausekkeita. Lauseke voi olla joko muuttuja, luku, merkkijono, lista, komento tai nimetön funktio. Lisäksi Rödassa on muutama operaattori merkkijonojen ja listojen käsittelemiseen.
Kaikki funktiot hyväksyvät lukujen tilalla merkkijonoja (joiden toki pitää sisältää vain lukuja) ja merkkijonojen tilalla lukuja. Optimointisyistä on kuitenkin aina hyvä käyttää lukuliteraaleja kaikkialla, missä mahdollista.
Merkkijonoja voi yhdistellä ..
-operaattorilla ja niiden pituuden voi saada #
-operaattorilla.
nimi := etunimi.." "..sukunimi
push "Nimesi pituus on ", #nimi, "\n"
Listaliteraali on joukko hakasulkeiden ympäröimiä arvoja, jotka erotellaan pilkuilla.
Listasta voi hakea yksittäisiä alkioita []
-operaattorilla. Lisäksi osalistoja voi luota [:]
-operaattorilla.
Kuten merkkijonoillakin, #
palauttaa listan koon.
Kaikki listan alkiot voi yhdistää merkkijonoksi &
-operaattorilla, jonka toinen operandi on merkkijono, joka
pistetään alkioiden väleihin.
tytöt := ["Annamari", "Reetta", "Vilma", "Susanna"]
push "Tyttöjä on ", #tytöt, " kpl.\n"
push "Ensimmäinen tyttö on ", tytöt[0], " ja viimeinen ", tytöt[-1], ". "
push "Välissä ovat ", tytöt[1:-1]&" ja ", ".\n"
Jos listaan yhdistää merkkijonon, yhdistetään se kaikkiin listan alkioihin:
sukunimi := "Kivinen"
sisarukset := ["Maija", "Ilmari"]
kokonimet := sisarukset.." "..sukunimi
push "Sisarusten koko nimet ovat ", kokonimet&" ja ", ".\n"
Listan alkioille voi määritellä tyypin, jos se luodaan new
-avainsanan avulla:
tytöt := new list<<string>>
tytöt .= ["Eveliina", "Lilja", "Nea"]
Jos listaan yrittäisi laittaa joitain muita olioita kuin merkkijonoja, antaisi koodi suorituksenaikaisen virheen.
Uuden kartan voi luoda samaan tapaan kuten tietueolion. Kuten listoille, myös tauluille voi määritellä erikseen alkion tyypin. Tätä ei kuitenkaan ole pakko tehdä.
iät := new map<<number>>
iät["Maija"] = 13
iät["Ilmari"] = 19
?
-operaattorilla voi tarkastaa, onko kartassa tietty alkio:
unless [ iät["Maija"]? ] do
push "Maijan ikää ei löydy!\n"
done
Operaattorit taulukossa (tunniste tarkoittaa joko lukua tai merkkijonoa riippuen siitä, onko kyseessä lista vai kartta):
Operaattori | Selitys | Ottaa | Palauttaa |
---|---|---|---|
.. |
Yhdistää merkkijonoja | 2 arvoa, merkkijonoja tai listoja | Merkkijonon tai listan |
& |
Yhdistää listan alkiot merkkijonoksi | Listan ja merkkijonon | Merkkijonon |
# |
Palauttaa arvon pituuden | Listan, kartan tai merkkijonon | Kokonaisluvun |
[] |
Palauttaa listan alkion | Listan tai kartan ja tunnisteen | Alkion |
[:] |
Palauttaa listan osalistan | Listan tai merkkijonon ja nollasta kahteen kokonaislukua | Listan tai merkkijonon |
[]? |
Kertoo, onko alkio olemassa | Listan tai kartan ja tunnisteen | Totuusarvon |
in |
Kertoo, onko listassa arvo | Minkä tahansa arvon | Totuusarvon |
is |
Kertoo, onko arvo tiettyä tyyppiä | Minkä tahansa arvon ja tyypin | Totuusarvon |
and |
Looginen JA | 2 totuusarvoa | Totuusarvon |
or |
Looginen TAI | 2 totuusarvoa | Totuusarvon |
xor |
Looginen JOKO-TAI | 2 totuusarvoa | Totuusarvon |
= |
Yhtäsuuruus | Mitä tahansa | Totuusarvon |
!= |
Erisuuruus | Mitä tahansa | Totuusarvon |
< |
Pienempi kuin | 2 lukua | Totuusarvon |
> |
Suurempi kuin | 2 lukua | Totuusarvon |
<= |
Pienempi tai yhtäsuuri kuin | 2 lukua | Totuusarvon |
>= |
Suurempi tai yhtäsuuri kuin | 2 lukua | Totuusarvon |
b_and |
Bittitason JA | 2 kokonaislukua | Kokonaisluvun |
b_or |
Bittitason TAI | 2 kokonaislukua | Kokonaisluvun |
b_xor |
Bittitason JOKO-TAI | 2 kokonaislukua | Kokonaisluvun |
b_shiftl |
Bittitason vasen siirto | 2 kokonaislukua | Kokonaisluvun |
b_shiftr |
Bittitason oikea siirto | 2 kokonaislukua | Kokonaisluvun |
b_shiftrr |
Bittitason etumerkitön oikea siirto | 2 kokonaislukua | Kokonaisluvun |
+ |
Yhteenlasku | 2 lukua | Luvun |
- |
Vähennyslasku | 2 lukua | Luvun |
* |
Kertolasku | 2 lukua | Luvun |
/ |
Jakolasku | 2 lukua | Liukuluvun |
// |
Pyöristävä jakolasku | 2 lukua | Kokonaisluvun |
% |
Jakojäännös | 2 kokonaislukua | Kokonaisluvun |
Unäärinen - |
Vastaluku | Luvun | Luvun |
Unäärinen b_not |
Bittitason EI | Kokonaisluvun | Kokonaisluvun |
Unäärinen not |
Looginen EI | Totuusarvon | Totuusarvon |
Laskujärjestys:
Sija | Operaattorit |
---|---|
1. | [] , [:] , []? , is |
2. | Unäärinen - , unäärinen ~ , unäärinen ! , unäärinen # |
3. | * , // , % |
4. | + , binäärinen - |
5. | b_and , b_or , b_xor , b_shiftl , b_shiftr , b_shiftrr |
6. | < , > , <= , >= , in |
7. | & |
8. | .. |
9. | = , != |
10. | and , or , xor |
Lauseke voi olla myös komento tai putkitettuja komentoja. Tälloin lausekkeen arvoksi tulee lista, joka on muodostettu kaikista viimeisen komennon ulostulon antamista arvoista.
Seuraava ohjelma tulostaa tiedoston rivinumeroiden kera.
rivit := [readLines(tiedosto)]
i := 1
for rivi in rivit do
push i, " ", rivi, "\n"
i ++
done
Jos on varmaa, että funktio antaa vain yhden arvon, voi hakasulkeet jättää pois. Tällöin arvoksi tulee listan ainoa arvo. Tämä heittää virheen, jos funktio palauttaa useampia arvoja (tai ei yhtään).
kaksi := parseInteger("2")
Nimetön funktio toimii kuten tavallinenkin funktio. Syntaksi on { |parametrit|; koodi }
.
Seuraavassa koodissa määritellään filter
-funktio, joka lukee arvoja ja palauttaa osan niistä.
filter condFunction {
for value do
if condFunction value do
push value
done
done
}
Funktiota käytetään antamalla sille nimetön funktio (tai tavallinenkin funktio käy), joka palauttaa
true
n tai false
n.
tytöt := [["Annamari", 1996], ["Reetta", 1992], ["Vilma", 1999]]
tytöt | filter { |tyttö|; [ tyttö[1] > 1995 ] } | for tyttö do
push tyttö[0], " on vielä nuori.\n"
done
Reflektion avulla on mahdollista saada metatietoa olioiden tyypeistä. Rödassa on kaksi mekanismia reflektion
käyttämiseen: reflect
-avainsana ja typeof
-avainsana.
reflect
palauttaa annetun tyypin metaluokan, joka on tyyppiä Type
.
record R {
a : string
b : number
}
...
(reflect R).fields /* palauttaa listan, jossa on kaksi field-oliota, yksi a:lle ja toinen b:lle */
typeof
toimii samoin, mutta ottaa tyyppinimen sijasta arvon ja palauttaa sen tyypin.
push ENV["PATH"] | split sep=":" | ls dir for dir | createGlobal komento, { |a...|; exec komento, *a } for komento
Etsii kaikki komentorivikomennot ja tekee jokaisesta funktion. Tämän jälkeen komentoja voi käyttää suoraan ilman
exec
iä.
Näiden lisäksi jotkin funktiot palauttavat omia tietueitaan, joiden määritykset listattu kyseisten funktioiden kohdalla.
record Error {
message : string
stack : list<<string>>
javastack : list<<string>>
}
record Type {
name : string
annotations : list
fields : list<<Field>>
newInstance : function
}
record Field {
name : string
annotations : list
type : Type
get : function
set : function
}
Sisäänrakennetut funktiot ja tyypit on dokumentoitu täällä.