A Cool programozási nyelv

Cool objektumok szintaktikája

Cool objektumok szintaktikája

Minden Cool érték egy objektum. Az objektumok tartalmaznak egy listát az attribútumaik nevéről, ami kissé hasonlít a C rekordjaihoz. Kiegészítésül minden objektum hozzá tartozik egy osztályhoz. A következő jelölést használjuk a Cool-ban az értékekhez:

                                                   v = X(a1 = l1, a2 = l2, …, an = ln)

A következőképpen kell kiolvasni: a v érték tagja az X osztálynak, ami az a1, …, an attribútumokat tartalmazza, amiknek a címei l1, …, ln. Minden attribútumnak van egy hozzárendelt címe, ami azt jelenti, hogy valamennyi hely fennt van tartva a memóriában minden attribútum számára.

A Cool alap objektumaihoz (Int, String, Bool) különleges jelölés tartozik. Az alap objektumoknak van osztályneve, de az attribútumaik nem olyanok, mint a normál osztályoké, mert nem módosíthatók. Ezért az alap objektumokat a következő jelöléssel írjuk le:

                                                                       Int(5)

                                                                   Bool(true)

                                                              String(4, „Cool”)

Az Int és a Bool esetében a jelentés egyértelmű. A String-ek két részből állnak: a hosszból és a tényleges tartalomból (ASCII karakterek sorozata).

Osztálydefiníciók

Tekintsünk egy példa Cool osztálydefiníciót, hogy a következőkben legyen mire hivatkozni:

 

class B is

  s : String <- ”Hello”;

  g (v:String) : Int is

y.concat(s)

  end;

  f (x:Int) : Int is

    x+1

  end;

end;

 

class A inherits B is

  a : Int;

  b : B <- new B;

f(x:Int) : Int is

    x+a

  end;

end;

 

Az class és implementation leképezések hozzá vannak rendelve az osztály definíciójához. A class leképezést használjuk, hogy elérjük az attribútumokat, azok típusait és az inicializátorokat; a példában:

class(A) = (s : String ß ”Hello”, a : Int ß 0, b : B ß new B)

Az A osztály információi tartalmaznak mindent, amit A a B-től örökölt, plusz a saját definícióit. Ha B is örökölt volna más attribútumokat, azok is megjelennének az A-ról gyűjtött információk között. Az attribútumok ugyanabban a sorrendben kerülnek listázásra, mint amilyen sorrendben örökölve lettek, és azután következik a forráskódbeli sorrend: a legtávolabbi őstől származó attribútumok szerepelnek először a deklarálásuk sorrendjében, azután a következő (a 2. legtávolabbi) őstől örökölt attribútumok, és így tovább. Erre a sorrendre támaszkodva dől el, hogy az új objektumok hogyan lesznek inicializálva.

Egy class leképezésnek az általános alakja:

class(X) = (a1 : T1 ß e1; …; an : Tn ß en)

Minden attribútum rendelkezik inicializáló kifejezéssel, még akkor is, ha a Cool program nem határoz meg ilyet egy attribútumhoz. Az alapértelmezett inicializációja egy változónak a típushoz tartozó alapértelmezés. Az Int alapértelmezése 0, a String-é „” és a Bool-é false, minden más típusé void. A T típus alapértelmezését DT jelöli.

Az implementation leképezés információkat ad egy osztály metódusairól. A fenti példában az A-hoz tartozó implementációk:

implementation(A, f) = (x, x + a)

implementation(A, g) = (y, y.concat(s))

Általában egy X osztály m metódusának az implementációja

implementation(X, m) = (x1, x2, …, xn, ebody)

megadja, hogy amikor az m metódust hívjuk az X osztályból, akkor annak formális paraméterei x1, …, xn, és a törzse az ebody kifejezés.

Működési szabályok

A Cool működési szabályainak szemantikáját le lehet írni a környezetekkel, készletekkel objektumokkal és osztálydefiníciókkal. A működési szemantika hasonló szabályokkal írható le, mint a típusellenőrzés. Az általános formája a szabályoknak:

so, S, E ├ e1 : v,S’

A szabály kiolvasása: a szövegkörnyezetben, ahol self egy so objektum, a készlet S, és a környezet E, az e1 kifejezés a v objektummá értékelődik ki, és az új készlet S’ lesz. A pontok a vízszintes vonal fölött egyéb kifejezéseket jelölnek e1 részkifejezéseiből.

A környezet és a készlet mellett a kiértékelési szövegkörnyezet tartalmaz még egy so self objektumot is. Ez pont az az objektum, amire a self kulcsszó hivatkozik (ha self szerepel a kifejezésben). Azonban a self-et nem helyezzük be sem a környezetbe, sem a készletbe, mivel a self nem egy változó – ennek nem lehet értéket adni. A szabály értelmében egy kifejezés kiértékelése egy új készletet eredményez. Az új készlet tartalmaz minden változást, ami a memóriában végbemegy az e1 kifejezés kiértékelésének mellékhatásai következtében.

Értékadás szabálya:

so, S1, E ├ e1 : v1,S2

E(Id) = l1

S3 = S2[v1/l1]

so, S1, E ├ Id ß e1 : v1,S3

Az értékadás először kiértékeli a jobb oldalon álló kifejezést, ami egy v1 értéket eredményez. Ezután ezt az értéket eltárolja a memóriában az Id azonosítóhoz tartozó címen.

Változó hivatkozás szabálya:

E(Id) = l

S(l) = v

so, S, E ├ Id : v, S

self hivatkozás szabálya:

 

so, S, E ├ self : so, S

true konstans:

 

so, S, E ├ true : Bool(true), S

false konstans:

 

so, S, E ├ false : Bool(false), S

Int konstans:

i egy integer konstans

so, S, E ├ i : Int(i), S

String konstans:

s egy string konstans

l = length(s)

so, S, E ├ s : String(l, s), S

A new kifejezés sokkal bonyolultabb, mint amire számítani lehet:

T0 = | X ha T = SELF_TYPE és v1 = X(…)

| T különben

class(T0) = (a1 : T1 ß e1, …, an : Tn ß en)

li = newloc(S1), for i = 1 … n

v1 = T0(a1 = l1, …, an = ln)

S2 = S1[DT1/l1, …, DTn/ln]

V1, S2, [a1 : l1, …, an : ln] ├ begin a1 ß e1; …, an ß en; end : v2, S3

so, S1, E ├ new T : v1, S3

A bonyolultságot az adja, hogy az attribútumokat a megfelelő sorrendben kell inicializálni. Inicializálás közben az attribútumok a megfelelő osztály default értékét is felvehetik.

Elágazás true feltétellel:

so, S1, E ├ e1 : Bool(true), S2

so, S2, E ├ e2 : v2, S3

so, S1, E ├ if e1 then e2 else e3 fi : v2, S3

Elágazás false feltétellel:

so, S1, E ├ e1 : Bool(false), S2

so, S2, E ├ e3 : v3, S3

so, S1, E ├ if e1 then e2 else e3 fi : v3, S3

Szekvencia:

so, S1, E ├ e1 : v1, S2

so, S2, E ├ e2 : v2, S3

so, Sn, E ├ en : vn, Sn+1

so, S1, E ├ begin e1; e2; …; en; end : vn, Sn+1

A blokkok kiértékelése az első kifejezéssel (e1) kezdődik és sorrendben haladva az utolsóval fejeződik be (en). Az eredmény az utolsó kifejezés eredménye.

Let szabálya:

so, S1, E ├ e1 : v1, S2

l1 = newloc(S2)

S3 = S2[v1/l1]

E’ = E[l1/Id]

so, S3, E’ ├ e2 : v2, S4

so, S1, E ├ let Id : T1 ß e1 in e2 end : v2, S4

A let kiértékel minden inicializációs kódot, hozzárendeli az eredményt a változóhoz egy új címen, és kiértékeli a törzset. Ha nincs inicializáció, akkor a változó a típus alapértelmezett értékével inicializálódik. Csak az egy változóra vonatkozó értékadás szemantikáját ajduk meg, a többváltozós értékadás előáll ezeknek a kompozíciójából:

let x1 : T1 ß e1, x2 : T2 ß e2, …, xn : Tn ß en in e end

ekvivalens a következő kifejezéssel:

let x1 : T1 ß e1 in (let x2 : T2 ß e2, …, xn : Tn ß en in e end) end

Case elágazás szabálya:

so, S1, E ├ e0 : v0, S2

v0 = X(…)

Ti = legközelebbi őse X-nek {T1, …, Tn}-ből

l0 = newloc(S2)

S3 = S2[v0/l0]

E’ = E[l0/Idi]

so, S3, E’ ├ ei : v1, S4

so, S1, E ├ case e0 of Id1 : T1 => e1; …; Idn : Tn => en; esac : v1, S4

A case szabálya megköveteli, hogy futásidőben elérhető legyen az osztályhierarchia valamilyen formában, hogy el lehessen dönteni, hogy melyik ágát kell kiválasztani a case-nek. Máskülönben a szabály magától értetődő.

A loop szabálya igaz ciklusfeltétel esetén:

so, S1, E ├ e1 : Bool(true), S2

so, S2, E ├ e2 : v2, S3

so, S3, E ├ while e1 loop e2 pool : v3, S4

so, S1, E ├ while e1 loop e2 pool : void, S4

A loop szabálya hamis ciklusfeltétel esetén:

so, S1, E ├ e1 : Bool(false), S2

so, S1, E ├ while e1 loop e2 pool : void, S2

isvoid szabálya (true eset):

so, S1, E ├ e1 : void, S2

so, S1, E ├ isvoid e1 : Bool(true), S2

isvoid szabálya (false eset):

so, S1, E ├ e1 : X(…), S2

so, S1, E ├ isvoid e1 : Bool(false), S2

A primitív aritmetikai, logikai és összehasonlító műveletek szabályai:

logikai nem:

so, S1, E ├ e1 : Bool(b), S2

v1 = Bool(Øb)

so, S1, E ├ not e1 : v1, S2

 

összehasonlítás:

so, S1, E ├ e1 : Int(i1), S2

so, S2, E ├ e2 : Int(i2), S3

op Î {£, <}

v3 = | Bool(true), ha i1 op i2

| Bool(false), különben

so, S1, E ├ e1 op e2 : v1, S3

 

negáció:

so, S1, E ├ e1 : Int(i), S2

v1 = Int(Øi)

so, S1, E ├ ~e1 : v1, S2

 

aritmetikai kifejezés:

so, S1, E ├ e1 : Int(i1), S2

so, S2, E ├ e2 : Int(i2), S3

op Î {*, +, -, /}

v3 = Int(i1 op i2)

so, S1, E ├ e1 op e2 : v1, S3

 

A Cool Int-jei 32 bites kettes komplemens tárolású előjeles egészek; az aritmetikai műveletek ennek megfelelően vannak definiálva.

Az itt megadott jelölések nem elég erősek ahhoz, hogy leírják az objektumok egyenlőségvizsgálatának módját, ezért ezt csak szavakkal adjuk meg.

Két objektum egyenlőségvizsgálatának első lépésében megnézzük a pointereiket (a címüket). Ha ezek megegyeznek, akkor a két objektum megegyezik. A void érték semmilyen objektummal nem egyenlő, kivéve önmagával. Ha a két objektum String, Bool vagy Int, akkor a tartalmuk kerül összehasonlításra.

Ezeken kívül a működési szabályok nem definiálják, hogy mi történik futás idejű hiba esetén. Ilyenkor a program végrehajtása megszakad. A következő lista tartalmazza a lehetséges futás idejű hibákat:

  1. (dinamikus vagy statikus) hívás void objektumon
  2. Case elágazás void típuson
  3. Case utasítás végrehajtása megfelelő ág nélkül
  4. Nullával való osztás
  5. Karakterlánc túlindexelés
  6. Heap túlcsordulás.