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.
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.
Lényegében ezek az E konténerosztályai.
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:
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.
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.
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.
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.
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álok egy kis bevezetője:
Egyszerü kvázi literálAz 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.
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
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,...) |
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],...]) |
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.
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]”.
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.
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.
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 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.
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 |
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.
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.
Szintaxis | Szemantika | Kifejtés |
expr "<<" expr | bal shift | l shiftLeft(r) |
expr ">>" expr | jobb shift | l shiftLeft(-r) |
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).
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) |
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.
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.
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.
A kétértelműségek feloldására.
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”.