Az ML programozási nyelv

Az OCaml implementáció leírása

Bevezető

children playing with sleeping camel 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.

Történelmi áttekintés

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: és további kiegészítések:

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ő.

Alapok

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

Lexikális elemek

Megengedett karakterek

A nyelvben az ISO 8859-1 (Latin 1) karakterkészlet használható. A fordító megkülönbözteti a kis- és nagybetűket!

Határoló karakterek

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.

Kommentek

Csak egyfajta megjegyzés van a nyelvben, mégpedig a (* komment *). Ezek egymásba ágyazhatóak.

Azonosítók

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.

azonosito ::= ( betu _ ) { betu 0...9 _ ' }
betu ::= A ... Z a ... z

Literálok

Egész szám literál

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.

integer-literal ::= [-] ( 0...9 ) { 0...9 _ }
| [-] ( 0x 0X ) ( 0...9 A...F a...f) { 0...9 A...F a...f _ }
| [-] ( 0o 0O ) ( 0...7 ) { 0...7 _ }
| [-] ( 0b 0B ) ( 0...1 ) { 0...1 _ }
PrefixRadix
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.

Lebegőpontos szám literál
float-literal ::= [-] ( 0...9 ) { 0...9 _ } [ . { 0...9 _ } ] [ ( e E ) [ + - ] ( 0...9 ) { 0...9 _ } ]

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.

Karakter literál
char-literal ::= ' regular-char ' | ' escape-sequence ' escape-sequence ::= \ ( \ " ' n t b r ) | \ ( 0...9 ) ( 0...9 ) ( 0...9 ) | \x ( 0...9 A...F a...f ) ( 0...9 A...F a...f )

A regular-char egy normál karakter, mely eltér az ' és \ jelektől.

Speciális vezérlő karakterek:

KarakterJelentés
\\backslash (\)
\"macskaköröm (")
\'idézőjel (')
\nsortörés (LF)
\rkocsivissza (CR)
\ttabulátor (TAB)
\bvisszalépés (BS)
\dddegy karakter decimális ddd ASCII kódja
\xhhegy karakter hexadecimális hh ASCII kódja


Sztring literál
string-literal ::= " { string-character } " string-character ::= regular-char-sorozat | escape-sequence

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!

Logikai literál

A True és a False.

Kulcsszavak

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 << <: >> $ $$ $:

Cimkék

label ::= ~ ( a ... z ) { betu 0...9 _ ' } : optlabel ::= ? ( a ... z ) { betu 0...9 _ ' } :

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.

Prefix és infix szimbólumok

infix-symbol ::= ( = < > @ ^ | & + - * / $ % ) {operator-char } prefix-symbol ::= ( ! ? ~) { operator-char } operator-char ::= ! $ % & * + - . / : < = > ? @ ^ | ~

Alapszabály

A leghosszabb egyezőség elvét követi a nyelv

Nevek és névterek

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:

value-name ::= lowercase-ident | ( operator-name ) operator-name ::= prefix-symbol | infix-op infix-op ::= infix-symbol | * | = | or | & | := | mod | land | lor | lxor | lsl | lsr | asr constr-name ::= capitalized-ident label-name ::= lowercase-ident tag-name ::= capitalized-ident typeconstr-name ::= lowercase-ident field-name ::= lowercase-ident module-name ::= capitalized-ident modtype-name ::= ident class-name ::= lowercase-ident inst-var-name ::= lowercase-ident method-name ::= lowercase-ident
NévtérElső betű
Értékkisbetű
Konstruktornagybetű
Cimkekisbetű
Változó cimkenagybetű
Kivételnagybetű
Típus konstruktorkisbetű
Rekord mezőkisbetű
Osztálykisbetű
Hivatkozó változókisbetű
Metóduskisbetű
Modulnagybetű
Modul típusbármi

Változók

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ó:

        let név = kifejezés

A let kulcsszavas definíciókat lehet egymásba ágyazni:

        let név = kifejezés1 in kifejezés2

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).

Beépített típusok

Elemi típusok

Típuskonstrukciók

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:

KonstansKonstructor
falselogikai hamis
truelogikai 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átorok

OperátorJelenté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
modEgész modulus. Division_by_zero kivétel, ha 0-val modulózok.
landBitenkénti logikai ``és'' egészeken
lorBitenkénti logikai ``vagy'' egészeken
lxorBitenkénti logikai ``kizásó vagy'' egészeken
lslBitenkénti logikai shiftelés balra egészeken
lsrBitenkénti logikai shiftelés jobbra egészeken
asrBitenké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

OperatorAssociativity
Type constructor application--
*--
->right
as--

Beépített típusokhoz tartozó műveletek

Egész műveletek:

+ - * / mod, valamint a szokásos léptető, stb. műveletek a bináris reprezentáción: lsl lsr asl asr land lor lxor.

Lebegőpontos műveletek:

+. -. *. /., valamint az int_of_float és float_of_int típuskonverziók.

Mutatók

A nyelvben ilyenek nincsenek, illetve azokhoz legjobban talán a változók standard könyvtárbeli megvalósítása hasonlít.

Rendezett n-es (tuple)

Deklarálása:

expr1 , ... , exprn

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>

Változó

Létrehozása a constr konstruktorral:

constr expr

Lista

Létrehozása, melynek fejeleme lesz az expr1 és a maradék az expr2 lesz:

expr1 :: expr2 [ expr1 ; ... ; exprn ] expr1 :: ... :: exprn :: []

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.

Rekord

{ field1 = expr1 ; ... ; fieldn = exprn }

Új rekord építése, melynek egyes mezői megváltoznak:

{ expr with field1 = expr1 ; ... ; fieldn = exprn }

Rekord mezőjére való hivatkozás:

expr1.field

Értékadás egy adott mezőnek:

expr1.field <- expr2

A kifejezés értéke () lesz. Ez csak akkor működik ha a field mező mutable típusú.

Tömb

Létrehozása:

[| expr1 ; ... ; exprn |]

Adott indexű elemére hivatkozás:

expr1.(expr2)

Értékadás egy adott indexű elemnek:

expr1.(expr2) <- expr3

A kifejezés értéke () lesz. Érvénytelen index: Invalid_argument kivételt vált ki!

Sztring

Adott indexű elemének elérése:

expr1.[expr2]

Értékadás egy adott indexű elemnek:

expr1.[expr2] <- expr3

A kifejezés értéke () lesz. Érvénytelen index: Invalid_argument kivételt vált ki!

Objektum:

Objektum létrehozás:

new class-path

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:

expr # method-name

Belső változó elérése és módosítása:

inst-var-name inst-var-name <- expr

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:

( expr :> typexpr ) ( expr : typexpr1 :> typexpr2 )

Típus kifejezések

typexpr ::= ' ident | _ | ( typexpr ) | [[?]label-name:] typexpr -> typexpr | typexpr { * typexpr }+ | typeconstr | typexpr typeconstr | ( typexpr { , typexpr } ) typeconstr | typexpr as ' ident | variant-type | < [..] > | < method-type { ; method-type } [; ..] > | # class-path | typexpr # class-path | ( typexpr { , typexpr } ) # class-path poly-typexpr ::= typexpr | { ' ident }+ . typexpr method-type ::= method-name : poly-typexpr

Típus változó

'ident _ anonymous típusú változó

Zárójel típus ( typexpr )

Megegyezik az eredeti typexpr típussal

Függvény típus: typexpr1 -> typexpr2

label-name : typexpr1 -> typexpr2
optlabel typexpr1 -> typexpr2

(* Ez utóbbinak fizikai típusa lesz: *)
typexpr1 option -> typexpr2

Rendezett n-es típus: typexpr1 * ... * typexprn
ahol az i. elem típusa typexpri

Konstruktor típus:

paraméter nélküli: typeconstr 1 paraméteres: typexpr typeconstr n paraméteres: (typexpr1,..., typexprn) typeconstr

Objektum típus:

< method-type { ; method-type } >

Konstans típus:

constant ::= integer-literal | float-literal | char-literal | string-literal | constr | false | true | [] | () | `tag-name

Minta (pattern):

pattern ::= value-name | _ | constant | pattern as value-name | ( pattern ) | ( pattern : typexpr ) | pattern | pattern | constr pattern | `tag-name pattern | #typeconstr-name | pattern { , pattern } | { field = pattern { ; field = pattern } } | [ pattern { ; pattern } ] | pattern :: pattern | [| pattern { ; pattern } |]

Sablon típus

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

Típus definiálás

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

type-definition ::= type typedef { and typedef } typedef ::= [type-params] typeconstr-name [type-information] type-information ::= [type-equation] [type-representation] { type-constraint } type-equation ::= = typexpr type-representation ::= = constr-decl { | constr-decl } | = { field-decl { ; field-decl } } type-params ::= type-param | ( type-param { , type-param } ) type-param ::= ' ident | + ' ident | - ' ident constr-decl ::= constr-name | constr-name of typexpr field-decl ::= field-name : poly-typexpr | mutable field-name : poly-typexpr type-constraint ::= constraint ' ident = typexpr

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ó

exception-definition ::= exception constr-name [of typexpr]
| exception constr-name = constr

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.

Vezérlési szerkezetek

Szekvencia

expr1 ; expr2

Elágazás

if expr1 then expr2 else expr3
Az else ág elhagyható, alapértelmezésben: else ()

Case szerkezet

match expr
with pattern1 -> expr1 | ... | patternn -> exprn

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!

Logikai operátorok

És:
&& & expr1 && expr2

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:

if expr1 then true else expr2
Vagy:
|| or expr1 || expr2

Ugyanúgy igaz mint az előbb, ha expr1 igaz, akkor az expr2 kifejezés nem fut le.

Ekvivalens kifejezés:

if expr1 then true else expr2

Ciklusok

while
while expr1 do expr2 done
for
for name = expr1 to expr2 do expr3 done for name = expr1 downto expr2 do expr3 done

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.

Alprogramok

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.

Kivételkezelés

try expr
with pattern1 -> expr1
| ...
| patternn -> exprn

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!

Források

Felelősök

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.

OOP

Osztályok és objektumok

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.

Névtelen osztályok

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.

A self-referencia

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>

Inicializátorok

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.

Virtuális metódusok

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

Privát metódusok

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

Osztály interfészek

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

Öröklődés

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>

Többszörös öröklődés

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.

Fejlesztői eszközök

Az interaktív (toplevel) rendszer (ocaml)

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:

toplevel-input ::= {toplevel-phrase} ;;
toplevel-phrase ::= definition
| expr
| # ident directive-argument
directive-argument ::= E
| string-literal
| integer-literal
| value-path

Egy kifejezés állhat egy definícióból, hasonlóan azokhoz, amelyeket a fordítási egységekben vagy a struct...end modul kifejezésekben találunk. A definíció köthető érték, típus, kivétel, modul vagy modul típus nevéhez. A toplevel rendszer végrehajtja a kötést az így definiált nevekhez és kiírja a típust, és ha van, akkor az értéket is.
Egy kifejezés ezenkívül állhat egy nyitott direktívában vagy egy érték kifejezésben is. A kifejezéseket egyszerűen kiértékeli a kötések végrehajtása nélkül és kiírja az értéküket.
Végül egy kifejezés állhat egy toplevel direktívában is a # jellel kezdve. Ezek a direktívák vezérlik a toplevel viselkedését.

Unix:
A toplevel rendszer az ocaml parancsal indítható a következő módon:

ocamlopciók objektumok# interaktív mód
ocamlopciók objektumok szkriptfájl# szkript mód

Az opciók leírása lejjebb található. Az objektumok .cmo vagy .cma kiterjesztésű fájlokban lehetnek, melyeket az opciók beállítása után azonnal betöltődnek az interpreterbe. A szkriptfájl akármilyen fájlnév lehet, aminek a kiterjesztése nem .cmo vagy .cma.

Ha nincs szkriptfájl megadva a parancssorban, akkor a toplevel rendszer interaktív módba lép: beolvassa a kifejezéseket a standard inputról és az eredményeket kiírja a standard outputra, a hibákat pedig a standard error kimenetre. A fájl vége (end-of-file) a standard bemeneten leállítja az ocaml-t (lásd még a #quit direktívát).

Induláskor (még az első kifejezés beolvasása előtt), ha megtalálható az aktuális könyvtárban a .ocamlinit fájl, akkor a tartalma Objective Caml kifejezések sorozataként beolvasásra, majd a #use direktívának megfelelő végrehajtásra kerül. A kiértékelés kimenete nem jelenik meg. Ha az aktuális könyvtár nem tartalmazza a .ocaml fájlt, de a felhasználó home könyvtára (a HOME környezeti változó határozza meg) igen, akkor megtörténik az előbb leírt beolvasás és végrehajtás.

A toplevel rendszer nem végez sor editálást, de könnyen összekapcsolható külső sor editorokkal, mint a ledit, ocaml2 vagy rlwrap. Másik lehetőség, az ocaml Gnu Emacs alóli használata, amely biztosítja annak teljes szerkesztési erejét (run-caml parancs az inf-caml könyvtárból).

Az aktuális kifejezés parszolása, fordítása vagy kiértékelése akármikor megállítható a ctrl-C megnyomásával (vagy precízebben megfogalmazva a INTR szignál elküldésével az ocaml processznek). A toplevel ekkor azonnal visszatér a # prompthoz.

Ha szkriptfájl is van megadva a parancssorban az ocaml-nak, akkor a toplevel rendszer szkript módba lép be: a fájl tartalma Objective Caml kifejezések sorozataként kerül beolvasásra és végrehajtásra a #use direktívának megfelelően. A kiértékelés kimenete nem kerül kiírásra. A fájl végének az elérésekor az ocaml parancs azonnal kilép. Ekkor nem történik parancs beolvasás a standard inputról. A Sys.argv transzformálásra kerül, felülírva az Objective Caml paramétereket, majd indításra kerül a Sys.argv.(0)-ban lévő szkriptfájl névvel.

Szkript módban amennyiben a szkript első sora #! karakterekkel kezdődik figyelmen kívül lesz hagyva. Így lehetséges önmagát futtató szkriptet készíteni, az első sorba a #!/usr/local/bin/ocaml stringet helyezve, mivel így automatikusan meghívódik a toplevel rendszer a szkript futtatásakor, annak ellenére, hogy az ocaml maga is egy #! szkript a legtöbb Objective Caml telepítésben és a Unix kernelek gyakran nem kezelik a beágyazott #! szkripteket. Egy jobb megoldás, szkript első sorába a következő sor elhelyezése:

#!/usr/local/bin/ocamlrun /usr/local/bin/ocaml

Windows:
A szöveges ocaml.exe pontosan ugyanúgy működik, mint a Unix-os (lásd fent), és egy grafikus interfész is elérhető a toplevel-hez ocamlwin.exe néven. Ez a Windows fájlmenedzserből vagy a program menedzserből indítható. Ez az interfész egy szöveges ablakot biztosít, amelybe beírhatóak és szerkeszthetőek a parancsok, valamint ide kerülnek kiírásra a toplevel válaszok is.
Opciók

Az ocaml parancs a következő parancssori paramétereket ismeri:

-I könyvtár
A megadott könyvtárat hozzáadja a források és a lefordított fájlok kereséséhez használt könyvtárak listájához. Alapból az aktuális könyvtárban keres először, aztán a standard library könyvtárban. A -I-vel hozzáadott könyvtárakban az aktuális könyvtár után fog keresni, a parancssorban megadott sorrendben, megelőzve a standard library könyvtárat.

Ha a megadott könyvtár + jellel kezdődik, akkor  azt a standard library könyvtárhoz képest relatívan veszi. Pl. a -I +labltk hozzáadja a standard library könyvtár labltk alkönyvtárát a keresési útvonalhoz.

A könyvtárakat a listához a toplevel #directory direktívával történő futtatásával is hozzá lehet adni.

-init fájl
A megadott fájlt tölti be az alap inicializációs fájl helyett. Az alap fájl az aktuális könyvtárban lévő .ocamlinit (ha létezik), egyébként pedig a felhasználó home könyvtárában található .ocamlinit fájl.

-labels
A címkék nincsenek figyelmen kívül hagyva a típusokban, címkéket kell használni az alkalmazásban és a címkézett paraméterek tetszőleges sorrendben adhatóak meg. Ez az alap.

-noassert
Nem fordítja az állítás ellenőrzéseket. A false állítást mindig fordítja, mivel speciálisan típusozott.

-nolabels
Figyelmen kívül hagyja a nem opcionális címkéket a típusokban. A címkék nem használhatóak alkalmazásokban és a paraméter sorrend is szigorúan rögzített.

-noprompt
Nem mutat semmilyen promptot, amikor inputra vár.

-nostdlib
Kihagyja a standard library könyvtárat a források és lefordított fájlok kereséséhez használt könyvtárak listájából.

-principal
Ellenőrzi az információs útvonalat típusellenőrzés közben, hogy megbizonyosodjon afelől, hogy az összes típus az elsődleges úton lett származtatva. Amikor címkézett argumentumokat és/vagy polimorfikus metódusokat használunk, ez az opció szükséges annak biztosítására, hogy a fordító későbbi verziói is képesek legyenek a típusokat korrekten kikövetkeztetni, még ha a belső algoritmusok meg is változnának. Minden program, ami elfogadott -principal módban, az elfogadott alap módban is ekvivalens típusokkal, de eltérő bináris szignatúrával, és ez lelassítja a típusellenőrzést. Jó ötlet ennek a használata egyszer, a forráskód publikálása előtt.

-rectypes
Engedélyezi a korlátlan rekurzív típusokat a típusellenőrzés során. Alapból csak olyan rekurzív típusok támogatottak, ahol a rekurzió egy objektum típuson megy keresztül.

-unsafe
Kikapcsolja a határok ellenőrzését tömb és string hozzáféréseknél (a v.(i) és az s.[i] szerkezetek). Az ezzel az opcióval fordított programok ezért némileg gyorsabbak, de nem biztonságosak. Ha a program egy tömbhöz vagy stringhez annak határain kívül szeretne hozzáférni, akkor bármi megtörténhet.

-version
Kiírja a verziót és kilép.

-w warning-list
A megadott listától függően engedélyezi vagy tiltja a warning-okat.

-warn-error warning-list
A megadott listában engedélyezett figyelmeztetéseket hibaként kezeli.

-help vagy --help
Megjelenít egy rövid használati összegzést és kilép.

Unix:
A következő környezeti változók szintén használhatóak:

LC_CTYPE
Ha iso_8859_1-re van beállítva, akkor az ékezetes karaktereket (az ISO Latin-1 karakterkészletből) a stringekben és a karakter literálokban úgy fogja kiírni, ahogy vannak, egyébként pedig decimális escape szekvenciaként (\ddd).

TERM
Hibaüzenetek kiírásakor a toplevel rendszer megpróbálja aláhúzni vizuálisan a hiba helyét. A TERM változóból próbálja meghatározni a kimeneti terminál típusát, és a terminál adatbázisból kikeresi az adottságait.

HOME
A könyvtár, ahol az .ocamlinit fájlt keresi.
Direktívák

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.

#quit;;
Kilép a toplevel hurokból és leállítja az ocaml parancsot.

#labels bool;;
Figyelmen kívül hagyja a címkéket a funkció típusokban, ha az argumentum false vagy visszakapcsol az alap viselkedésre (kicserélő stílus), ha az argumentum true.

#warnings "warning-list";;
A megadott listától függően engedélyezi vagy tiltja a figyelmeztetéseket.

#directory "könyvtár név";;
A megadott könyvtárat hozzáadja a források és lefordított fájlok kereséséhez használt könyvtárak listájához.

#cd "könyvtár név";;
Megváltoztatja az aktuális munkakönyvtárat.

#load "fájl név";;
Betölt egy az ocamlc batch fordítóval előállított bájtkód objektum fájlt (.cmo) a memóriába.

#use "fájl név";;
Beolvassa, lefordítja és futtatja a megadott fájlban lévő forrás kifejezéseket. A kifejezések feldolgozása szövegesen történik, mintha csak a standard inputon írták volna be őket. A fájl beolvasása az első hibánál megáll.

#install_printer printer név;;
Ez a direktíva regisztrálja a printer név függvényt, mint egy printer azoknak az értékeknek, amelyeknek a típusa megfelel a függvény argumentum típusának. A toplevel hurok, amikor ilyen kiírandó érték, ezt a printer nevet fogja meghívni.

A printer név kiíró függvénynek Format.formatter ->t -> unit típusúnak kell lennie, ahol t a kiírandó értékek típusa, és a t típus értékének szöveges reprezentációja a kimenet a megadott formatteren, a Format library által biztosított függvényeket használva. A visszafele kompatibilitás miatt a printer név lehet t-> unit típusú is és akkor a kimenet a standard formatter, de ez a használat érvénytelenített.

#remove_printer printer név;;
Eltávolítja a megadott nevű függvényt a toplevel printerek táblázatából.

#trace függvény név;;
Ennek a direktívának a végrehajtása után minden függvény név nevű függvényhívás nyomkövetve lesz. Ekkor az argumentum és az eredmény minden hívásnál megjelenítésre kerül, valamint a kivételek is függetlenül attól, hogy a függvény maga, vagy egy általa hívott másik függvény váltotta-e ki. Ha a függvény curry-zett, minden argumentum úgy kerül kiírásra, ahogy a függvénynek át lett adva.

#untrace függvény név;;
Leállítja a megadott függvény nyomkövetését.

#untrace_all;;
Az összes függvény nyomkövetését leállítja.

#print_depth n
Az értékek kiírását n mélységben maximalizálja. Az értékek azon részei, amelyek meghaladják az n mélységet ...-ként kerülnek kiírásra.

#print_length n
A kiírt érték nod-ok számát n-ben maximalizálja. Az értékek maradék részei ...-ként kerülnek kiírásra.
A toplevel és a modul rendszer

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.

Általános hibák

A leggyakrabban előforduló hibaüzenetek leírása a következő:

Cannot find file fájlnév
A megnevezett fájl nem található sem az aktuális könyvtárban, keresési útvonal könyvtáraiban.

Ha a fájlnév mod.cmi formájú, akkor ez azt jelenti, hogy a mod fordítási egységre hivatkozol, de a lefordított interfész nem található. Javítás: Először fordítsd le a mod.mli-t vagy a mod.ml-t, hogy elkészüljön a lefordított mod.cmi interfész.

Ha a fájlnév mod.cmo formájú, akkor ez azt jelenti, hogy a #load direktívával próbálsz betölteni egy bájtkód objektum fájlt, ami még nem létezik. Javítás: Először fordítsd le a mod.ml-t.

Ha a programod több könyvtárat is használ, akkor ez a hiba azért is lehet, mert nem adtad meg a könyvtárakat a kereséshez. Javítás: használd a #directory direktívát a megfelelő könyvtárak keresési útvonalhoz történő hozzáadásához.

This expression has type t1, but is used with type t2
Ez a legáltalánosabb típus hiba a programokban. A t1 típus a kifejezés kikövetkeztetett típusa (a program része, amely megjelenik a hibaüzenetben), ránézve a kifejezés maga. A t2 típus a kifejezés kontextusa által elvárt típus, melynek levezetése úgy történik, hogy megnézi, hogy ennek a kifejezésnek az értéke hogyan van használva a program maradékában. Ha a t1 és t2 típus nem kompatibilis, akkor a fenti hibaüzenetet kapjuk.

Néhány esetben nehéz megérteni, hogy a t1 és a t2 típusok miért inkompatibilisek egymással. Pl. a fordító azt jelzi, hogy, "expression of type foo cannot be used with type foo", és valóban úgy tűnik, hogy a két foo típus kompatibilis. Ez nem mindig igaz. Két típuskonstruktornak lehet ugyanaz a neve, de aktuálisan különböző típusokat is reprezentálhatnak. Ez történhet, ha a típuskonstruktort újradefiniáljuk. Példa:

type foo = A | B
let f = function A -> 0 | B -> 1
type foo = C | D
f C

Ez az "expression C of type foo cannot be used with type foo" hibaüzenetet eredményezi.

Reference to undefined global mod
El kell kerülni egy modul implementációjának #load-al történő betöltését a memóriába.
Saját toplevel rendszer építése (ocamlmktop)

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;;

Opciók

Az ocamlmktop parancs a következő parancssori paramétereket ismeri:

-cclib lib név
Átadja a -llibname opciót a C linkernek, amikor "custom runtime" módban linkel.

-ccopt opció
Átadja a megadott opciót a C fordítónak és a linkernek, amikor "custom runtime" módban linkel.

-custom
Linkelés "custom runtime" módban.

-I könyvtár
A megadott könyvtárat hozzáadja a források és a lefordított objektum kód fájlok (.cmo és .cma) kereséséhez használt könyvtárak listájához.

-o futtatható fájl
A linker által előállított toplevel fájl nevét adja meg. Az alap a.out.

A böngésző és editor (ocamlbrowser)

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:

Használata

A böngészőt az ocamlbrowser parancs kiadásával lehet elindítani

ocamlbrowser opciók

A következő parancssori opciók használhatóak

-I könyvtár
A megadott könyvtárat hozzáadja a források, és lefordított fájlok kereséséhez használ könyvtárak listájához. Alapból csak a standard library könyvtárban keres. A standard library megváltoztatható a OCAMLLIB környezeti változó beállításával.
-nolabels
Figyelmen kívül hagyja a nem opcionális címkéket a típusokban. A címkék nem lesznek használhatóak az alkalmazásokban és a paraméter sorrendje kötötté válik.

-oldui
A régi többablakos interfész. Az alap most jobban hasonlít a Smalltalk osztály-böngészőjéhez.

-rectypes
Megengedi a korlátlan rekurzív típusokat a típusellenőrzés során. Alapból csak olyan rekurziv típusok támogatottak, ahol a rekurzió egy objektum típuson megy keresztül.

-version
Kiírja a verziót és kilép.

-w warning-list
A megadott listától függően engedélyezi vagy tiltja a warning-okat.

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.

Nézegető

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:

Modul böngésző

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ájl editor

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.

Shell

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.

Lexer és parser generátorok (ocamllex, ocamlyacc)

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 áttekintése

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

ocamllex lexer.mll

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.

Opciók

Az ocamllex parancs a következő parancssori paramétereket ismeri:

-ml
Kimeneti kód, ami nem használja az OCaml beépített automata interpreterét. Az automata helyett Caml függvényekkel van kódolva. Ez az opció főleg az ocamllex debuggolásakor hasznos, éles lexerekhez nem ajánlott.

-o output fájl
Megadja az ocamllex által előállított kimeneti fájl nevét. Alapértelmezés az input fájl neve, a kiterjesztését .ml-re cserélve.

-q
Csendes mód. Az ocamllex normálisan információs üzeneteket ír ki a standard output-ra. A -q opció használatával ezek elnyomhatóak.

-v vagy -version
Kiírja a verziót és kilép.

-help vagy --help
Megjelenít egy rövid használati összefoglalót és kilép.

Lexer definíciók szintaxisa

A lexer definíciók formája a következő:

{ header }
let ident = regexp ...
rule entrypoint [arg1... argn] =
parse regexp { action }
| ...
| regexp { action }
and entrypoint [arg1... argn] =
parse ...
and ...
{ trailer }

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.

Header és trailer

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.

Reguláris kifejezések elnevezése

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.

Belépési pontok

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.

Reguláris kifejezések

A reguláris kifejezések lex stílusúak, egy kicsit több Caml szerű szintaxissal.

regexp ::= ...

'regulár-karakter | escape szekvencia'
Egy karakter konstans, ugyanazzal a szintaxissal, mint az Objective Caml karakter konstansok. A megjelölt karakterre illeszkedik.

_
(Aláhúzás) Akármilyen karakterre illeszkedik.

eof
A lexer input végére illeszkedik.
Megjegyzés: Néhány rendszeren az interaktív inputnál az end-of-file-t (fájl vége), több karakter is követheti. Az ocamllex nem fogja megfelelően kezelni azokat a reguláris kifejezéseket, amelyek az eof után mást is tartalmaznak.

"{string-karakter}"
Egy string konstans, ugyanazzal a szintaxissal, mint az Objective Caml string konstansok. A megegyező karakter-sorozatra illeszkedik.

[karakterhalmaz]
Akármelyik egyedi karakterre illeszkedik, amelyik megtalálható a megadott karakterhalmazban. Érvényes karakterhalmazok: egyedülálló karakter konstans 'c'; karakterek tartománya 'c1' - 'c2' (minden karakter a c1 és c2 között, beleértve a széleket is); két vagy több karakterhalmaz uniója, konkatenációval megjelölve.

[^ karakterhalmaz]
Akármelyik egyedi karakterre illeszkedik, amelyik nem található meg a megadott karakterhalmazban.

regexp1 # regexp2
(Karakterhalmazok különbsége.) A regexp1 és regexp2 reguláris kifejezéseknek karakterhalmazoknak kell lenniük, [...] módon definiálva (vagy egy különálló karakter kifejezésnek vagy aláhúzásnak _). A két megadott karakterhalmaz különbségére illeszkedik.

regexp *
(Ismétlődés.) Nulla vagy több, a regexp-re illeszkedő string konkatenációjára illeszkedik.

regexp +
(Szigorú ismétlődés.) Egy vagy több, a regexp-re illeszkedő string konkatenációjára illeszkedik.

regexp ?
(Opcionális.) Vagy egy üres stringre, vagy a regexp-re illeszkedő stringre illeszkedik.

regexp1 | regexp2
(Alternatív.) Akármelyik stringre illeszkedik, amely vagy a regexp1-re vagy a regexp2-re illeszkedik.

regexp1 regexp2
(Konkatenáció.) Két string konkatenációjára illeszkedik, ahol az első a regexp1-re, a második a regexp2-re illeszkedik.

( regexp )
Ugyanarra a stringre illeszkedik, mint a regexp.

ident
Az azonosítóhoz (ident) korábban a let ident = regexp definícióval kötött reguláris kifejezésre hivatkozik.

regexp as ident
A regexp-re illeszkedő substringet az ident azonosítóhoz köti.

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.

Akciók

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.

Lexing.lexeme lexbuf
Visszaadja az illesztett stringet.

Lexing.lexeme_char lexbuf n
Visszaadja az illesztett string n-edik karakterét. Az első karakter eléréséhez n = 0.

Lexing.lexeme_start lexbuf
Visszaadja az illesztett string kezdetének az abszolút pozícióját az input szövegben.

Lexing.lexeme_end lexbuf
Visszaadja az illesztett string végének az abszolút pozícióját az input szövegben. Az input szöveg első beolvasott karakterének a pozíciója 0.

entrypoint [exp1...expn] lexbuf
(Ahol az entrypoint egy másik belépési pont neve ugyanabban a lexer definícióban.) Rekurzívan meghívja a lexer-t a megadott belépési ponton. Például beágyazott megjegyzések feldolgozásakor (lexing) lehet hasznos.

Változók a reguláris kifejezésekben

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.

Fentartott azonosítók

Minden __ocaml_lex-el kezdődő azonosító fenntartott az ocamllex általi használatra, ezért ezeket ne használjuk a programunkban.

Az ocamlyacc áttekintése

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

ocamlyacc options grammar.mly

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 szintaxisa

A nyelvtani definíciók formája a következő:

%{ header %} declarations %% rules %% trailer

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.

Header és trailer

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.

Deklarációk

Soronként egy deklaráció adható meg. Az első karakterük a % jel.

%token constr...constr
Tokenként (terminális szimbólum) deklarálja a megadott constr...constr szimbólumokat. Ezek a szimbólumok, mint konstans konstruktorok vannak hozzáadva a token konkrét típusához.

%token <typexpr> constr...constr
A megadott típus attribútumával csatolt tokenként deklarálja a megadott constr...constr szimbólumokat. Ezek a szimbólumok, mint konstruktorok a megadott típus argumentumával vannak hozzáadva a token konkrét típusához. A typexpr egy tetszőleges Caml típuskifejezés része, kivéve, hogy minden konstruktor nevének "fully qualified"-nek (pl. Modname.typename) kell lennie a típusokra, kivéve standard beépített típusokat, még akkor is, ha a megfelelő open direktívák (pl. open Modname) meg lettek adva a header szakaszban. Ez azért van, mert a header csak a .ml output fájlba van bemásolva, a .mli-be nem, míg a %token deklaráció typexpr része mindkettőbe.

%start symbol...symbol
A megadott szimbólumokat belépési pontokként deklarálja a nyelvtannak. Minden belépési ponthoz, ugyanazzal a névvel egy parsing függvény van definiálva az output modulban. A nemterminálisoknak, amelyek nincsenek belépési pontként deklarálva, nincs ilyen parsing függvényük sem. A start szimbólumoknak meg kell adni egy típust a %type direktívával.

%type <typexpr> symbol...symbol
A megadott szimbólumokhoz specifikálja a szemantikus attribútumok típusát. Ez csak a start szimbólumokhoz kötelező. Más, nemterminális szimbólumoknak nem kell kézzel megadni a típusukat, mivel azoknak akkor lesz kikövetkeztetve, amikor az output fájl átfut az Objective Caml fordítón (hacsak nem a -s opcióval használjuk). A typexpr egy tetszőleges Caml típuskifejezés része, kivéve hogy minden típuskonstruktor nevének "fully qualified"-nek kell lennie, ugyanúgy, mint a %token esetén.

%left symbol...symbol
%right symbol...symbol
%nonassoc symbol...symbol
Precedenciákat és asszociativitásokat kapcsol a megadott szimbólumokhoz. Az ugyanabban a sorban megadott szimbólumoknak ugyanaz lesz a precedenciájuk. Magasabb a precedenciájuk, mint azoknak a szimbólumoknak, amelyek egy megelőző %left, %right vagy %nonassoc sorban lettek deklarálva. Alacsonyabb a precedenciájuk, mint azoknak a szimbólumoknak, amelyek egy későbbi %left, %right vagy %nonassoc sorban lettek deklarálva. A szimbólumok asszociativitását bal (%left), jobb (%right), vagy nem asszociatívnak (%nonassoc) lehet deklarálni. A szimbólumok gyakran tokenek. Lehetnek látszólagos nem terminálisok is a %prec direktívával használatához a szabályok belsejében.

A precedencia deklarációkat a következő módon használjuk a reduce/reduce és a shift/reduce konfliktusok feloldásáért:

  • A tokeneknek és a szabályoknak van precedenciája. Alapból egy szabály precedenciája a legjobboldalibb terminálisának a precedenciája. Ez felülírható a %prec direktíva használatával a szabályban.

  • Egy reduce/reduce konfliktus az első szabálynak (a forráskódban megadott sorrendben) kedvezve kerül feloldásra, és az ocamlyacc kiír egy figyelmeztetést.

  • Egy shift/reduce konfliktus a redukálandó szabály és az eltolandó token precedenciájának összehasonlításával lesz feloldva. Ha a szabály precedenciája magasabb, akkor a szabály lesz redukálva, ha a token precedenciája magasabb, akkor a token lesz eltolva.

  • Megegyező precedenciával rendelkező szabály és token közti shift/reduce konfliktus az asszociativitás használatával lesz feloldva: ha a token bal asszociatív, akkor a parser redukálni fog, ha a token jobb asszociatív, akkor a parser shiftelni fog. Ha a token nem asszociatív, akkor a parser egy szintaktikai hibát fog deklarálni.

  • Ha egy shift/reduce konfliktus nem oldható fel a fenti módon, akkor az ocamlyacc kiír egy figyelmeztetést és a parser mindig shiftelni fog.



Szabályok

A szabályok szintaxisa

nonterminal :
symbol ... symbol { semantic-action }
| ...
| symbol ... symbol { semantic-action }
;

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.

Hibakezelés

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.

Opciók

Az ocamlyacc parancs a következő opciókat ismeri fel:

-b prefix
A kimeneti fájlok neve prefix.ml, prefix.mli és prefix.output lesz, szemben az alap elnevezési konvencióval.

-q
Ennek az opciónak nincs hatása.

-v
Legenerálja a parsing táblák leírását, valamint egy riportot a nyelvtan félreérthetőségéből adódó konfliktusokról. A leírást a grammar.output fájlba teszi.

-version
Kiírja a verziót és kilép.

-version
Kiírja a verziót és kilép.

-
Beolvassa a nyelvtani specifikációt a standard input-ról. Az alap output fájlok nevei stdin.ml és stdin.mli.

-- file
Nyelvi specifikációként dolgozza fel a file-t, még ha a neve gondolatjellel (-) is kezdődik. Ennek az opciónak az utolsónak kell lennie a parancssorban.

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.

Egy komplett példa

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:

/* File parser.mly */
%token <int> INT
%token PLUS MINUS TIMES DIV
%token LPAREN RPAREN
%token EOL
%left PLUS MINUS /* legalacsonyabb precedencia */
%left TIMES DIV /* közepes precedencia */
%nonassoc UMINUS /* legmagasabb precedencia */
%start main /* a belépési pont */
%type <int> main
%%
main:
expr EOL { $1 }
;
expr:
INT { $1 }
| LPAREN expr RPAREN { $2 }
| expr PLUS expr { $1 + $3 }
| expr MINUS expr { $1 - $3 }
| expr TIMES expr { $1 * $3 }
| expr DIV expr { $1 / $3 }
| MINUS expr %prec UMINUS { - $2 }
;

A megfelelő lexer definíciója:

(* File lexer.mll *)
{
open Parser (* A típus token a parser.mli fájlban van definiálva *)
exception Eof
}
rule token = parse
[' ' '\t'] { token lexbuf } (* üresek figyelmen kívül hagyása *)
| ['\n' ] { EOL }
| ['0'-'9']+ as lxm { INT(int_of_string lxm) }
| '+' { PLUS }
| '-' { MINUS }
| '*' { TIMES }
| '/' { DIV }
| '(' { LPAREN }
| ')' { RPAREN }
| eof { raise Eof }

A főprogram, amely kombinálja a parser-t a lexer-rel:

(* File calc.ml *)
let _ =
try
let lexbuf = Lexing.from_channel stdin in
while true do
let result = Parser.main Lexer.token lexbuf in
print_int result; print_newline(); flush stdout
done
with Lexer.Eof ->
exit 0

Az egész lefordítása, futtatása:

ocamllex lexer.mll # lexer.ml generálása
ocamlyacc parser.mly # parser.ml és parser.mli generálása
ocamlc -c parser.mli
ocamlc -c lexer.ml
ocamlc -c parser.ml
ocamlc -c calc.ml
ocamlc -o calc lexer.cmo parser.cmo calc.cmo
Általános hibák
ocamllex: transition table overflow, automaton is too big
Az ocamllex által generált determinisztikus automata legfeljebb 32767 átmenetben limitált. A fenti hibaüzenet arra utal, hogy az általad definiált lexer túl komplex és meghaladja ezt a határt. Ezt általában akkor okozzák a lexer definíciók, ha a nyelv összes alfabetikus kulcsszavához külön szabály van, mint ahogy a következő példában is.

rule token = parse
"keyword1" { KWD1 }
| "keyword2" { KWD2 }
| ...
| "keyword100" { KWD100 }
| ['A'-'Z' 'a'-'z'] ['A'-'Z' 'a'-'z' '0'-'9' '_'] * as id
{ IDENT id}

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:

{ let keyword_table = Hashtbl.create 53
let _ =
List.iter (fun (kwd, tok) -> Hashtbl.add keyword_table kwd tok)
[ "keyword1", KWD1;
"keyword2", KWD2; ...
"keyword100", KWD100 ]
}
rule token = parse
['A'-'Z' 'a'-'z'] ['A'-'Z' 'a'-'z' '0'-'9' '_'] * as id
{ try
Hashtbl.find keyword_table id
with Not_found ->
IDENT id }

ocamllex: Position memory overflow, too many bindings
Az ocamllex által generált determinisztikus automata karbantart egy táblázatot az átnézett lexer pufferbeni pozíciókról. Ennek a táblázatnak a mérete maximum 255 cellában limitált. Normál szituációkban ennek a hibának nem szabadna előjönnie.