A Theta programozási nyelv

Kifejezések

Egy kifejezés kiértékel egy objektumot a Theta univerzumban. Ezt az objektumok a kifejezés eredményének, vagy értékének nevezzük. A legegyszerűbb kifejezések literálok és azonosítók, amiknek közvetlenül a nevük az eredmény objektumuk. Összetettebb kifejezések általában eljáráshívások egymásba skatulyázásából épülnek fel. Egy ilyen kifejezés visszatérési értéke a legkülső hívás visszatérési értéke. A "primary" egy korlátozott fajtája a kifejezéseknek, amely értékadások baloldalán szerepel, valamint hívás utasításokban és store utasításokban.

Egy kifejezésnek van egy a compiler által ismert látszólagos típusa. Ez a típust az összetevők entitásának típusából származtatja, például a benne használt változók típusai, a benne meghivott eljárások típusa. A forditási időben történő típus ellenörzés garantálja, hogy egy kifejezés látszólagos típusa a kifejezés kiértékelésével kapott objektumnak az (szupertípusa) őstípusa.

A Theta-ban az általános aritmetikai és összehasonlitásos műveleteknek létezik infix és prefix változata is, és a tömb megszokott indexelése használatos (például, "a[i]"). Azonban a Theta-ban ezek a jelölések metódus hívások röviditései. Ezek hozzájárulnak ahhoz, hogy a felhasználó által definiált típusok esetén is (ha helyénvaló) használhatók a megszokott jelölések.

Literálok

A literálok az "int", "real", "char", "string", "bool", és "null" beépített típusok objektumaira mutatnak. Egy literál kifejezés típusa a literál által megnevezett objektum típusa. Néhány példa változókhoz hozzárendelt literálokra:

t: bool := true
f: bool := false
s: string := "A string"
c: char := 'c'
nl: char := '\n' % új sor
oct47: int := 8_47 % oktális szám
hex7e: int := 16_7e % hexadecimális egész szám
p: int := -5
pi: real := 3.141592
avog: real := 6.02e23
empty: null := nil

 

Objektumra mutató azonosítók

Objektumokra mutató azonositókat használhatunk kifejezésként. Ha egy ilyen azonositót kifejezésként használunk, az értéke az általa mutatott objektum lesz, és a kifejezés típusa az azonositó típusa.

Az azonositók következő fajtáit használhatjuk kifejezésekként:

  • Változók, amelyeket deklarálással vezettünk be, ahol meghatároztuk a típusukat, és ezáltal egy értéadás hatásaként egy objektumra mutat.
  • Egyenlőséggel definiált azonositókat használhatunk kifejezésként, és igy azonositoként is, amelyek beépített és felhasználó által definiált rutin definiciókat jelölnek. Ilyen azonosítók egy konkrét (konstans) objectumra mutatnak. Egy egyenlőséggel meghatározott azonosító típusa az általa mutatott objektum típusa.
  • A fenntartott szavakat használhatjuk egy metódus implementációján belül hogy mutasson egy objektum metódusára, és a típusa az osztálytípusa a befoglaló osztály típusának  Egy "make" utasítás "then" záradékán belül szintén használható az inicializált objektumra való hivatkozásra, és a típusa az inicializált objektum osztálytípusa.

 

Konstruktorok

A konstruktorok hívása egy speciális módszert ad a felhasználó kezébe arra, hogy létrehozzon és inicializáljon "record"-ot, "struct"-ot, "oneof" és "maybe" objektumokat. A konstruktorok a következő formával rendelkeznek:

<tagged_type_desig> "{" <field_inits> "}"

ahol

<field_inits> -> <field_init> ["," <field_init>]*
<field_init> -> <idn> := <expr>

A tagged_type_design a létrehozott objektum típusa. Ha egy "struct"-ot vagy egy "record"-ot konstruáltunk meg, akkor a mezőlistában található komponens neveknek a tagged_type_design-ban levő mezők nevének kell hogy legyen, habár a nevek tetszőleges sorrendben szerepelhetnek a konstruktorban. Egy mezőt inicializáló kifejezés típusának a mező deklarált típusának altípusa kell legyen. A kifejezések nem meghatározott sorrendben lesznek kiértékelve. Az új megkonstruált objektum komponenseinek visszetérési értékeinek formája az, ami a konstruktor kifejezés értékét képviseli. Például,

 
 
rt = record[x: int, c: char]
x: rt := rt{c := 'A', x := 7} % megengedett
x := rt{x := 7} % forditási idejű hiba -- nincs elég mező megadva
x := rt{x := 7, d: 'A'} % forditási idejű hiba -- hibás mezőnév

Ha egy "oneof" vagy "maybe" objetumot konstruálunk, akkor csak egy mezőnevét szabad megjelölni a típusnak, és a kifejezés típusa a mező deklarált típusának altípusa kell legyen. Az eredmény egy új "oneof" vagy "maybe" objektum a megadott taggal, aminek az értéke a kifejezés kiértékeléséből eredményeképpen létrejott objektum. Például,

ot = oneof[none: null, some: int]
x: ot := ot{none := nil} % x objektuma a none taggal rendelkezik és az értéke nil
x := ot{some: 7} % most x objektuma a some taggal rendelkezik és az értéke 7
x := ot{some: 3.1} % forditási idejű hiba -- a kifejezés rossz típussal rendelkezik

Egy "oneof" objektum szétbontása gyakran a tagcase utasítás segítségével történik.

 

Osztály konstruktorok

Egy osztályon és a modulján belül, az osztályhoz tartozó az új objektumokat az osztály egy speciális konstruktorával hozzuk létre és inicializáljuk. Ez a konstruktor megegyezik a "record" és "strukt" típusok konstruktorával, és a "make" utasításban található speciális konstruktorral is. A következő formával rendelkezik:

<type_designator> "{" [<ivar_inits>] "}"

A type_designator-nak egy osztály típus nevének kell lennie ; Ez adja a megkonstruált objektum típusát. Az összes osztály konstruktor kifejezés rendelkezik egy (lehetőleg üres) ivar_inits szakasszal. Ez a szakasz két részből áll:

        <ivar_inits> -> <field_inits> ";" <maker_invoc> | <field_inits> | <maker_invoc>
<field_inits> -> <field_init> ["," <field_init>]*
<field_init> -> <idn> ":=" <expr>
<maker_invoc> -> <idn> [<actual_parms>] "(" [<args>] ")"
<actual_parms> -> "[" <type_list> "]"
<type_list> -> <type_designator> ["," <type_designator>]*

A field_inits rész inicializálja a maker osztály példányváltozóit. Minden példányváltozóhoz lennie kell egy field_init-nek. (Ha az osztálynek nincsenek példányváltozói, a field_inits-t nem használjuk). A maker_invoc rész az öröklött példányváltozókat inicializálja. Ez a hívás jelen van, ha a type_designator által megnevezett osztálynak van egy ősosztálya. Az idn-nek egy (ősosztály által rendelkező) maker.

A konstruktor az osztály típus egy új objektumát hozza létre, kiértékeli a field_init kifejezédeket (nem meghatározott sorrendben), hozzárendeli a megkapott értékeket az új objektum példányváltozóihoz, és meghivja az őstípus "maker"-ét, ha a maker_invoc létezett. Ha ezen lépések mindegyike normális módon terminált (azaz nem váltottak ki kivételt), akkor az osztály konstrukciós kifejezés normális módon terminál az újonnan létrehozott objektummal, mint visszatérési értékkel.

Például, ha egy "C" osztály két példányváltozóval rendelkezik, "x" típusa "int", az "y" típusa "char", és nem rendelkeznek ősosztállyal, akkor

n: C := C {x := 3, y := 'A'}

létrehoz egy új "C" objektumot a megjelölt értékekkel a példányváltozóiban, és n rá mutat az objektumra.

 

Példány-változók kiválasztása

Egy osztályon és a modulján belül, az osztály objektumainak példányváltozóira a következő formával közvetlenül lehet hivatkozni:

[<expr> "."] <idn>

ahol a expr típusa az osztály típusa, és idn az osztály példányváltozójának neve. Ha az expr szót elhaszjuk, akkor alapértelmezésben őnmagát helyettesiti be; Tehát az osztály egy metódusán belül, a példányváltozókra közvetlenül a nevükkel.

Az osztály modulon kivül, egy osztály objektumának példányváltozóit nem lehet közvetlenül elérni, csak az objektum metódusainak hívásával.

 

Mező kiválasztása

A"record" és "struct" típusú objektumok mezőit a

<expr> "." <idn>

forma segítségével választhatjuk ki. Az expr kiértékelésének "record" vagy "struct" objektumnak kell lennie, és az idn-nek az objektum egyik mezőnevének kell lennie. Az eredmény az objektum által abban a mezőben tárolt érték.

Rutin példányosítás

Egy paraméterezett rutin példányositását használhatjuk kifejezésként. A formája:

<idn> <actual_parms>

Idn-nek egy rutin definicióra kell mutatnia. Az actual_parms-ban tárolt aktuális paramétereket fogja továbbitani, és a típusok megengedési ellenörzése ugyanaz, mint típus példányositás esetén. Egy ilyen kifejezés értéke egy rutin objektum.

Az eredmény rutin típusát a paraméterezett típus interfészéből származtatja a formális paraméterek összes előfordulásának a neki megfelelő aktuális paraméterek kicserélésével. Ily módon

p[T] (x: T) returns (T) signals (E)
ahol T rendelkezik lt(T ) returns (bool) metódussal

egy példányositása

p[int]

megengedett, és a következő típussal rendelkezik :

proc (int) returns (int) signals (E)

 

Eljáráshívások

Azok az procedura hívások használhatók kifejezésként, amelyeknek pontosan egy visszatérési értékük van. A kifejezés típusa a meghivott procedura visszatérési értékének típusa lesz.

 

Megkötés (binding)

Egy rutinból előallithatunk egy másik, kevesebb argumentummal rendelkező rutint néhány arumentumának megkötésével. A megkötés alapjában véve egy hiányos hívás: a megkötött argumentumokat aktuális paraméterekként kezeljük, és illesszük a formálisokhoz úgy, mint egy hívásnál. Egy csillag (*) egy speciális megjelölés, amivel egy olyan argumentumot jelölünk meg, amelyek nem akarunk megkötni.

A megkötés formája a következő:

bind "(" <expr> <bind_args> ")"

ahol

<bind_args> ->  ["," <bind_arg>]* ["," <varying_args>] 
<bind_arg> -> <expr> | "*"

A kifejezések nem meghatározott sorrendben lesznek kiértékelve. Az első kifejezés eredményének egy rutinnak kell lennie. A bind_arg kifejezések kiértékelésével kapott eredményeket a függvényhívással, megegyező módon kezeljük, kivéve ha egy bind_arg a csillag jel, akkor nem hajtunk végre hozzárandelést a megfelelő formális argumentumhoz. Egy megkötés megengedett, ha megfelelő számú argumentum szerepel, és a hozzárendelések megengedettek. A bind kifejezés eredménye egy új rutin. Ez a rutin csak azokon a helyeken rendelkezik argumentumokkal, amelyek formális argumentumai csillaggal lettek megjelölve. Az új rutin az eredetivel megegyező típusú lesz (procedura vagy iterátor), a vissztérési értékek (a yield-el szolgáltatott értékek is) típusa és száma is megegyezik, és a kivételek sem változtak.

Például, tekintsünk egy procedurát

p (x: int, y: char) returns (bool)

Ekkor a

q: proc (int) returns (bool) := bind(p, *, 'c')

megengedett, és az eredménye egy új "q" procedura, egy "'c'" megkötéssel az "y" formális argumentumára. A "q" procedurát egy sima "int" típusú paraméterrel kell meghivni az "x" formális paraméterre, amin nincs megkötés. Ezért a "q(5)" hívás viselkedése megegyezik a "p(5,'c')" híváséval. A megkötötéssel kapott rutint újra meg lehet kötni, például

r: proc ( ) returns (bool) := bind(q, 5)

egy új "r" nevű procedura, és az "r()" hívás viselkedése megegyezik a "p(5,'c')" hívással.

Megjegyezzük, hogy ha varying_args-t használunk, akkor a neki megfelelő formális argumentumot megkötjük egy új-konstruált sorozattal. Arra nincs lehetőség hogy meghosszabbitsuk a sorozatot egy elkövetkezendő megkötött kifejezésben vagy hívásban.

 

Metódus kiválasztás

A metódus kiválasztás segítségével az objektum egy metódusát válaszhatjuk ki, és egy rutin típusú értéket állit elő. A formája a következő:

[<expr> "."] <method_idn>

ahol

<method_idn -> ["^"] <idn> [<actual_parms>]

Az expr arra az objektumot jelöli, amelyikből ki szeretnénk választani a metódust. Ezt az osztályon belül elhagyhatjuk, elég használni a metódus nevét. A kiválasztás akkor és csak akkor megengedett, ha a kifejezés nyilvánvaló (statikus) típusa rendelkezik az idn metódussal. A metódust lehet paraméterezni is. Ebben az esetben az actual_parms-nak egy sikeres példányositást kell adnia. Az opcionális "^" kizárolag az alosztály definicióján belül használhatjuk arra, hogy megnevezzük a szülő osztály egy túlterhelt metódusát.

A metódus kiválasztás megköti az expr kifejezés kiszámitásával kapott objektumot, előállit egy rutin objektumot, amelynek a típusa a metódus típusával megegyező. Ha egy rutin fut, akkor önmagával hivatkozhat a megkötött objektumra. Ezért adódik a következő kódrészlet:

counter = type
inc(x: int)
end counter
 
c: counter := ... % valami inicializáció

a következő metódus kiválasztás

c.inc

megköti "c"-t a metóduson belül, előállitva egy "`proc` (x: int)" típusú rutin objektumot.

Minden metódus hívás fogalmilag egy metódus kiválasztás, amit egy egy rutin hívása követ. A implementációknak előre kell optimalizálnia azokat a gyakori eseteket, amelyekben közvetlen hívás történik. A programozóknak előre kell látniuk azt, hogy a

c.inc(1)

kódrészlet gyorsabban fog futni, mint a

m: proc(x:int) := c.inc
m(1)
 
 
kódrészlet.

Prefix és Infix operátorok

Bizonyos metódusok hívása esetén a rövidités érdekében a Theta megengedi a prefix és infix jelölét is. A jelölés megengedett, ha a megfelelő metódus egy procedura, és a hívása megengedett. A következő tábla a röviditéseket, és a velük ekvivalens terjedelmesebb alakot mutatja minden operátorra:

a+b

a.add(b)

~a

a.not()

a-b

a.sub(b)

a<b

a.lt(b)

a*b

a.mul(b)

a<=b

a.le(b)

a/b

a.div(b)

a=b

a.equal(b)

a//b

a.mod(b)

a~=b

~(a.equal(b))

a**b

a.power(b)

a>=b

a.ge(b)

-a

a.munis()

a>b

a.gt(b)

a||b

a.concat(b)

 

 

Ezek a jelöléseket a beépített típusok széles körében használhatjuk, és a felhasználó által definiált típusok esetén is jól használhatók.

Használati megjegyzések:

Ha ezeket a metódusokat felhasználó által definiált típusok esetén használjuk, akkor mellékhatás mentesnek kell lennie, és nagyjából hasonló dolgot kell jelentenie, mint amit a beépített típusok esetén jelent. Például, az összehasonlitó metódusok csak azon típusok esetén lehet használni, amelyeken értelmezve van egy parciális vagy teljes rendezés.

 

Elemkiválasztás (fetch)

Ez a speciális forma egy "array", "vector", "sequence" típusú objektum, vagy egy 'fetch' nevű metódussal rendelkező absztrakt objektum elemének kiválasztására szolgál:

<expr0> "[" <expr1> "]"

Ez a forma a fetch metódus hívásának röviditése, és ekvivalens a következővel:

<expr0> "." "fetch" "(" <expr1> ")"

A kifejezés akkor megengedett, ha a neki megfelelő hívás megengedett. Másszóval, az expr0 típusnak rendelkeznie kell egy "fetch" nevű metódussal (procedurával), amelynek egy sima argumentuman van, melynek típusa az expr1 őstípusa. Például, ha "a" egy egész számokból álló tömb, akkor "a[27]" ekvivalens a "a.fetch(27)" hívással.

Használati megjegyzések:

Felhasználó által definiált típusok esetén a fetch használatát korlátozni kell a tömb jellegűen működő típusokra. Az ilyen típusú objektumok objektumok indexelt gyűjteményét tartalmazzák. Például, létrehozhatunk egy jelentéssel biró hozzárendeléses "map" típust, amelyet elláthatjuk egy "fetch" metódussal, ami egy sztring kulccsal társitott értékét határozza meg. Egy "fetch" metódusnak nem lehet mellékhatása. A tömb jellegűen működő típusokra hasonlóan definiálhatunk "store" operátort is.

 

& és |

Két speciális ``short-circuit'' bináris Boolean (logikai) operátor léterzik, "&" és "|".

expr1 & expr2

az expr1 és expr2 logikai '"és"-elése', kivéve, hogy expr1 biztosan előbb lesz kiértékelve, mint expr2 bármely része. Ha expr1 hamis, expr2 nem lesz kiértékelve. Hasonlóan, ha "|" a logikai vagy művelet, kivéve hogy expr2 nem lesz kiértékelve, ha expr1 értéke igaz. Mind az "&" és "|" műveletre igaz, hogy mindkét kifejezésnek bool típusúnak kell lennie, és az eredmény is bool típusú.

A feltételes kifejezés-kiszámitás miatt a "&" és "|" használata nem ekvivalens a normális hívásokkal.

 

Precedencia és asszociativitás

Ha egy kifejezést nem tesszük teljesen zárójelek közé, a részkifejezések sajátos illeszkedése általában nehezen értelmezhető. A követkető elsőbbségi relációk és asszociativitási szabályok arra használhatók, hogy elemezzük az ilyen nehezen értelmezhető helyzeteket. A táblázat a magasabb precedenciával rendelkező operátorokat előbb mutatja mint az alacsonyabb precedenciával rendelkezőket:

. [ ] ( )
~ - (unary minus)
**
// * /
|| + - (binary minus)
< <= = ~= >= >
&
|

A bináris operátorok mind balról asszociativak, kivéve a "**" exponenciális operátort. Az exponenciális operátor jobbról asszociativ. Azaz "a+b+c" zárólejezhető mint "(a+b)+c". "a**b**c" zárójelezhető mint "a**(b**c)". "a[10].b" zárójelezhető mint "(a[10]).b". "a.b[10]" zárójelezhető mint "(a.b)[10]".

Konstans kifejezések

A konstans kifejezések egy szűk fajtája a kifejezéseknek, amelyeket egyenlőségekben lehet használni. Forditási időben lesznek kiértékelve, és immutable, beépített objektumot állitanak elő. Tartalmazhatnak metódushívásokat, de csak olyan metódusokat, amelyek foritási időben ismertek (beépített immutable objektumok). Az összes tübbi hívásnak bizonyos beépített mellékhatás nélküli rutinnak kell lennie. A következő formák lehetségesek:

  • literálok;
  • "struct", "oneof", and "maybe" típus konstruktorai, amelyek minden mezőhöz konstans kifejezéseket rendelnek hozzá;
  • új sorozatokat létrehozó beépített rutinhívások, amelyek az összes argumentumhoz konstans kifejezéseket rendelnek;
  • egyenlőséggel definiált azonositók;
  • azonositók, amelyek beépített vagy felhasználó által definiált egyedülálló rutinokat jelölnek;
  • beépített vagy felhasználó által definiált paraméterezett egyedülálló rutinok példányositása;
  • konstans kifejezésekre mutató objektumok metódusainak kiválasztása;
  • konstans kifejezésekre mutató objektumok metódusainak hívása, amelynek aktuális argumentumai szintés konstans kifejezések;

Egy konstans kifejezés kiértékelése normális módon kell hogy befejeződjön, különben futási hiba lép fel.

 

Primaries

A primary a kifejezések egy korlátozott fajája, amit egy kifejezés baloldalán, egy hívó utasításban vagy egy store utasításban használhatunk. A "primary" szabályok szintaxisa elutasítja az olyan kifejezéseket, amelyek ebben a helyzetben kétértelműek, ugyanis azok a kifejezések, amelyek egy bal zárójellel kezdődnek, és amelyek infix és prefix operátorokat használnak a legfelső szinten.

A "primary"-k a következő módon vannak definiálva:

<primary> -> <simple_expr>
| <primary> "." <idn>
| <primary> "." <method_idn>
| <simple_invoc>
| <primary> "[" <expr> "]"
 
<simple_expr> -> <literal>
| <idn> [<actual_parms>]
| self
| <method_idn>
| <tagged_type_desig> "{" <field_inits> "}"
| <type_designator> "{" [<ivar_inits>] "}"
| bind "(" <expr> <bind_args> ")"
 
<simple_invoc> -> <primary> "(" [ <args> ] ")"

Itt található pár példa:

(a[x]) % nem megengedett -- "(" bal zárójellel kezdődik
x + y % nem megengedett -- infix operátor van a legfelső szinten
a[x+y] % megengedett (ahol a: array[int] and x, y: int)
p(x) % megengedett (ahol p: proc(int) and x: int)