Objective Caml egy általános célú magasszintű programozási nyelv, ami ötvözi a funkcionális, az imperatív és az objektum-orientált programozást. Szigorúan típusos nyelv, és így biztosítja a programok helyes kiértékelését. A típusokat automatikusan következteti ki. A nyelv hatásos eszközöket kínál, úgy mint felhasználó által definiált adattípusok, sablonokkal definiálható függvények és kivételkezelés lehetősége. A programozást nagyban megkönnyíti az érett osztály alapú objektum orientált réteggel, és a kifejező modulrendszerrel.
Az O-Caml az ML programozási nyelv-családból származik és az INRIA Rocquencourt implementálta a "Cristal project" csoportban. A 70-es évek végétől, az ML kezdeteitől az INRIA folyamatosan közreműködött a nyelv kifejlesztésében, és tökéletesítésében. Az Objective Caml sokban hasonlít az eredeti ML nyelv központi törzséhez és az első Caml implementációhoz (1985-1990). Egy új byte-kód implementáció a Caml Light, a 90-es évek elején lett kifejlesztve. Ezt a nyelvet mind a mai napig használják, főleg oktatási célokra. Azokután nevezték át a nyelvet Objective Caml-ra, hogy a nyelvet kibővítették egy modul-rendszerrel és egy objektum-orientált réteggel.
Ahogy az ML összes leszármazottja, az O-Caml is tudja a következőket:Az implementáció tartalmaz általános célú könyvtárakat (tetszőleges pontosságú aritmetika, többszálas programok és grafikus felületek készítésének lehetősége, stb.), valamint Unix-stílusú programozási környezetet. Az OCaml programok illeszthetők más nyelvekhez, elsősorban C programokhoz és könyvtárakhoz.
Lehetőség van önálló, lefordított programok készítésére a gyorsabb futtatás érdekében, valamint bytekóddá fordításra, ha a hordozhatóság a követelmény. Az interaktív rendszerben pedig a kifejezések azonnal kiértékelhetők.
A Caml nyelvet széles körben használják oktatási célokra Franciaországban mind a felsőoktatásban, mind az követően. Továbbá használják több akadémiai projectben Euróbában, Japánban, Észak- és Dél-Amerikában. Néhány nagyobb francia vállalat ipari projectekhez használja az O-Camlt, úgymint a France Télécom, a Dassault, és a CEA (Commissariat a` l'Énergie Atomique).
Az INRIA megalapította a Caml konzorciumot a World Wide Web konzorcium alapján. Ez fogja nyújtani az ipari és tudományos partnereinek részvételét a fejlesztésekben, a karbantartásban és az legújabb fejlesztések leírását.
Az Objective Caml implementációja, csakúgy mint az átfogó dokumentációja teljesen ingyenesen elérhető az interneten:
Ez a dokumentáció az Objective Caml 3.11-es verziójához készült, minden megjegyzés, ami a jelenlegi implementációra vonatkozik, erre értendő.
A dokumentáció további részében szereplő példák abban a formában lesznek leírva, ahogyan azokat az interaktív rendszerbe kell beírni, illetve ahogyan a rendszer válaszol.
Ott a felhasználói bevitelt a # jellel kezdődő, míg a rendszer válaszát a # nélküli sorok jelzik
Az interaktív rendszerben a kifejezéskapcsolatokat ;;-vel kell befejezni, ekkor a rendszer azokat lefordítja, lefuttatja, és a kiértékelés eredményét kiírja. A kifejezéskapcsolatok lehetnek egyszerű kifejezések, vagy azonosítók (értékek vagy függvények) definíciói.
1+2*3;;
- : int = 7
#let pi = 4.0 *. atan 1.0;;
val pi : float = 3.14159265359
#let square x = x *. x;;
val square : float -> float = <fun>
#square(sin pi) +. square(cos pi);;
- : float = 1.
A Caml rendszer a kifejezéssorozatnak mind az értékét, mind a típusát kiszámítja. Még a függvényparaméterek esetén sincsen szükség a típus megadására, azt a függvényen belüli felhasználásból automatikusan következteti ki. A fenti példákon látszik, hogy az egészek és a lebegőpontos számok különbözőek, különböző műveletekkel: a + és * egészekre vonatkozik, a +. és *. lebegőpontos számokra.
Ez például hibás:
1.0 * 2;;
This expression has type float but is here used with type int
Rekurzív függvényeket a let rec kötéssel definiálhatunk:
let rec fib n =
if n < 2 then 1 else fib(n-1) + fib(n-2);;
val fib : int -> int = <fun>
#fib 10;;
- : int = 89
A nyelvben az ISO 8859-1 (Latin 1) karakterkészlet használható. A fordító megkülönbözteti a kis- és nagybetűket!
szóköz, új sor, horizontalis tabulátor, kocsi vissza, sor- és
oldal-törés
Ahogyan a funkcionális nyelveknél megszoktuk, itt is indentált módon
tördeljük a szöveget. Ez azt jelenti, hogy a sor eleji whitespace-ek
határozzák meg, hogy éppen hol tartunk a blokkszerkezet kialakításában.
Csak egyfajta megjegyzés van a nyelvben, mégpedig a (* komment *). Ezek egymásba ágyazhatóak.
Betű, szám, _ és ' jel szerepelhet benne. Megkülönböztetjük a kis- és nagybetűvel kezdődő szavakat, lsd később. Betűvel vagy _ jellel kell kezdődnie, maximum 16000000 hosszú lehet. A betűk közé legalább az ASCII karakterkészlet 52 kis- és nagybetűje értendő, de a jelenlegi implementáció (MacOS kivételével) engedélyezi az ISO Latin-1 készlet ékezetes betűit is.
Az egészekre 4-féle alap engedélyezett: 2, 8, 10, 16. A negatív számok elé - jelet írunk. A megengedett tartományon kívül eső literálok értelmezése definiálatlan.
Prefix | Radix |
decimális (radix 10) | |
0x, 0X | hexadecimális (radix 16) |
0o, 0O | octális (radix 8) |
0b, 0B | bináris (radix 2) |
A könnyebb olvashatóság kedvéért használható az _ karakter a számjegyek közötti elválasztójelként.
Egy lebegőpontos szám 3 részből áll: egész rész, tört rész, kitevő. A tört rész és a kitevő közül az egyik elhagyható. De egyszerre mindkettő nem, mivel akkor ütközés lenne az egész szám literálok alakjával. A könnyebb olvashatóság kedvéért használható az _ karakter a számjegyek közötti elválasztójelként.
A regular-char egy normál karakter, mely eltér az ' és \ jelektől.
Speciális vezérlő karakterek:
Karakter | Jelentés |
\\ | backslash (\) |
\" | macskaköröm (") |
\' | idézőjel (') |
\n | sortörés (LF) |
\r | kocsivissza (CR) |
\t | tabulátor (TAB) |
\b | visszalépés (BS) |
\ddd | egy karakter decimális ddd ASCII kódja |
\xhh | egy karakter hexadecimális hh ASCII kódja |
A karaktersorozat belsejében nem fordulhat elő a " és \ jelek egyike sem.
Megengedett a hosszú sorok tördelése, melyre a sor végén lévő \ jellel hivatkozhatunk.
A sztring hosszára nincs megkötés!
A True és a False.
A kulcsszavak azonosítókban és máshol nem szerepelhetnek!
predefinit azonosítók:
and as assert asr begin class constraint do done downto else end exception external false for fun function functor
if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new object of open
or private rec sig struct then to true try type val virtual when while with
egyéb predefinit jelek, jelsorozatok
!= # & && ' ( ) * + , -
-. -> . .. : :: := :> ; ;; <
<- => >] >} ? ?? [ [< [> [|
] _ ` { {< | |] } ~
Camlp4-ben használt további kulcsok
parser << <: >> $ $$ $:
A névütközések és a többértelműség elkerülésére be lehet vezetni a névcimkéket. Így meg lehet különböztetni az opcionális és a normál azonosítót.
A leghosszabb egyezőség elvét követi a nyelv
Különböző névterekbe lehet osztani az egyes nyelvi elemeket a könnyebb áttekinthetőség érdekében, erre a nyelv ad is egy direktívát:
Névtér | Első betű |
Érték | kisbetű |
Konstruktor | nagybetű |
Cimke | kisbetű |
Változó cimke | nagybetű |
Kivétel | nagybetű |
Típus konstruktor | kisbetű |
Rekord mező | kisbetű |
Osztály | kisbetű |
Hivatkozó változó | kisbetű |
Metódus | kisbetű |
Modul | nagybetű |
Modul típus | bármi |
ML-ben a változók valójában értékekre hivatkozó nevek. Tisztán funkcionális alkalmazás esetén nem lehet különbséget tenni a változó és az általa reprezentált érték között. Változó-kötéseket a let kulcsszóval vezethetünk be. Egy egyszerű felső szintű deklaráció:
A let kulcsszavas definíciókat lehet egymásba ágyazni:
Ekkor kifejezés2 a let törzse, amelyben név a kifejezés1 értékét fogja felvenni, és azon kívül nem lesz elérhető. Természetesen a törzzsel rendelkező let is kifejezés, aminek értéke a törzs értéke. A kötések statikusak: ha egy névre több kötés is van, mindig a legutóbbi számít. Ezen túlmenően, a kötés csak a let törzsében érvényes, illetve felső szintű kötések esetén a file hátralévő részében.
A szokásos változókról: OCaml-ben, mint általában a funkcionális nyelvekben, nincsenek a megszokott értelemben vett változók (vagyis, olyan azonosítók, amelyek értékei megváltoztathatóak). Azonban az OCaml nyújt bizonyos imperatív szolgáltatásokat is. A megszokotthoz hasonló változók a standard könyvtárban hivatkozásként: ún. változtatható indirekciós cellákként (mutable indirection cells), azaz egyelemű tömbként lettek megvalósítva. A ! operátorral hozzá lehet férni az aktuális tartalmukhoz, és a := operátorral új tartalmat lehet hozzájuk rendelni.
Ezek a referenciák hasznosak olyan függvények írásakor is, amelyeknek a hívások közt bizonyos értékeket meg kell tartaniuk (mint pl. egy véletlenszám-generátor).
Rendezett n-es: ( v1, ..., vn )
ahol n legfeljebb 2^22-1 lehet
Lista: [ v1; ...; vn]
Rekord: { field1 = v1; ...; fieldn = vn }
Gyakorlatilag cimkézett rendezett n-es
Tömb: Azonos típusú elemek szerepelhetnek csak benne. 2^22-1 db
elem fér el maximum. 64 bit-es platformon 2^54-1 elem. 0-tól n-1-ig indexelődik
Változó érték:
Állhat konstans konstruktorból (constr), vagy lehet egy nem konstans konstruktorból és egy v értékből képzett pár: constr(v).
Beépített konstans kontruktorok:
Konstans | Konstructor |
false | logikai hamis |
true | logikai igaz |
() | a "unit" érték |
[] | az üres lista |
Minden változó típushoz legfeljebb 246 nem konstans konstruktor lehet.
Polimorfikus változó:
Alternatív formája a változónak: nem tartozik egyik előredefiniált változótípushoz sem.
Lehet konstans `tag-name, vagy nem konstans `tag-name(v)
Függvény: A függvény egy leképezés érték és érték között, pl #let square x = x *. x;;
Objektum: áll egy rejtett belső állapotból, ami a hivatkozott változók rekordja, és a metódusok halmazából, amik elérik és beállítják ezeket a változókat. Az objektum struktúráját az őt létrehozó legfelső szintű osztály írja le.
Unió (variáns rekord) : Az unió többféle típus egy típusban való reprezentálását teszi lehetővé. Például készíthetünk bináris fát, amely levelekből és csomópontokból áll, ahol az adatokat a csomópontokban tároljuk. Ez esetben az üres fa nyilván egyetlen levél. Ekkor a fa maga tulajdonképpen unióként ábrázolható, nevezetesen: a fa vagy egy levél, vagy egy csomópont, alatta mindkét oldalán 1-1 fával. Mivel az OCaml szigorúan típusos, így természetesen az aktuális kitöltés típusa is adott. Az érték ennek megfelelően mintaillesztéssel szerezhető meg. Az előbbi példával, a fában lévő elemek száma:
let rec cardinality = function
Leaf -> 0
| Node (_, left, right) ->
cardinality left + cardinality right + 1;;
val cardinality : ’a btree -> int = <fun>
Operátor | Jelentés |
+ | Egész összeadás |
- (infix) | Egész kivonás |
- (prefix) | Egész negálás |
* | Egész szorzás |
/ | Egész osztás. Division_by_zero kivétel, ha 0-val osztok |
mod | Egész modulus. Division_by_zero kivétel, ha 0-val modulózok. |
land | Bitenkénti logikai ``és'' egészeken |
lor | Bitenkénti logikai ``vagy'' egészeken |
lxor | Bitenkénti logikai ``kizásó vagy'' egészeken |
lsl | Bitenkénti logikai shiftelés balra egészeken |
lsr | Bitenkénti logikai shiftelés jobbra egészeken |
asr | Bitenkénti aritmetikai shiftelés jobbra egészeken |
+. | Lebegőpontos összeadás |
-. (infix) | Lebegőpontos kivonás |
-. (prefix) | Lebegőpontos negáció |
*. | Lebegőpontos szorzás |
/. | Lebegőpontos osztás |
** | Lebegőpontos hatványozás |
@ | Lista konkatenálás |
^ | Sztring konkatenálás |
! | Dereferencing (a referencia aktuális értékével tér vissza) |
:= | Értékadás |
= | Struktúrális egyenlőség vizsgálat |
<> | Structúrális egyenlőtlenség vizsgálat |
== | Fizikai egyenlőség vizsgálat |
!= | Fizikai egyenlőtlenség vizsgálat |
< | Vizsgálat "kisebb" |
<= | Vizsgálat "kisebb egyenlő" |
> | Vizsgálat "nagyobb" |
>= | Vizsgálat "nagyobb egyenlő" |
Különböző precedenciaszintű operátorok
Operator | Associativity |
Type constructor application | -- |
* | -- |
-> | right |
as | -- |
+ - * / mod, valamint a szokásos léptető, stb. műveletek a bináris reprezentáción: lsl lsr asl asr land lor lxor.
+. -. *. /., valamint az int_of_float és float_of_int típuskonverziók.
A nyelvben ilyenek nincsenek, illetve azokhoz legjobban talán a változók standard könyvtárbeli megvalósítása hasonlít.
Deklarálása:
Tetszőleges típusokból álló, szokásos direktszorzat típus.
Az elemekhez való hozzáférés a listákhoz hasonlóan mintaillesztéssel történik, de a párokra van beépített utasítás, amelyekkel az első (fst) és a második (snd) elem elérhető. Pl. az fst definíciója (az _ jelet akkor használjuk, ha nem kívánjuk felhasználni az illeszkedés eredményét):
let fst (x, _) = x;;
val fst : ’a * ’b -> ’a = <fun>
Létrehozása a constr konstruktorral:
Létrehozása, melynek fejeleme lesz az expr1 és a maradék az expr2 lesz:
Ekvivalens kifejezések, mindkettőnek egy lista lesz az eredménye. Változó hosszúságúak lehetnek a listák, azonban az elemeknek azonos típusúaknak kell lenniük.
Az egyes elemekhez, illetve a lista részeihez mintaillesztéssel lehet hozzáférni. Pl.
# match lst with
# [] -> ........
Ez konstrukció akkor illeszkedik az lst nevű listára, ha az üres.
Új rekord építése, melynek egyes mezői megváltoznak:
Rekord mezőjére való hivatkozás:
Értékadás egy adott mezőnek:
A kifejezés értéke () lesz. Ez csak akkor működik ha a field mező mutable típusú.
Létrehozása:
Adott indexű elemére hivatkozás:
Értékadás egy adott indexű elemnek:
A kifejezés értéke () lesz. Érvénytelen index: Invalid_argument kivételt vált ki!
Adott indexű elemének elérése:
Értékadás egy adott indexű elemnek:
A kifejezés értéke () lesz. Érvénytelen index: Invalid_argument kivételt vált ki!
Objektum létrehozás:
Ha a class-path egy osztály törzsére mutat, akkor egy olyan objektum jön létre, ami tartalmazza a változókat, és a metódusokat. Ha ugyanez egy osztály függvényére mutat, még meg kell adni a szükséges argumentumokat is.
Üzenet küldés:
Belső változó elérése és módosítása:
Ezt csak az osztály metódusai, vagy az osztály leszármazottai hajthatják végre. Valamint a változónak mutable típusúnak kell lennie. A kifejezés értéke () lesz.
Egy objektum típusa lehet megszorított:
Típus változó
Zárójel típus ( typexpr )
Megegyezik az eredeti typexpr típussal
Függvény típus: typexpr1 -> typexpr2
Rendezett n-es típus: typexpr1 * ... * typexprn
ahol az i. elem típusa typexpri
Konstruktor típus:
Objektum típus:
Konstans típus:
Minta (pattern):
Különböző sablon típusok vannak:
Változó, konstans, aliased, zárójel, logikai vagy, változat, polimorfikus változó,
változó rövidítés, rendezett n-es, rekord és tömb
A típus definíció köti hozzá a típus konstruktorokat az adattípusokhoz, melyek a
következők lehetnek: változó, rekord, rövidítés vagy absztrakt adat típus
Absztrakt típus: Nincs egyenlet, nincs reprezentáció
Rövidítés típus: egy egyenlet, nincs reprezentáció
Új változó vagy rekord típus: nincs egyenlet, egy reprezentáció
Újra felhasznált változó vagy rekord típus: egy egyenlet, egy reprezentáció
Kivétel definíció
A kivétel definíció új konstruktort ad egy beépített változó típushoz. Az első egy új kivételt generál, mely eltér az eddigiektől, a második egy alternatív nevet ad egy meglévőhöz.
Az első találatnál kilép a szerkezetből. Ha nem talál megfelelő ágat, akkor a Match_failure kivétel váltódik ki!
lusta kiértékelés működik, tehát ha expr1 hamis, akkor az expr2 kifejezés nem értékelődik ki. Ez a kifejezés ekvivalens a következővel:
Ugyanúgy igaz mint az előbb, ha expr1 igaz, akkor az expr2 kifejezés nem fut le.
Ekvivalens kifejezés:
Mindkét esetben először kiértékelődik expr1 és expr2 és értékül adódik n és p egészeknek.
A name egymás után ugrál végig n-től p-ig. Az első esetben növekvő, a másodikban
csökkenő sorrendben. Az első esetben, ha n>p teljesül, akkor expr3 kifejezés
egyszer sem értékelődik ki. A második esetben is hasonlóképpen működik.
Az Objective Caml funkcionális nyelv, így a függvényeket azok teljes matematikai értelmében támogatja, és azokat tetszőlegesen lehet alkalmazni ill. továbbadni, mint bármilyen más adatot. Például az alábbi deriv függvény paramétere egy tetszőleges float függvény, és visszaadja a deriváltja egy közelítését:
let deriv f dx =
function x -> (f(x +. dx) -. f(x)) /. dx;;
val deriv : (float ->
float) -> float -> float -> float = <fun>
let sin' = deriv sin
1e-6;;
val sin' : float -> float
= <fun>
sin' pi;;
- : float =
-1.00000000013961143
Lehetséges a függvénykompozíció is:
let compose f g = function x
-> f(g(x));;
val compose : ('a -> 'b)
-> ('c -> 'a) -> 'c -> 'b = <fun>
let cos2 = compose square
cos;;
val cos2 : float -> float
= <fun>
Természetesen, a fentiek szerint funkcionálokat, azaz olyan függvényeket is használhatunk, amelyek más függvényeket kapnak paraméterül. Ezek különösen iterátorokként illetve olyan általános műveletekként hasznosak, amelyek valamilyen adatstruktúrára alkalmazhatók.
Ha az expr kifejezésben kivétel váltódik ki, azt megpróbálja az egyik kivételkezelő ág elkapni. Ha sikerül, annak megfelelően hajtódik végre a blokk. Ha nem, akkor kezeletlenül tovább terjed!
A 2004/2005 I. félévében meghírdetett Programozási nyelvek III című tárgy keretében
Kereszty Gábor.
2003.08.27.: A dokumentációt Programozási nyelvek IV tárgy keretében készítette:
Oroszi Sándor.
2005.07.03.: A dokumentációt Programozási nyelvek III tárgy keretében kiegészítette:
Szépe Zoltán.
2009.07.02.: A dokumentációt Programozási nyelvek 3. tárgy keretében
megformázta:
Lázár János.
2010.01.07.: A dokumentációt a Programozási nyelvek és paradigmák összehasonlítása I. tárgy keretében kiegészítette és megformázta:
Komjáti László.
2012.06.07.: A dokumentációt a Programozási nyelvek és paradigmák összehasonlítása II. tárgy keretében ellenőrizte, kiegészítette, NetBeans és w3.org validátor segítségével javította:
Nagy Tamás.
Az alábbi point osztály egy x példányváltozót és egy get_x és move metódust definiál. A példányváltozó kezdeti értéke 0. Az x változó mutable-ként kerül deklarálásra, így a move metódus meg tudja változtatni az értékét.
#
class point =
object
val mutable x = 0
method get_x = x
method move d = x <- x + d
end;;
class point :
object val mutable x : int method get_x : int method move : int -> unit end
Most létrehozunk egy p pontot, a point osztály objektumaként.
#
let p = new point;;
val p : point = <obj>
Jegyezzük meg, hogy a p típusa point. Ez egy automatikusan definiált rövidítése a fenti osztály definíciónak. Ez a get_x : int; move : int -> unit objektum típust helyettesíti, ami a point osztály metódusainak felsorolása.
Most meghívunk pár metódust a p-re:
#
p#get_x;;
- : int = 0
#
p#move 3;;
- : unit = ()
#
p#get_x;;
- : int = 3
Az osztály törzsének kiértékelése, csak az objektum létrehozásakor történik meg. Ezért a következő példában az x példányváltozó különböző kezdeti értékkel kerül inicializálásra, két különböző objektumban.
#
let x0 = ref 0;;
val x0 : int ref = {contents = 0}
#
class point =
object
val mutable x = incr x0; !x0
method get_x = x
method move d = x <- x + d
end;;
class point :
object val mutable x : int method get_x : int method move : int -> unit end
#
new point#get_x;;
- : int = 1
#
new point#get_x;;
- : int = 2
A point osztályt az x koordináta kezdeti értékétől függően is tudjuk definiálni.
#
class point = fun x_init ->
object
val mutable x = x_init
method get_x = x
method move d = x <- x + d
end;;
class point :
int ->
object val mutable x : int method get_x : int method move : int -> unit end
A függvény definíciókhoz hasonlóan, a fenti osztálydefiníciót a következőképpen tudjuk rövidíteni:
#
class point x_init =
object
val mutable x = x_init
method get_x = x
method move d = x <- x + d
end;;
class point :
int ->
object val mutable x : int method get_x : int method move : int -> unit end
A point osztály egy példánya most egy függvény ami egy inicializáló paramétert vár a pont objektum létrehozásához.
#
new point;;
- : int -> point = <fun>
#
let p = new point 7;;
val p : point = <obj>
Az x_init paraméter, természetesen látható az osztály egész törzsében, a függvényeken belül is. Például az alábbi példában a get_offset metódus visszaadja a pont objektum relatív pozícióját, a kezdeti pozícióhoz képest.
#
class point x_init =
object
val mutable x = x_init
method get_x = x
method get_offset = x - x_init
method move d = x <- x + d
end;;
class point :
int ->
object
val mutable x : int
method get_offset : int
method get_x : int
method move : int -> unit
end
A kifejezések kiértékelhetőek az osztály objektum törzsének definiálása előtt. Ez az invariánsok kikényszerítésénél lehet hasznos. Például a pontok automatikusan a pontrácson a legközelebbi pozícióra igazíthatóak:
#
class adjusted_point x_init =
let origin = (x_init / 10) * 10 in
object
val mutable x = origin
method get_x = x
method get_offset = x - origin
method move d = x <- x + d
end;;
class adjusted_point :
int ->
object
val mutable x : int
method get_offset : int
method get_x : int
method move : int -> unit
end
Természetesen, ugyanez elérhető a point osztály origin értékkel való meghívásával.
#
class adjusted_point x_init = point ((x_init / 10) * 10);;
class adjusted_point : int -> point
Ez a tulajdonság nyújtja az osztály konstruktorok lehetőségét, ahogy az más programozási nyelvekben is megtalálható. Ezzel a módszerrel többféle konstruktort lehet definiálni objektumok létrehozására ugyanahoz az osztályhoz, de különböző inicializáló mintával.
Van még egy módszer objektumok létrehozására: létrehozni anélkül hogy létrehoznánk az osztályát.
A szintaxis ugyanaz mint az osztályoknál, de az eredmény egyetlen objektum, nem egy osztály. Minden ebben a fejezetben tárgyalt konstrukció alkalazható egyedi objektumokra.
#
let p =
object
val mutable x = 0
method get_x = x
method move d = x <- x + d
end;;
val p : < get_x : int; move : int -> unit > = <obj>
#
p#get_x;;
- : int = 0
#
p#move 3;;
- : unit = ()
#
p#get_x;;
- : int = 3
Nem úgy mint az osztályoknál, amik nem állhatnak kifejezések belsejében, a névtelen osztályok szerepelhetnek bárhol.
#
let minmax x y =
if x < y then object method min = x method max = y end
else object method min = y method max = x end;;
val minmax : 'a -> 'a -> < max : 'a; min : 'a > = <fun>
A névtelen osztályok, az osztályokkal összehasonlítva, két gyengéjük van: a típusaik nem rövidítettek, és nem lehet belőlük származtatni. De ez a két hátrány előnnyé válhat, ahogy majd látjuk a későbbi fejezetekben.
Egy metódus vagy inicializátor küldhet üzeneteket önmagának. Ezért egy változóhoz kell kötnünk, itt az s változóhoz (s egy azonosító, gyakran a self nevet választjuk).
#
class printable_point x_init =
object (s)
val mutable x = x_init
method get_x = x
method move d = x <- x + d
method print = print_int s#get_x
end;;
class printable_point :
int ->
object
val mutable x : int
method get_x : int
method move : int -> unit
method print : unit
end
#
let p = new printable_point 7;;
val p : printable_point = <obj>
#
p#print;;
7- : unit = ()
Dinamikusan, az s változó a metódus meghívásakor kerül megkötésre. Speciálisan, amikor a printable_point osztály származtatva van, az s változó helyesen az alosztály objektumához lesz kötve.
Egy általános probléma, hogy a típusa az alosztályokban bővülhet, nem lehet rögzíteni. Íme egy egyszerű példa:
#
let ints = ref [];;
val ints : '_a list ref = {contents = []}
#
class my_int =
object (self)
method n = 1
method register = ints := self :: !ints
end;;
This expression has type < n : int; register : 'a; .. >
but is here used with type 'b
Self type cannot escape its class
A hibaüzenet első két sorát kihagyhatjuk, számunkra most az utolsó érdekes: önmagát egy külső referenciába helyezve lehetetlenné tenné a későbbi bővítést. Egy későbbi fejezetben láthatjuk ennek a problémának a kikerülését. Mivel a névtelen osztályokat nem származtathatjuk, ez a probléma náluk nem jelentkezik.
#
let my_int =
object (self)
method n = 1
method register = ints := self :: !ints
end;;
val my_int : < n : int; register : unit > = <obj>
Az osztálydefiníciókon belüli let-kifejezések kiértékelése még az objektum létrehozása előtt megtörténik. Egy kifejezés kiértékelése közvetlenül az obejktum létrehozása után is megtörténhet. Ilyen az inicializátor (initializer), amely egy név nélküli rejtett metódus. Az inicializátor hozzáférhet a self-hez és a példányváltozókhoz is.
#
class printable_point x_init =
let origin = (x_init / 10) * 10 in
object (self)
val mutable x = origin
method get_x = x
method move d = x <- x + d
method print = print_int self#get_x
initializer print_string "new point at "; self#print; print_newline()
end;;
class printable_point :
int ->
object
val mutable x : int
method get_x : int
method move : int -> unit
method print : unit
end
#
let p = new printable_point 17;;
new point at 10
val p : printable_point = <obj>
Az inicializátorokat nem definiálhatjuk felül. Kiértékelésük szekvenciálisan történik.
A virtual kulcsszó használatával, lehetőségünk van metódus deklarálására anélkül hogy definiálnánk. Ez a metódus később a származtatott osztályokban kerül kifejezésre. A virtuális metódust tartalmazó osztályt virtual kulcsszóval kell megjelölni, és belőle objektum nem készülhet.
#
class virtual abstract_point x_init =
object (self)
val mutable x = x_init
method virtual get_x : int
method get_offset = self#get_x - x_init
method virtual move : int -> unit
end;;
class virtual abstract_point :
int ->
object
val mutable x : int
method get_offset : int
method virtual get_x : int
method virtual move : int -> unit
end
#
class point x_init =
object
inherit abstract_point x_init
method get_x = x
method move d = x <- x + d
end;;
class point :
int ->
object
val mutable x : int
method get_offset : int
method get_x : int
method move : int -> unit
end
A privát metódusok nem jelennek meg az osztály interfészén. Csak az osztályhoz tartozó objektumból hívhatóak.
#
class restricted_point x_init =
object (self)
val mutable x = x_init
method get_x = x
method private move d = x <- x + d
method bump = self#move 1
end;;
class restricted_point :
int ->
object
val mutable x : int
method bump : unit
method get_x : int
method private move : int -> unit
end
#
let p = new restricted_point 0;;
val p : restricted_point = <obj>
#
p#move 10;;
This expression has type restricted_point
It has no method move
#
p#bump;;
- : unit = ()
A privát metódusokat, alapértelmezett esetben, a származtatott osztályok is öröklik, hacsak külön azt nem jelöljük, ahogy később bemutatjuk.
A privát metódusok a származtatott osztályokban public-ká tehetőek.
#
class point_again x =
object (self)
inherit restricted_point x
method virtual move : _
end;;
class point_again :
int ->
object
val mutable x : int
method bump : unit
method get_x : int
method move : int -> unit
end
A virtual jelölés szolgál arra, hogy egy metódust vezetünk be anélkül, hogy megadnánk a definícióját. Mivel nem írtuk ki a private kulcsszót, ezért a metódus public lesz, megtartva az ősosztálybeli definíciót.
Egy alternatív definíció a következő:
#
class point_again x =
object (self : < move : _; ..> )
inherit restricted_point x
end;;
class point_again :
int ->
object
val mutable x : int
method bump : unit
method get_x : int
method move : int -> unit
end
Az a megkötésünk a self típusára, hogy rendelkezzen egy public move metódussal, és ez elég, hogy a privátot felüldefiniálja.
Egyesek azt gondolhatják, hogy a privát metódusoknak privátoknak kell maradniuk az alosztályokban. Mivel a metódus látható a származtatott osztályokban, ezért lehetőségünk van, hogy a kódját kiragadva definiáljunk egy metódust ugyanazzal a névvel, így kapjuk a következőt:
#
class point_again x =
object
inherit restricted_point x as super
method move = super#move
end;;
class point_again :
int ->
object
val mutable x : int
method bump : unit
method get_x : int
method move : int -> unit
end
Természetesen privát metódusok lehetnek virtuálisak is. Ebben az esetben a kulcsszavak sorrendje a következő: method private virtual
Az osztályinterfészek az osztály definícióból következnek. Az osztály típusának megszorításához, direkt módon is megadhatók. Az osztály deklarációkhoz hasonlóan, egy új típus rövidítést is definiálnak.
#
class type restricted_point_type =
object
method get_x : int
method bump : unit
end;;
class type restricted_point_type =
object method bump : unit method get_x : int end
#
fun (x : restricted_point_type) -> x;;
- : restricted_point_type -> restricted_point_type = <fun>
A program dokumentálásán kívül, az osztály interfészt használhatjuk az osztály típusának a megszorítására. Ezt a megszorítást használva az osztály példányváltozóit és a konkrét privát metódusokat elrejthetjük (a virtuális és public metódusokat nem).
#
class restricted_point' x = (restricted_point x : restricted_point_type);;
class restricted_point' : int -> restricted_point_type
Vagy, ami ezzel ekvivalens:
#
class restricted_point' = (restricted_point : int -> restricted_point_type);;
class restricted_point' : int -> restricted_point_type
Az öröklődést a következő példán szemléltetjük: definiáljuk a színes pontok osztályát (colored_point), amely a pontok osztályából (point) származik. Ez az osztály rendelkezik a pont osztály összes példányváltozójával és metódusaival, plusz még egy új c példányváltozóval és egy color metódussal.
#
class colored_point x (c : string) =
object
inherit point x
val c = c
method color = c
end;;
class colored_point :
int ->
string ->
object
val c : string
val mutable x : int
method color : string
method get_offset : int
method get_x : int
method move : int -> unit
end
#
let p' = new colored_point 5 "red";;
val p' : colored_point = <obj>
#
p'#get_x, p'#color;;
- : int * string = (5, "red")
A pont és a színes pont objektumoknak különböző típusuk van, hiszen a pontnak nincs color metódusa. A get_x függvény alább egy generikus függvény, amely a get_x metódust használja minden olyan p objektumra, amelynek van ez a metódusa. Tehát alkalmazható a pontra és a színes pontra is.
#
let get_succ_x p = p#get_x + 1;;
val get_succ_x : < get_x : int; .. > -> int = <fun>
#
get_succ_x p + get_succ_x p';;
- : int = 8
A metódusokat nem kell előre deklarálni, ahogy majd láthatjuk a következő példában:
#
let set_x p = p#set_x;;
val set_x : < set_x : 'a; .. > -> 'a = <fun>
#
let incr p = set_x p (get_succ_x p);;
val incr : < get_x : int; set_x : int -> 'a; .. > -> 'a = <fun>
Osztályokat létrehozhatunk több osztályból való örökléssel is. Például definiáljunk egy pont osztályt, aminek van egy szín attribútuma (color). A color osztály a szokásos módon legyen definiálva.
#
type color = Black | Red | Green | Blue;;
type color = | Black | Red | Green | Blue
#
class color =
object
val mutable color = Black
method get_color = color
method set_color color' = color <- color'
method reset = color <- Black
end;;
class color :
object
val mutable color : color
method get_color : color
method reset : unit
method set_color : color -> unit
end
#
let c = new color;;
val c : color = <obj>
#
c#set_color Green;;
- : unit = ()
#
c#get_color;;
- : color = Green
A színes pont definiálásához mindkét osztályból kell örököltetni. Így az osztály objektumai rendelkezni fognak mindkét osztályban szereplő metódusokkal és példányváltozókkal.
#
class colored_point =
object
inherit point
inherit color
end;;
Warning: the following methods are overriden
by the inherited class: reset
class colored_point :
object
val mutable color : color
method get : int
method get_color : color
method reset : unit
method set : int -> unit
method set_color : color -> unit
end
#
let cp = new colored_point;;
val cp : colored_point = <obj>
#
cp#get;;
- : int = 0
#
cp#get_color;;
Amint látjuk a compiler egy figyelmeztető üzenetet dob amikor a színes pont létrejön. Mindkét osztály, a point és a color is, tartalmaz egy reset metódust. Melyik definíciót fogja a színes pont használni?
#
cp#set 7;;
- : unit = ()
#
cp#set_color Red;;
- : unit = ()
#
cp#reset;;
- : unit = ()
#
cp#get;;
- : int = 7
#
cp#get_color;;
- : color = Black
A megszokott módon a compiler a metódus utolsó definícióját használja.
A színes pont helyes verziójában mindkét osztály reset metódusát meg kell hívni. Ezért a colored_point metódusának felül kell definiálnia a reset definícióit. Ehhez az inherit deklarációknál nevet kell rendelnünk mindegyik osztályhoz.
#
class colored_point =
object
inherit point as p
inherit color as c
method reset =
p#reset;
c#reset
end;;
Warning: the following methods are overriden
by the inherited class: reset
class colored_point :
object
val mutable color : color
val mutable pos : int
method get : int
method get_color : color
method reset : unit
method set : int -> unit
method set_color : color -> unit
end
#
let cp = new colored_point;;
val cp : colored_point = <obj>
#
cp#set 5;;
- : unit = ()
#
cp#set_color Red;;
- : unit = ()
#
cp#reset;;
- : unit = ()
#
cp#get;;
- : int = 0
#
cp#get_color;;
- : color = Black
A compiler még így is figyelmeztet, de a reset metódus helyesen fog működni.
A toplevel rendszeren keresztül a felhasználók interaktívan használhatják (beolvasás-kiértékelés-kiírás ciklusokon keresztül) az Objective Caml rendszert. A rendszer ebben a módban ismétlődően beolvassa a Caml kifejezéseket az inputról, típusellenőrzi, lefordítja és kiértékeli őket, majd kiírja a kikövetkeztetett típust, és ha van, akkor az eredményt is. A rendszer minden kifejezés beolvasása előtt egy # promptot ír ki.
Az input több sorba is törhető és ;; fejezi be. A bemenet egy vagy több toplevel kifejezést tartalmazhat a következő szintaxissal:
ocaml | opciók objektumok | # interaktív mód |
ocaml | opciók objektumok szkriptfájl | # szkript mód |
Az ocaml parancs a következő parancssori paramétereket ismeri:
A következő direktívák vezérlik a toplevel viselkedését, fájlok betöltését a memóriába és a program végrehajtás nyomkövetését. Minden direktíva a # jellel kezdődik. Ezt a # jelet kell beírni minden direktíva előtt, és nem szabad összetéveszteni a # prompt-al, amit az interaktív hurok jelenít meg. Pl. a #quit;; kilép a toplevel hurokból, de a quit;; eredménye egy “unbound value quit” hiba lesz.
A toplevel kifejezések hivatkozhatnak olyan azonosítókra, amelyek fordítási
egységekben lettek definiálva, ugyanazzal a mechanizmussal, mint a külön-külön
fordított egységek, vagyis vagy a minősített nevek (Modulename.localname),
vagy az open szerkezet és a minősítetlen nevek használatával.
Mielőtt hivatkozunk egy másik fordítási egységre, annak az egységnek egy
implementációjának jelen kell lennie a memóriában. Induláskor a toplevel
rendszer tartalmazza a standard library-ban lévő összes modul implementációját.
A felhasználói modulokat a #load direktívával lehet betölteni.
Implementációval nem rendelkező egységre történő hivatkozásnál az eredmény "Reference to undefined global ‘. . . ’ "
hiba.
Azt beírva, hogy open Mod pusztán hozzáférünk a Mod lefordított
interfészhez (.cmi fájl), de nem töltődik be a Mod
implementációja és ezért nem történik hiba, ha nincs a betöltött Mod-nak implementációja.
A "reference to undefined global Mod" hiba csak akkor következik be,
amikor végrehajtódik egy érték vagy modul definíció, ami a Mod-ra hivatkozik.
A leggyakrabban előforduló hibaüzenetek leírása a következő:
Az ocamlmktop parancs olyan Objective Caml topleveleket készít, amelyek induláskor előre betöltve tartalmazzák a felhasználói kódot.
Az ocamlmktop parancs argumentuma lehet .cmo és .cma fájlok halmaza, és összeköti őket az objektum fájlokkal, amelyek implementálják az Objective Caml toplevel-t. A tipikus használat:
ocamlmktop -o mytoplevel foo.cmo bar.cmo gee.cmo
Ez elkészíti a mytoplevel bájtkódot, amely tartalmazza az Objective Caml toplevel
rendszert, valamint a kódot három .cmo fájlból. Ez a toplevel
közvetlenül végrehajtható a következő módon:
./mytoplevel
Ez belép egy szokásos toplevel hurokba, kivéve, ha foo.cmo, bar.cmo és gee.cmo
kódja már be van töltve a memóriába, mert a toplevel-be belépéskor már beírtad, hogy:
#load "foo.cmo";;
#load "bar.cmo";;
#load "gee.cmo";;
A Foo, Bar és Gee modulok nincsenek megnyitva. Ha szeretnéd, akkor magadnak kell beírni, hogy:
open Foo;;
Az ocamlmktop parancs a következő parancssori paramétereket ismeri:
Az OCamlBrowser egy LablTk használatával megírt program, ami a forráskód és a lefordított interfészek böngészésére használható.
Funkciói:
A böngészőt az ocamlbrowser parancs kiadásával lehet elindítani
A következő parancssori opciók használhatóak
A legtöbb opció az alkalmazáson belül is módosítható a Modules - Path editor és a Compiler - Preferences parancsokkal. A beállítások a toplevel shell-ből öröklődnek az indításkor.
Ez az első ablak, amely megjelenik az OCamlBrowser indításakor. Egy kereső ablakot tartalmaz, valamint a betöltési útvonalon elérhető modulok listáját. A programban elérhető menüpontok:
Egy modult kiválasztani a legbaloldalibb dobozban lehet kattintással vagy az enter megnyomásával.
Gyors hozzáférés is elérhető minden dobozban a kívánt név első néhány betűjének lenyomásával. A dupla kattintás vagy dupla enter megjeleníti a teljes szignatúráját a modulnak.
A modulban definiált nevek egy másik dobozban, az előzőtől jobbra vannak megjelenítve. Ha valamelyiken kattintunk, akkor vagy megjelenik a tartalma egy másik dobozban (ha ez egy almodul), vagy alul megjeleníti az azonosító szignatúráját.
A szignatúrák kattinthatóak. Az egér bal gombjával történő kattintás egy szignatúrában lévő azonosítón előhozza annak a szignatúráját. A jobb egérgombbal történő kattintás felhoz egy menüt, típus deklaráció megjelenítésére a kiválasztott azonosítóhoz. Ennek a címe, ha választható, akkor szintén előhozza a szignatúráját.
Az alul lévő nyomógombok működése az aktuális kontextustól függ.
A Control-S-el a szignatúrában lévő szövegre kereshetünk rá.
Fájlokat lehet vele szerkeszteni (ha még nem használtuk az emacs-t). Máskülönben böngészőként is használható, az alkalmi javítások elvégzésére.
Az Edit menü parancsokat tartalmaz az ugrásra (C-g), keresésre (C-s), és az aktuális kifejezés elküldésére (vagy kiválasztásra, ha egy szöveg ki van jelölve) egy al-shell-nek (M-x). Ehhez az utolsó opcióhoz a sell-t egy párbeszéd ablakban kell kiválasztani.
A lényegi funkciók a Compiler menüben találhatóak.
Amikor egy shell-t indítunk egy párbeszéd ablakot kapunk, amiben kiválaszthatjuk a parancsot, amit futtatni szeretnénk, valamint tartalmazza a shell címét is, amit az editorban lehet kiválasztani.
A végrehajtott al-shell megkapja az aktuális betöltési útvonalat.
Az ocamllex egy lexikai elemzőt készít a reguláris kifejezések egy halmazából, míg az ocamlyacc egy parsert egy nyelvtanból, a hozzákapcsolt szemantikus akciókkal. Ez a két programgenerátor nagyon hasonló a jól ismert lex és yacc parancsokhoz, amelyek a legtöbb C programozási környezetben megtalálhatóak. Ezekről részletesebben a következő két könyvben olvashatunk: "Compilers: principles, techniques, and tools" (Aho, Sethi és Ullman (Addison-Wesley, 1986)), és "Lex & Yacc", (Levine, Mason és Brown (O’Reilly, 1992)).
Az ocamllex parancs egy lexikai elemzőt állít elő reguláris kifejezések egy halmazából a hozzákapcsolt szemantikus akciókkal, a lex stílusában. Feltételezve, hogy a bemeneti fájl neve lexer.mll, lefuttatva az
parancsot, a lexer.ml fájlba előállítja a Caml kódot a lexikai elemzőnek. Ez a fájl definiál a lexer definícióban lévő minden belépési pontnak egy lexikálási függvényt. Ezeknek a függvényeknek ugyanaz a neve, mint a belépési pontoknak. A lexikálási függvények argumentumként megkapják a lexer puffert és visszatérnek a megfelelő belépési pont szemantikus attribútumával. A lexer puffer egy absztrakt adattípus, ami a Lexing standard könyvtár modulban van implementálva. A Lexing.from_channel, Lexing.from_string és Lexing.from_function függvények egy lexer puffert készítenek, ami egy bemeneti csatornáról, egy stringből, vagy valamilyen olvasó függvényből olvas. Egy ocamlyacc által generált parserrel összekapcsolva, a szemantikus akciók kiszámítanak egy értéket, amely a generált parser modul által definiált token típusához tartozik.
Az ocamllex parancs a következő parancssori paramétereket ismeri:
A lexer definíciók formája a következő:
A megjegyzéseket (* és *) határolja, hasonlóan, mint a Caml-ben. A parse kulcsszó helyettesíthető a legrövidebb kulcsszóval, a később megmagyarázott szemantikus következményekkel.
A header és trailer szakaszok korlátlan Caml szövegek kapcsos zárójelek közé
zárva. Egyik vagy mindkettő elhagyható. Ha létezik, akkor a header szöveg az
output fájl elejére, a trailer pedig a végére másolódik. Tipikusan a header
szakasz az open direktívákat tartalmazza, amelyek az akciók számára szükségesek,
és esetleg néhány kiegészítő függvényt, amelyek az akciókban használhatóak.
A header és a belépési pontok között, a gyakran előforduló reguláris
kifejezéseknek let ident = regexp formában adhatunk nevet. A reguláris
kifejezésekben, az előbbi deklarációnak megfelelően, az ident azonosítót
használhatjuk a regexp megadására.
A belépési pontok nevei érvényes azonosítók kell, hogy legyenek Caml értékeknek
(kisbetűvel kell kezdődniük). Hasonlóan az arg1...argn argumentumoknak is
érvényes Caml azonosítóknak kell lenniük. Minden belépési pontból lehet egy Caml
függvény, ami n+1 argumentumot fogad, ahol az extra utolsó argumentum típusa Lexing.lexbuf.
A Lexing.lexbuf argumentumból olvassa a karaktereket és illeszti a szabályban
megadott reguláris kifejezésekre, amíg az input illesztések prefixe a szabályok
egyike. A megfelelő akció ezután kiértékelődik és a függvény eredményeként tér
vissza.
Ha több reguláris kifejezés illeszkedik egy input prefixére, a "leghosszabban
illeszkedő" szabály lesz alkalmazva, vagyis az a reguláris kifejezés lesz
kiválasztva, amelyik az input leghosszabb prefixére illeszkedik. Döntetlen
esetén a szabályban korábban előforduló reguláris kifejezés lesz kiválasztva.
Ha a lexer szabályok a shortest kulcsszót tartalmazzák a parse kulcsszó
helyén, akkor a "legrövidebben illeszkedő" szabály lesz alkalmazva, vagyis az
input legrövidebb prefixe lesz kiválasztva. Ez a feature nem a rendes lexikai
elemzőkhöz készült, hanem, hogy megkönnyítse az ocamllex használatát egyszerű
szövegfeldolgozó eszközként.
A reguláris kifejezések lex stílusúak, egy kicsit több Caml szerű szintaxissal.
regexp ::= ...
Az operátorok precedenciája: * és + a legmagasabb, aztán a ?, majd a konkatenáció, aztán a | (alternatíva), majd az as következik.
Az akciók tetszőleges Caml kifejezések lehetnek. Kiértékelésre abban a kontextusban kerülnek, amelyben az as konstrukció használatával definiált azonosítók az illesztett string alrészeihez lettek kötve. Ezenkívül a lexbuf az aktuális lexer pufferhez is kötve van. A lexbuf néhány tipikus használatát összekapcsolva a Lexing standard library modul által a lexer puffereken biztosított műveletekkel az alábbi felsorolás tartalmazza.
Az as konstrukció hasonló a "csoportokhoz", ami számos reguláris kifejezés
csomag által biztosított. Ezeknek a változóknak a típusa string, char, string
option vagy char option lehet.
Először nézzük meg a lineáris minták esetét, amikor minden kötött változó
különböző. A regexp-ben, mint azonosítóban, az azonosító típusa általában string
(vagy string option), kivéve, amikor a regexp egy karakter konstans, egy
aláhúzás, egy hossz string konstansa, egy karakterhalmaz specifikáció, vagy azok
egy változata. Ekkor az azonosító típusa char (vagy char option).
Az option
típusok akkor kerülnek bevezetésre, amikor az általános szabályillesztésből nem
következik a kötött al-minta illesztése. Ez a különleges (regexp as ident) ?
és a regexp1 | (regexp2 as ident) esetekben fordulhat elő.
Nincs linearitási megkötés a kötött változókon. Amikor egy változó egynél
többször is kötött, a megelőző szabályok a következők szerint lesznek
kiterjesztve:
Például az (’a’ as x) | (’a’ (_ as x)) kifejezésben az x változó karakter típusú, míg az
("ab" as x) | (’a’ (_ as x) ?) kifejezésben string option típusú.
Néhány esetben a sikeres illesztés nem adhatja a kötések egy egyedi halmazát.
Például az aba illeszkedése a ((’a’|"ab") as x) (("ba"|’a’) as y) reguláris
kifejezésre eredményes lehet az x "ab"-hoz és y "a"-hoz, vagy az x "a"-hoz és y "ba"-hoz kötésében.
Az automatikusan előállított ocamllex az ilyen félreérthető reguláris
kifejezéseknél ki fogja választani az egyik lehetséges eredmény halmazát a
kötéseknek. A kiválasztott kötések halmaza szándékosan specifikálatlanul hagyott.
Minden __ocaml_lex-el kezdődő azonosító fenntartott az ocamllex általi használatra, ezért ezeket ne használjuk a programunkban.
Az ocamlyacc parancs egy parsert állít elő egy környezetfüggetlen nyelvtani specifikációból a csatolt szemantikus akciókkal, az yacc stílusában. Feltételezve, hogy az input fájl a grammar.mly, lefuttatva az
parancsot legyártja a parser Caml kódját a grammar.ml fájlba, valamint annak
interfészét a grammar.mli fájlba.
A legenerált modul a nyelvtanban belépési pontonként egy elemző függvényt
definiál. A függvények nevei megegyeznek a belépési pontoknak a neveivel. Az
elemző függvények argumentumai egy lexikai elemző (egy függvény, amely a lexer
pufferből tokent állít elő) és egy lexer puffer, visszatérési értéke a megfelelő
belépési pont szemantikus attribútuma. A lexikai elemző függvények gyakran egy
lexer specifikációból vannak legenerálva az ocamllex program által. A
lexer puffer egy absztrakt adattípus, amely a Lexing standard library-ben van
implementálva. A tokenek értékek a konkrét típus tokenekből, amelyek az
ocamlyacc által előállított grammar.mli interfész fájlban vannak definiálva.
A nyelvtani definíciók formája a következő:
A "declarations" és a "rules" szakaszokban a megjegyzések /* és */ közé vannak zárva (hasonlóan a C-hez), és (* és *) közé (hasonlóan a Caml-höz) a "header" és "trailer" szakaszokban.
A header és a trailer szakaszok Caml kódok, amelyek változtatás nélkül kerülnek átmásolásra a grammar.ml fájlba. Mindkét szakasz opcionális. A header az output fájl elejére kerül, gyakran tartalmazza az open direktívát és olyan kiegészítő függvényeket, amelyek a szabályok szemantikus akciói számára szükségesek. A trailer az output fájl végére kerül.
Soronként egy deklaráció adható meg. Az első karakterük a % jel.
A szabályok szintaxisa
A szabályok tartalmazhatják a %prec symbol direktívát is a jobb
oldali részben, hogy felülírják az alap precedenciáját és asszociativitását a
szabálynak a megadott szimbólum precedenciájával és asszociativitásával.
A szemantikus akciók tetszőleges Caml kifejezések, amelyek kiértékelése
előállítja a megadott nemterminálishoz csatolt szemantikus attribútumot. A
szemantikus akciók hozzáférnek a szabály jobb oldalán a szimbólumok szemantikus
attribútumaihoz a $ jelöléssel: $1 az első (leg baloldalibb) szimbólum
attribútumához, $2 a második szimbólum attribútumához, stb.
A szabályok tartalmazhatnak speciális error szimbólumot a reszinkronizációs
pontok jelzésére, mint az yacc-ban.
A szabályok közepén előforduló akciók nem támogatottak.
A nemterminális szimbólumok olyanok, mint a megszokott Caml szimbólumok,
kivéve, hogy nem végződhetnek ' (aposztróf) jellel.
A hiba utáni helyreállítás a következő módon támogatott: amikor a parser elér
egy hibaállapotot (nem tud nyelvtani szabályt alkalmazni), meghívja a
parser_error nevű függvényt a "syntax error" stringgel, mint argumentummal. Az
alap parser_error függvény nem csinál semmit, és visszatér, ezzel elindítva a
hiba helyreállítást (lásd alább).
A felhasználó definiálhat saját parse_error függvényt a nyelvtani fájl header
szakaszában.
A parser akkor is belép a hiba helyreállítási módba, ha a nyelvtani szabályok
egyike kiváltja a Parsing.Parse_error kivételt.
A hiba helyreállítási módban a parser eldobja a veremben lévő állapotokat,
addig, amíg el nem ér egy helyet, ahol az error tokent shiftelni tudja. Ekkor
eldobja a tokeneket az inputról, addig, amíg nem talál három egymást követő
elfogadható tokent, és elkezdi feldolgozni ezek közül az elsőt. Ha nincs
felfedendő állapot, ahol az error token shiftelhető, akkor a parser abortál és
dob egy Parsing.Parse_error kivételt.
Az ocamlyacc parancs a következő opciókat ismeri fel:
Az ocamlyacc generálta parser futásidőben debuggolható a p opció beállításával az OCAMLRUNPARAM környezeti változóban. Ekkor a parser kiírja az általa végrehajtott akciókat (tokenek eltolása, szabályok redukálása, stb.). Az így kapott trace tartalmazza a szabályok és az állapotok számát, amelyek az ocamlyacc -v opciója által generált grammar.output fájl segítségével értelmezhetőek. output generated by ocamlyacc -v.
Ez a program egy egyszerű asztali számológép. Soronként egy aritmetikai kifejezést olvas be a standard input-on, és kiírja az értékét. A nyelvtani definíciója:
A megfelelő lexer definíciója:
A főprogram, amely kombinálja a parser-t a lexer-rel:
Az egész lefordítása, futtatása:
Ahhoz, hogy a generált automata kicsi maradjon, a definíciókat újra kell írni egy általános "azonosító" szabályban, ahol hashtábla keresés van az azonosítókból a kulcsszavak szétválasztására: