A Theta egy utasítás-orientált nyelv. Két féle utasítás található a nyelvben: egyszerű utasítás és vezérlő utasítás. A vezérlő utasítások közül néhánynak egy van több kód-törzse van komponensenként. Az egyenlőségből álló törzset utasítások követnek:
<body> -> [<equate>]* [<statement>]*
Ezt követi egy egymásba-ágyazott utasítás struktúra.
A egyszerű utasítások egy adott számítást végeznek el. Állhatnak deklarációkból , kifejezésekből és hívásokból .
Egy hívó utasítás egy eljárást hív meg. Ennek a formája:
<primary> "(" [<args>] ")"
(A "primary" egy korlátozott formája a kifejezéseknek.) Egy hívó utasítás szemantikája megegyezik egy hívó kifejezés szemantikájával, kivéve hogy az eljárások hívásával tetszőleges számú visszatérési értéket kaphatunk, és tetszőleges ilyen értéket el is dobhatunk.
Ez a speciális utasítás a tömb típusú változók összetevőinek változtatására való. Ez az utasítás hasonlít szintaktikailag a kifejezésekre , de igazából egy hívás. A következő formával rendelkezik:
<primary> "[" <expr1> "]" ":=" <expr2>
(A "primary" egy korlátozott, meghatározott formája a kifejezéseknek.) Ez az alak teljesen egy gyorsírása a store metódus hívásának, és ekvivalens a következő hívási kifejezéssel:
<primary> "." "store" "(" <expr1> "," <expr2> ")"
A "primary" és a többi kifejezés kiszámítása nem meghatározott sorrendben történik.
Akkor megengedett egy alak, ha a neki megfelelő hívási utasítás megengedett, és következésképpen nem szorítja szűk keretek közé a tömbök használatát, de a felhasználó által definiált típusoknál jól használható lesz. A "primary"-tól visszatérő objektumnak rendelkeznie kell a "store" metódussal, aminek két argumentuma van, melynek típusa őstípusai az expr1 és expr2 típusoknak.
A return utasítás formája:
return ["(" <expr> ["," <expr>]* ")"]
A return utasítás befejezi az őt tartalmazó eljárás vagy iterátor végrehajtását. A return utasítást követő kifejezések számának meg kell egyeznie a rutin fejlécében megadottal, és a típusuk a megfelelő típus, vagy annak altípusa lehet. A kifejezések kiértékelésének sorrendje nem definiált.
A yield utasítás csak egy iterátoron belül használható. A formája a következő:
yield "(" <expr> ["," <expr>]* ")"
A yield utasítás átmenetileg felfüggeszti az iterátor végrehajtását, és átadja a vezérlést a hívó for utasításnak. A kifejezések számának meg kell egyeznie az iterátor fejlécében szereplő yields típus listával található elemek számával, és a típusának a fejléc lista megfelelő eleme altípusának kell lennie. Az értékeket kifejezések (nem meghatározott sorrendben történő) kiszámításával kapjuk meg, és átadódnak a for utasításnak úgy, hogy hozzárendelődnek a megfelelő ciklusváltozókhoz. Miután a for ciklus törzse lefutott, az iterátor végrehajtása folytatódik a yield utasítást követő utasítással.
Kivétel kiváltása a signal utasítással történik, ennek formája:
signal <name> ["(" <expr> ["," <expr>]* ")"]
ahol
<name> -> <idn>
A signal utasítás végrehajtása a kifejezések kiértékelésével kezdődik (ha vannak) nem definiált sorrendben. A rutin végrehajtása megszakad, és a végrehajtás a hívóban folytatódik.
A kivétel neve a rutin fejlécében felsoroltak egyike lehet, vagy a "failure". Ez utóbbi esetben pontosan egy sztring típusú kifejezést kell megadni, egyébként pedig az adott kivételnek megfelelő számú, és típusút. (A megadott típus lehet a várt típus altípusa is.)
Az if utasítás formája:
if <expr> then <body> [elseif <expr> then <body>]* [else <body>] end
A kifejezésnek bool típusúnak kell lennie. A kiértékelés befejeződik, ha true értéket kapunk. Az elseif alkalmazásával kényelmesen írhatunk többágú elágazásokat.
A while utasítás alakja:
while <expr> do <body> end
Az utasítás törzsét addig hajtja végre, míg a kifejezés igaz értéket (true) ad. A kifejezésnek "bool" típusúnak kell lennie.
A for utasítással iterátorokat használhatunk, és ez az egyetlen módja az iterátorok használatának. Az iterátor elemek egy sorozatát készíti el (ahol az elem egy vagy több objektumból álló csoport lehet), egyszerre egy elemet; a for utasítás törzse végrehajtódik minden elemre a sorozatban.
A for utasítás formája:
for <for_idns> in <invoc> do <body> end
ahol
<for_idns> -> <idn_list> | <decl> ["," <decl>]*
A ciklusváltozók lehetnek már korábban deklaráltak, vagy új változók, amelyek lokálisak a for utasításra. Fontos, hogy nem használhatunk egyszerre mindkét típusú változóból.
Az első ciklusváltozó felveszi az első objektumot az átadott elemből, a második (ha van) a másodikat, és így tovább. (Említettük, hogy az iterátor elemeket ad vissza, amik objektumok csoportjai lehetnek, esetleg ezek a csoportok csak egy elemet tartalmaznak.)
A for utasítás végrehajtása a következő. Először az iterátor meghívódik, és visszaad egy elemet, vagy terminál. Ha visszaadott egy elemet, a végrehajtása átmenetileg felfüggesztődik, az elem objektumai értékül adódnak a ciklusváltozóknak, és a ciklus törzse végrehajtódik. A ciklus következő végrehajtásánál folytatódik az iterátor végrehajtása azon a ponton, ahol felfüggesztődött. Ha az iterátor terminált, a for utasítás végrehajtása is befejeződik. Ha a for utasítás befejeződése szintén terminálja az iterátort is.
A következő példa készít egy "array[int]" tömböt, és feltölti számokkal 1-től 10-ig:
A példában az int objektum "to" iterátorát használtuk, amely egymást követő egész számokat ad vissza, az első az adott objektum, az utolsó pedig a megadott argumentum.
A break utasítás formája:
break
Hatására az őt tartalmazó legbelső for vagy while utasítás végrehajtása befejeződik. Fordítási hibát okoz, ha a break utasítást nem for vagy while utasítás törzsében használjuk.
A continue utasítás formája:
continue
Hatására az őt tartalmazó legbelső for vagy while utasítás törzsének végrehajtása befejeződik, és elkezdődik egy újabb iteráció végrehajtása (ha lehetséges). Fordítási hibát okoz, ha a continue utasítást nem for vagy while utasítás törzsében használjuk.
A tagcase utasítás a "oneof" vagy "maybe" objektumok szétbontására használható. Ennek formája:
tagcase <expr> <tag_arm> <tag_arm>* [others ":" <body>] end
ahol
<tag_arm> -> when <name> ["," <name>]* ["(" <idn> ":" <type_designator> ")"] ":" <body>
A kifejezés értéke egy "oneof" vagy "maybe" objektum lehet. Az objektum címkéjét (tag) összehasonlítjuk a megadottakkal. Mikor egyezést találunk, ha meg van adva egy deklaráció az adott ágban, akkor az felveszi a megfelelő komponensét az objektumnak. (Ez a változó lokális a törzsre nézve.) Ezután a megfelelő törzs kerül végrehajtásra. Ha nem találtunk egyezést, akkor az others ág kerül végrehajtásra.
Egy tagcase utasítás szintaktikailag helyes, ha megfelel a következő követelményeknek:
Egy példa:
Egy változónak vagy kifejezésnek a Theta-ban mindig van a fordító által ismert típusa, és a fordító ellenőrzi, hogy a változó által mutatott objektum aktuális típusa, vagy a kiszámolt kifejezés típusa mindig altípusa legyen ennek a típusnak. Sokszor hasznos lehet meghatározni egy objektum aktuális típusát. Ezt könnyen megtehetjük a typecase utasítás használatával. Ennek formája a következő:
typecase <expr> <type_arm> [<type_arm>]* [others ":" <body>] end
ahol
<type_arm> -> when <type_designator> [ ( <idn> ) ] ":" <body>
A kifejezés kiértékelődik, és az eredményül kapott objektum aktuális típusát használjuk, hogy kiválasszuk a megfelelő ágat (type_arm). A kiválasztás az ágak sorrendjében halad, és az első olyan ág választódik ki, melyben megadott típus altípusa az aktuális típusnak. Az objektum értékül adódik az adott ágban megadott változónak (ha ilyen létezik), és a megfelelő törzs végrehajtódik. Ez a változó lokális a törzsre nézve. Az others ág mindig illeszkedik, de nem ad további információt az objektum típusáról.
Egy helyes typecase utasítás megfelel a következő követelményeknek:
A következő példában a "set" és a "bag" típusok altípusai a "collection" típusnak, és a "stack" altípusa a "bag"-nek.
Az others ág választódik ki, ha az "x" aktuális típusa "set" (vagy más "collection" típus, amelyik nem altípusa "stack"-nek vagy "bag"-nek).
A begin utasítással hozhatunk létre blokkutasításokat. Segítségével több utasítást egyetlen egyszerű utasítássá csoportosíthatunk. Ennek formája:
begin <body> end
Mivel a Theta-ban a vezérlő utasításoknak van body része, ezért a begin utasítást leggyakrabban az except utasítással használjuk.
Ahhoz, hogy kivétel-kezelőket adjunk meg utasításokhoz, a hívóban specifikálni kell, hogy mi történjen, mikor egy kivétel váltódik ki az adott utasítás végrehajtása közben (Az except utasítással kezelhetők le az exit utasítás által dobott szignálok is.). Ennek formája:
<statement> except [<handler>]* [others ["(" <idn> ":" string ")"] ":" <body>] end
ahol:
<handler> -> when <name> ["," <name>]* ["(" <decl> ["," <decl>]* ")"] ":" <body>
Legyes S az az utasítás, amelyikhez a handler kapcsolva van, és legyen X az egész exept utasítás. Minden handler ág tartalmaz egy vagy több kivétel nevet, és egy törzset. A törzs végrehajtódik, ha valamelyik nevű kivétel kiváltódik az S végrehajtása közben. A felsorolt neveknek egyedinek kell lenniük. Az opcionális others ág használható azon kivételek kezelésére, melyek nincsenek explicit felsorolva egyetlen kivétel ágban sem. S bármilyen utasítás lehet, még másik except utasítás is.
Ha az S végrehajtása közben az E kivétel váltódik ki, a vezérlés azonnal a legbelső alkalmazható kezelőre kerül: a legbelső kezelő az E-hez az, amelyik tartalmazza a végrehajtás alatt álló utasítást. Ha a kivétel-kezelő végrehajtása befejeződött, a vezérlés azzal az utasítással folytatódik, mely az után az utasítás után áll, amelyhez a kezelő kapcsolva volt. Így ha a legbelső kezelő az S-hez volt kapcsolva, akkor a vezérlés az S utáni utasítással folytatódik. Ha az S végrehajtása anélkül fejeződik be, hogy szignál váltódna ki, akkor a csatlakoztatott kezelők nem hajtódnak végre.
Ha egy E kivétel kezelőjének nincs deklarációja, akkor ha ez választódik ki az E kezelésére, minden E-hez kapcsolt paraméter egyszerűen figyelmen kívül lesz hagyva. Így egy kezelőt használhatunk arra, hogy olyan kivételeket kezeljünk le, amelyek paraméter nélkül váltódtak ki, vagy a csatolt paraméter nem érdekes.
A handler with declarations is used to handle exceptions with the given names when the exception results are of interest. The declared variables, which are local to the handler, are assigned the exception results before the body is executed.
Deklarációval rendelkező kezelőt használhatunk, ha éredekelnek minket az adott nevű kivételhez csatolt paraméterek is. A deklarált változók lokálisak a kezelőre nézve, és értéküket a törzs lefutása előtt megkapják.
Mikor egy kivétel illesztődik egy kezelőhöz, csak a kivétel neve lesz figyelembe véve. Ha az E kivételhez tartozó kezelő tartalmaz deklarációkat, ennek le kell kezelnie minden lehetséges paramétert, mely az E-vel ki tud váltódni. Például, legyen egy utasításban két rutin hívás, az egyik szignatúrája "`signals`(foo(int))", a másiké "`signals` (foo(string,real))". Ebben az esetben lehetetlen deklarációval rendelkező kezelővel lekezelni a "foo" kivételt; nincs egyetlen olyan kezelő, mely mindkét esethez illeszthető. (A megoldás a deklaráció nélküli kezelő használata lehet, vagy az utasítást fel kell bontani két különböző except utasításra, így különböző kezelőket használhatunk.)
Az others ág opcionális, és a kezelő lista utolsó helyén állhat. Ez lekezel minden kivételt, mely nem lett lekezelve egyetlen más ágban sem. Ha egy változót deklarálunk, ennek "string" típusúnak kell lennie. A változó, mely lokális a kezelőre nézve, a kivétel kisbetűssé alakított nevét fogja tartalmazni; a kivétel minden visszatérési értéke figyelmen kívül marad.
A következő példában a p eljárás kiválthat e1, e2 és e3 kivételeket, ahol e1 és e2 paraméter nélküli, míg az e3-nak egy int paramétere van. A g eljárás kiválthat e1 (paraméter nélküli) és e4 (szintén paraméter nélküli) kivételeket.
Az első ág lekezeli az e1 kivételt, amelyik kiváltódhatott a q vagy a p hívásában is. Az e2 és e4 kivételek nem kezelődnek le a belső except utasításban, de lekezelődnek eggyel kintebb.
A "failure(string)" kivétel implicit hozzáadódik minden rutin interfészéhez, ezért explicit nem szabad felsorolni.
Ha egy rutin végrehajtása közben egy olyan kivétel keletkezik, amelyet egyetlen except utasítással sem kezelünk le a rutin törzsében, akkor a rutin terminál, és automatikusan kivált egy "failure" kivételt. Ha a nem kezelt kivétel nem a "failure", akkor a kiváltott "failure" kivétel sztring paraméterében a nem kezelt kivétel nevét fogja tartalmazni; egyébként pedig a paraméter a kezeletlen "failure" paramétere lesz. Ez szemantikailag azonos azzal, mint ha minden rutin törzse egy except utasítással lenne kiegészítve a következő formában:
begin
% routine_body
end
except when failure (s: string): signal failure(s)
others (s: string): signal failure ("unhandled exception: " || s)
end
A resignal utasítás egy rövidített formája a kivételkezelésnek:
<statement> resignal <name> ["," <name>]*
Minden felsorolt névnek egyedinek kell lennie, és mindegyik a rutin fejlécében felsoroltak egyike lehet, vagy a "failure". A resignal utasítás úgy viselkedik, mint egy except utasítás ugyanezekkel a nevekkel, ahol mindegyik kezelő kiváltja az adott kivételt ugyanazzal a paraméterekkel. Ezért ha egy resignal kifejezés specifikál egy kivételt a rutin fejlécével összhangban a következő formában: name(T1, ..., Tn), ehhez a kezelő a következő:
when name (x1: T1, ..., xn: Tn): signal name(x1, ..., xn)
A fordító ellenőriz minden kivételt, ami kiváltható azon utasítás által, amelyhez a resignal kapcsolódik. Ahogy feljebb említettük, hibás, ha egy kivétel kiváltódhat rossz argumentum számmal, vagy a paraméter típusa nem altípusa a kezelőben deklaráltnak.
Az exit utasítás a szignálokhoz hasonló vezérlés-átadást tesz lehetővé a helyi kezelőknek, anélkül, hogy a jelenlegi végrehajtás terminálna.
Az exit utasítás formája:
exit <name> ["(" <expr> ["," <expr>]* ")"]
Egy exit utasítás kivált egy lokális kivételt, amelynek le kell kezelődnie a befoglaló except utasítás valamelyik when ágában; a fordító hibát jelez, ha egy exit nincs kezelve, vagy egy resignal vagy others handler-rel lenne kezelve. Továbbá a kezelőnek minden paramétert: ha több paraméter lehetséges, az illeszkedő when ágnak deklarálnia kell minden olyan változót, mely szupertípusa minden, az adott exit utasításban szereplő kifejezés típusának.
A make utasítás segítségével adunk egy új létrehozott változónak kezdeti változónak. Csak egy "maker"-en belül használható; Egy osztály "make" rutinjának feladata hogy az objektumot inicializálja, kezdeti értéket adjon neki. A "maker" "make"-et használ, és nincs visszatérési értéke termináláskor. Ha a make utasítás normális módon terminál, akkor a "maker" normális módon terminál.
A make utasítás a következő formával rendelkezik:
make "{" [<ivar_inits>] "}" [then <body> end]
Minden "make" utasítás rendelkezik egy (lehetőleg üres) ivar_inits résszel körülvéve "kacskaringós összekapcsolásokkal". Ez a rész (amit az osztály konstruktor kifejezésével azonosítunk) két tagbó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>] ")"
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álynak 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 akkor van jelen, ha a marker osztálynak van egy ősosztálya. ; Az idn-nek egy (ősosztály által rendelkező) maker nevének kell hogy legyen.
A make utasítás rendelkezhet egy opcionális "then törzs end" záradékkal, ahol utólagos munkákra használhatunk, miután a példányváltozókhoz értékeket rendeltünk. Ezen záradékon belül a most létrehozott objektumokra a változó saját nevével hivatkozhatunk, és a most létrehozott objektumok példányváltozóit használhatjuk mind közvetlenül változóként, mind a metódus törzsében.
A make utasítás kiértékelése három lépésben történik. A későbbi lépések csak akkor hajthatók végre, ha a korábbi lépések normális módon befejeződtek (vagyis nem váltottak ki kivételt). A lépések a következők:
Ha mind a három lépés normálisan fejeződik be, akkor a make utasítás terminál, és a benne levő makerek normálisan terminálnak.
A make utasítás nem szerepelhet egy másik make utasítás törzsében.