Az E programozási nyelv

Fejlesztői környezet

Az E nyelv kifejezés-alapú programozási nyelv, szemben az olyan nyelvekkel, mint a Pascal vagy a C az E nyelvben nem léteznek állítások, csak kifejezések. Egy példával szemléltetjük: a C nyelv feltételes elágazása egy állítás, nincs visszatérési értéke. A kifejezés-alapú nyelvekben nem léteznek állítások, az állítás alapú nyelvekben léteznek állítások és kifejezések is.

A fentiek ellenére a literálokat, vezérlési szerkezeteket, az absztrakciós eszközöket (eljárások, függvények, objektumok), stb. a saját helyükön tárgyaljuk, alkalmazkodva az imperatív programnyelvek bemutatásának hagyományos menetéhez. A kifejezések tárgyalása során hivatkozunk ezekre a szokatlan „kifejezésekre” is.

Az E tervezőinek céljai közt szerepelt, hogy a nyelv nagy kifejezőerővel rendelkezzen, ugyanakkor biztosítani szerették volna, hogy egy kis lambda-nyelvet adjanak meg, amely megfelel az automatikus program elemzés céljainak. Ezt úgy érték el, hogy a nyelvet két szinten definiálták, egyrészt adott a Kernel-E, másrészt adott maga az E nyelv. A Kernel-E nyelv az E nyelv egy részhalmaza, az E nyelv és a Kernel-E nyelv különbsége (halmazelméleti értelemben véve) a Kernel-E nyelvén van definiálva.

Adattipusok

Skalárok

Az E nyelv négy skalár típust ismer, ezek sorban: az integer, a float64 (lebegopontos), a boolean (logikai), a char (karakteres) típus. A típusok értelmezése, műveletei, pontossága a java.math.BigInteger (tetszőlegesen nagy egész), java.lang.Double, java.lang.Boolean, java.lang.Character Java osztályokénak megfelelő.

A típusok számára hozzáférhető műveleteket a kifejezések közt tárgyaljuk. Paraméterátadáskor a skalár változókat is érték szerint adjuk át.

A float64 változók pozitív végtelen, negatív végtelen, NaN és 64 biten ábrázolt lebegőpontos értékeket vehetnek fel. A boolean változók a true és false értékeket vehetik fel. A char változók a Java char változóihoz hasonlóan viselkednek, azaz 16 biten ábrázolt Unicode karakterek, megadhatóak aposztrófok között, az escape ’\’ karakter használata is megengedett.

Kollekciók

Lényegében ezek az E konténerosztályai.

IO

Egy E fájlobjektumot a < file:név > kifejezéssel készíthetünk(, ami egy „elfojott” Java File Object –et ad). Ha meg tudod adni közvetlenül a nevet, akkor nincs szükséged arra, hogy kettőspontot használj (kivétel, ha space –t vagy érvénytelen url karaktert tartlmaz). Ha a nevet egy változó tartalmazza, vagy egy függvényhívással kapjuk meg, akkor egy üres helyet kell hagyni a kettőspont és a név megadása között. Ha a „file” szót egy egyszerű betűre cseréljük le, pl.: „c”, akkor a betű a meghajtót fogja jelölni, ami windows-os gépre utal:

#File objektumok amiket közvetlenül megadott fájlokból #kapunk: def file1 := < file:myFile.txt > def file2 := < file:/home/marcs/myFile.txt >

#Változó használata a fájlhoz. Figyeljünk az üres helyre a #„:” elott: def filePath := "c:\\docs\\myFile.txt" def file3 := <file: filePath>

#Egyszeru betu használata a Windows meghajtó megadásához: def file4 := <c:/docs/myFile.txt> def file5 := <c:\docs\myFile.txt>

Figyeljük meg, hogy a file4 példánál pert használtuk a vissza per helyett a könyvtárak elkülönítéséhez a Windows meghajtón. Az E –ben a perjel könyvtárelválasztásra szolgál függetlenül a meghajtón található rendszertol.

Amikor a filePath karaktersorozatot készítettük, dupla vissza pert használtunk, mivel az idézojelek között a vissza per elé egy escape, vissza per karaktert kell tenni.

A File objektumokat reprezentálhatjuk könyvtárakkal ugyan olyan jól, mint egyszeru fájlokkal. A könyvtáron belül a fájlokat elérhetjük indexeléssel.

# E syntax def dir := <file:/home/marcs/dataDir/> def file6 := dir["myFile.txt"]

A text fájlok és a könyvtárak is együttműködnek a „for” ciklussal. A könyvtáron keresztül a ciklus iterálja, eléri a fájlokat. A fájlokon keresztül pedig a fájl sorait. Minden egyes sor-objektum egy újsor (’\n’) karakterrel zárul.

Változók deklarálása, definiálása

Az olyan változókat, amelyek értékét létrehozásukkor meghatározzuk és később nem változtatjuk meg a „def” < változónév > „:=” < kezdőérték > kifejezéssel definiálhatjuk.

Az olyan változókat, amelyek értékét a definiálás után is megváltoztathatjuk a „def” helyett a „var” kulcsszóval vezetjük be.

Azonosság (Sameness)

Az azonosság fogalma az E-ben eltér a „hagyományos” objektumorientált programnyelvek fizikai és logikai egyenlőség fogalmaitól. Egyrészt az azonosság kevesebbet jelent, mint a fizikai egyenlőség (amikor az operátor mindkét oldalán ugyanaz a változó áll (ld. Java == operátor referenciatípusokra)), másrészt bizonyos értelemben többet jelent, mint a logikai egyenlőség „hagyományos” fogalma (amikor is az objektum, vagy az osztály maga definiálja az egyenlőség fogalmát). Az azonosság a programozási módszertanból ismert egyenlőség axióma értelmében az E által definiált tulajdonság. Bővebben, az E egyezőnek tekint két objektumot, ha azok a külvilág szempontjából megkülönböztethetetlenül viselkednek.

Literálok

Skalár literálok)

Egész literálok tetszőleges számjegyű egészek, amelyeket a tizes, nyolcas (ha „0”-val kezdődik), vagy tizenhatos (ha „0x”-szel kezdődik) számrendszer jegyeinek sorozatával adhatunk meg. A számjegyek közt használhatunk „_” karaktereket, amit a fordító figyelmen kívül hagy. Ez utóbbi tulajdonságtól eltekintve ez a Java által megkövetelt formátum is.

A lebegőpontos literálok is a Java írásmódjához hasonlóan adhatóak meg, csak itt is engedélyezett a „_” karakter használata mind a mantisszában, mind az exponensben.

A karakterliterálok megegyeznek a Java unicode literál karaktereivel. A karakterlánc-literálok szinaxisa megegyezik a Java unicode literál karakterláncainak szintaxisával.

Kvázi-literál

Kvázi-literálok egy kis bevezetője:

Egyszerü kvázi literál

Az alap kvázi-literál(elemző) egy szöveg manipuláló eszköz, ami képes eltávolítani szövegből adatokat, és készíteni új sztringeket. A legegyszerűbb formájában, a sztring készítés egy egyszerű és tiszta dolog:

  # E sample
  
  def a := 3  
  def answerString := `The value a is $a, and a times two is ${2 * a}.` 
  println(answerString) 
 

Itt most egy egyszerü kvázi-literált használtunk, ami egy output sztringet készített, a C-beli printf-hez hasonlóan. A kvázi literálokat aposztrófok közé írjuk. A dollár jellel jelezük egy érték leírásának a kezdetét, ahova majd be kell szúrni. Ha a dollár jelet egy változó neve követi, akkor a változó értékét használjuk. Ha egy nyitó kapcsos zárójel követi, akkor minden ami az bezáró zárójelig van, az egy kiszámítandó kifejezés része lesz, aminek az értékét szúrjuk be az adott helyre. Tehetünk $ jelet a kapcsos zárójelek közé is, ha ott szintén egy résszámítást akarunk végeztetni. A kvázi-literálokat több sorba is írhatjuk, abban az esetben, ha a kocsi-vissza része a struktúrának.

Egy mesterkéltebb használata a kvázi-literálnak a mintaillesztésre. Elemezünk egy mondatot:

  # E sample
  
  def line := "By the rude bridge that arched the flood" 
  if (line =~ `@word1
   @{word2} rude @remainder`) { 
		println(`text matches, word1 = $word1`) 
	} 
 

Az =~ bal oldalán lévő sztringet hasonlítjuk össze a jobb oldalon lévő kvázi literállal. Az értéke igaz, ha a sztring elemzése megfelelő a kvázi-literállal. A „@” azt jelenti, hogy a sztring ezen részének bármely szövege behelyettesíthető ide, és a „@” után lévő változó deklarációját is jelenti, aminek az értéke az a szöveg amivel megfeleltetjük. A word2 zárójelek között van, hogy ezáltal kiemeljük az utána következő „rude „ szót, ami úgy is tűnhetne, hogy a változó nevének része, a kiemelés nélkül.

Ebben a példában azt nézzük meg, hogy a szöveget hozzá tudjuk e illeszteni a kvázi literálhoz. Vagyis hogy a 3. szó (vagy egy korábbi a mintaszövegben) egyezik e a „rude” szóval. Azért lehet az 1., vagy a 2. is, mert a @ -os változónak lehet nullhosszú értéke is. Így a példánkban a változók értke a következő lett: word1==”By”, word2==”the”, és a remainder==”bridge that arched the flood”. Így a képernyőre a „text matches, word1 = By” került.

URI-literál

Szintaxis Pl Jelentés Kifejtés
URILiteral < file:/autoexec.bat > c:/autoexec.bat Szerezd meg a dokumentum
egy példányát az URI
protocol handler alapján
uriGetters["file"]["/autoexec.bat"]
file:c:/autoexec.bat

A láthatósági szabályok alapján látható uriGetters gyűjtemény megfelelő elemét híva meg (pl. a fenti példában a filekezeléssel foglalkozót), amelynek az a feladat, hogy az adott erőforrás elérését biztosítsa. Ha a protokol-azonosító (pl.: „file:”, „http:”) helyén a-z vagy A-Z áll, akkor azt egy meghajtó betűjeleként értelmezi és a „file:” protokolt választja.

Ennek az operátornak nagy a jelentősége, ugyanis elérhetővé teszi a Java összes erőforrását az „import:” protokolon keresztül, ráadásul a fájlok eléréséhez is szükség van erre az eszközre.

Részletesebben, lásd Interface a Java-hoz (Csatlakozás a Java-hoz) részt

Lista (List) kifejezés

Szintaxis Pl Jelentés Kifejtés
"[" "]" [] Üres kontans lista ListMaker()
"["expr,..."]" [1, 2, 4, 9] A megadott értékekből felépülő konstans lista ListMaker(expr,...)

Térkép (Map) kifejezés

Szintaxis Jelentés Kifejtés
[(k "=>" v),..."]" A megadott párokból felépülő konstans térkép
k a kulcs kifejezés, v az érték kifejezés
MapMaker([[k,v],...])

Kifejezések

A kifejezéseket precedenciasorrendben mutatjuk be, az erősebb kötésűekkel kezdve és a gyengébb kötésűek felé haladva.

Az alábbiakban bemutatjuk az E-ben megengedett kifejezéseket, ilyenkor, ha a táblázatok „Kifejtés” oszlopában a „kernel” szó szerepel, akkor az adott kifejezés a Kernel-E-nek is része. Ha pedig egy másik kifejezés szerepel, akkor a kifejezés a „Kifejtés” oszlopban szereplővel ekvivalens.

Szekcencia (asszociativitása érdektelen)

Definició és hozzárendelés (jobboldali asszociativ)

Szintaxis Szemantika
varName ":=" expr Hozzárendelés
expr get(...) ":=" expr l put(..., r); r
expr getName(...) := expr l setName(..., r); r
expr run(...) := expr l setRun(..., r); r
lval "|=" expr
lval "&=" expr
lval "^=" expr
lval "<<=" expr
lval "+=" expr
lval "-=" expr
lval "*=" expr
lval "/=" expr
lval "_/=" expr
lval "%=" expr
lval "%%=" expr
lval "**=" expr
Egyszerüsített hozzárendelés
lval ">>=" expr l <<= -r
"def" patt ":=" expr Változók definíciója mintaillesztéssel

A hozzárendelés, avagy értékadás, akár a Pascal-ban a ’:=’ karaktersorozattal történhet.

Az alakiság szempontjától eltekintve, minden más tulajdonságot tekintve az értékadás a C-re emlékeztet. Az értékadás maga is kifejezés, a baloldalon található balérték a jobboldali kifejezés értékét veszi fel, a kifejezés értéke szintén ez az érték lesz. Az értékadás operátornak, akár a C-ben léteznek egyszerűsített változatai arra az esetre, ha a jobboldalon álló bináris kifejezés első eleme az értékadás baloldali kifejezése lenne.

A balérték nem csak ez egyszerű változó lehet, a táblázat második blokkja az egyéb alternatívákat ábrázolja. Az egyes alternatívák jelentését a második oszlop magyarázza el. Pl.: a „flist(3,5) := subl” kifejezéssel ekvivalens a „flist run(3,5) := subl” kifejezés, ez pedig a fenti szabály alapján a „flist setRun(3,5, subl); subl” kifejezéssel egyezik meg. Ez is egy olyan eszköz, mely az E kifejezőerejét növeli.

A táblázat utolsó sora a változók definíciójának a lehetséges módját mutatja be. Az E itt is többet nyújt a „hagyományos” programnyelveknél.

A változódefiniálás is kifejezés, értéke a jobboldali kifejezés értéke.

A mintaillesztés fogalma arra utal, hogy egyszerre több változónak is értéket adhatunk, ez hasznos funkció, ha például egy függvény több értéket ad vissza. Pl.: „def [promise, resolver] := Ref.promise()”, ahol Ref.promise() egy olyan kételemű listát ad vissza, melynek elemei között kapcsolat áll fenn.

Lehetőség van rekurzív definiókra is, így az E-ben lehetőség van rekurzív, ciklikus adatstruktúrák definiálására egy sorban. Pl.: a „def a := [1, a, 3]” által létrehozott struktúra listák végtelen mélységű fája: „[1, [1, [1, ..., 3], 3], 3]”.

Feltételes vagy (asszociativitása érdektelen)

Szintaxis: a”||”b.

Szemantika: balról-jobbra értékeli ki a kifejezéseket. Ha egy igaz állításra talál, leáll és igaz értéket ad vissza, különben az utolsó kifejezés kiértékelése után hamis értéket ad vissza.

Feltételes és (asszociativitása érdektelen)

Szintaxis: a”&&”b.

Szemantika: balról-jobbra értékeli ki a kifejezéseket. Ha egy hamis állításra talál, leáll és hamis értéket ad vissza, különben az utolsó kifejezés kiértékelése után igaz értéket ad vissza.

Összehasonlítások és bitmuveletek (nem asszociativ)

Szintaxis Szemantika Kifejtés
expr "==" expr
expr "!=" expr
megegyeznek?
eltérnek?
E same(l, r)
!(l == r)
expr "&" expr
expr "|" expr
expr "^" expr
típusfüggő jelentés l and(r)
l or(r)
l xor(r)
expr "=~" patt
expr "!~" patt
egyezés
nem egyezés
kernel
!(l =~ r)

A „==” és „!=” operátorok két objektum azonosságát vizsgálják.

A következő három operátor az operandusok típusától függően mást és mást jelent. Egészekre (integer) pl. bitenkénti műveleteket, logikai értékekre (boolean) a megszokott műveleteket, ahol mind a bal-, mind a jobboldalt kiértékeljük, mielőtt a kifejezést magát kiértékelnénk, stb. Saját objektumainkra is megmondhatjuk, hogyan reagáljanak ezekre az operátorokra ha felüldefiniáljuk az „and”, „or”, „xor” metódusokat. (Ez az összes többi operátorra is vonatkozik.)

A következő két operátor a Perl-ből ismert mintaillesztés operátorai. A „=~” az illeszkedő mintát jezi, a „=~” az illeszkedés hiányát jelzi.

Részbenrendezés (nem asszociativ)

Szintaxis Szemantika Kifejtés
expr "<" expr kevesebb mint l compareTo(r) belowZero
expr "<=" expr kevesebb vagy egyenlő l compareTo(r) atMostZero
expr ">=" expr nagyobb vagy egyenlő l compareTo(r) atLeastZero
expr ">" expr nagyobb l compareTo(r) aboveZero
expr "<=>" expr akkora mint l compareTo(r) isZero

Mindegyik operátor működését a compareTo függvény szabályozza. A compareTo függvénnyel szemben a következő elvárásokat támasztjuk:

Az E beépített típusai közül számoson értelmezett valamiféle parciális rendezés, most csak az egész (integer) és valós (float) típusokat említjük, melyeken a hagyományos értelemben értelmezzük a részbenrendezést.

Különbség van az „a<=b” és „b >=a” kifejezések közt. Az első a-t a második b-t kérdezi meg, fontos tehát, hogy melyik objektumban bízunk meg jobban.

Intervallum (nem asszociativ)

Szintaxis Szemantika Kifejtés
expr ".." expr inclusive-inclusive (_ >= l) & (_ <= r)
expr "..!" expr inclusive-exclusive (_ >= l) & (_ < r)

Ezek a kifejezések egy úgynevezett tartományt (region) adnak vissza. Minden úgynevezett koordináta-tér (Coordinate Space) definiálja a saját tartományait. Jelenleg csak az IntegerRegion, RealRegion és CharRegion típusok támogatottak.

Shift (bal asszociativ)

Szintaxis Szemantika Kifejtés
expr "<<" expr bal shift l shiftLeft(r)
expr ">>" expr jobb shift l shiftLeft(-r)

Összeadás (bal asszociativ)

Szintaxis Szemantika Kifejtés
expr "+" expr összeadás, konkatenáció l add(r)
expr "-" expr kivonás, különbség l subtract(r)

Egészekre, valósakra és karakterekre a hagyományos értelemben használhatjuk őket. Összetett adattípusokra más, összetett viselkedést valósítanak meg (pl: „String”-ekre a „+” operátor összefűzést).

Szorzás (bal asszociativ)

Szintaxis Szemantika Kifejtés
expr "*" expr
expr "/" expr
expr "_/" expr
expr "%" expr
expr "%%" expr
szorzás
valós osztás
egész osztás
maradék
modulo
l multiply(r)
l approxDivide(r)
l floorDivide(r)
l remainder(r)
l modulo(r)
expr ** expr %% expr modular exponentiation l modPow(e, m)

Egyváltozós, prefix operátorok (extrém mód nem asszociativ)

Szintaxis Szemantika Kifejtés
"!" prim "~" prim "-" prim nem komplemens 0 - kifejezés r not r complement r negate
"&" varName változó "slot"-ja kernel kernel
protName ":" prim dinamikus uri kifejezés

A „!” operátor logikai, a „~” operátor egész, a „-” operátor egész és valós típusokra értelmezett. A „:” operátor a Uniform Resource Idnetifier-ek képzését segíti. A „&” operátor egy változóra alkalmazhatjuk, és a változót tartalmazó Slot-ra való hivatkozást ad vissza.

A fenti operátorokról azt mondjuk, hogy extrém mód nem asszociatív, mert a jobboldali kifejezések csak primitív kifejezések lehetnek.

Egyváltozós, postfix operátorok (bal asszociatív)

Szintaxis Szemantika Kifejtés
expr verb
expr "[" expr "]"
expr "." propName
expr "." propName"("expr,...")"
egyszerű hívás
indexelés
tulajdonság hozzáférés
idexelt tulajdonság hozzáférés
l verb()
l get(r)
l getPropName
l getPropName(expr,...)
expr "<-" verb "(" expr,... ")"
expr "<-" verb
expr "<-" "(" expr,... ")"
küldés
egyszerű küldés
függvény küldés
kernel
l <- verb()
l <- run(expr,...)
meta verb static reflection kernel

Ezeket a kifejezéseket a hívásokkal együtt tárgyaljuk.

Hívás (többnyire bal asszociatív)

Szintaxis Szemantika Kifejtés
postfix verb "(" expr,... ")" l "(" expr,... ")" call, do now függvény hívás kernel l run(expr,...)
meta verb "(" expr,... ")" static reflection kernel

Az E számos lehetőséget biztosít az eljárások hívására.

Egyrészt megkülönböztethetünk aszinkron hívást (ezt küldésnek („send”) nevezzük a továbbiakban) és szinkronizált hívást. Az első esetben a program nem várja meg, hogy az objektum végrehajtsa a kérést (ennek az elosztott, aszinkron rendszerekben ehet szerepe), a második esetben mielőtt a hívó objektum folytathatná a végrehajtást a két objektum (a hívó és a hívott) szinkronizál, és a hívott eleget tesz a hívásnak.

Másrészt mindkét forma esetén lehetőségünk van egyszerűsítésekre. Ilyen, hogy minden objektum „run” metódusát egyszerűbben hívhatjuk, hogy az objektumok változóihoz az „objektum.getVáltozó()”, „objektum.setVáltozó()” hívások helyett használhatjuk a megszokott „objektum.Változó” jelölést (sőt ez még indexelt tulajdonságokra is igaz).

A Kifejtés oszlopban láthatjuk, milyen Kernel-E kifejezésnek felelnek meg az egyes írásmódok.

Zárójeles kifejezés

A kétértelműségek feloldására.

Változónév

Szintaxis Pl. Jelentés Kifejtés
Azonosító foo A változó értéke kernel
"_" _ meta változók region-ok és twister-ek készítéséhez ForAllX

Az azonosítók szintaxisa azonos a Javáéval, eltekintve két változástól: „$”-t nem használhatunk, „_” nem szerepelhet az azonosítók elején.

A „_” használatára csak egy példát mutatunk: „def r := _ >= 3”.