• Non ci sono risultati.

Meta-programmazione di binding OCaml per GTK3

N/A
N/A
Protected

Academic year: 2021

Condividi "Meta-programmazione di binding OCaml per GTK3"

Copied!
88
0
0

Testo completo

(1)

SCUOLA DI SCIENZE

Corso di Laurea in Informatica Magistrale

Meta-programmazione di binding OCaml

per GTK3

Relatore:

Chiar.mo Prof.

Claudio Sacerdoti Coen

Presentata da:

Alberto Nicoletti

Sessione III

(2)

GTK `e una delle principali librerie per costruire interfacce grafiche, ed essendo scritta in C sono molti i linguaggi che realizzano dei binding a questa libreria. GTK dispone di un sistema di introspezione in grado di generare un file XML che descrive l’intera API della libreria. Tramite questo meccanismo per numerosi linguaggi sono state realizzate delle librerie in grado di leggere questo file XML e generare automaticamente i binding per GTK. Nel caso di OCaml invece esiste la libreria lablgtk, la quale `e per`o scritta a mano: questo permette di avere una API pi`u vicina ai costrutti idiomatici del linguaggio, ma incompleta, sensibile ai cambi di versione e prona ad errori. L’obiettivo raggiunto in questa tesi `e la realizzazione di un prototipo funzionante in grado di generare auto-maticamente i binding di GTK per OCaml. Il prototipo `e stato ottenuto modificando haskell-gi, la libreria analoga per Haskell, un linguaggio simile ad OCaml che ha dovuto affrontare problematiche simili. In particolare questa libreria `e composta di due moduli principali, uno per la lettura del file XML, ed uno per la generazione del codice Haskell a partire dalle informazioni ottenute dal parsing. In ocaml-gi-gtk, la libreria creata in questa tesi, `e stato riusato completamente il modulo di parsing, ma `e stato riscritto il modulo di generazione del codice per generare codice OCaml invece che Haskell.

(3)

1 Introduzione 1

2 Background e strumenti usati 4

2.1 GTK . . . 4

2.1.1 GLib . . . 5

2.1.2 GObject Introspection . . . 7

2.2 OCaml . . . 8

2.2.1 Moduli . . . 8

2.2.2 Algebraic Data Types (ADT) . . . 8

2.2.3 Polimorfismo . . . 10

2.2.4 Varianti Polimorfe . . . 13

2.2.5 Garbage collector . . . 14

2.3 Meccanismi di interfacciamento tra C e OCaml . . . 14

2.3.1 Binding a delle primitive . . . 14

2.3.2 Il tipo value . . . 16

2.3.3 Rappresentazione di tipi di dato OCaml . . . 17

2.3.4 Cooperare con il garbage collector . . . 19

2.3.5 Callback . . . 20

2.4 lablgtk . . . 21

2.4.1 Binding alle classi di GTK . . . 21

2.4.2 Limiti di lablgtk . . . 24

2.5 haskell-gi . . . 24

2.5.1 Type . . . 25

(4)

2.5.2 Moduli . . . 28

3 Struttura di ocaml-gi-gtk 29 3.1 Reperibilit`a del codice . . . 29

3.2 Struttura del progetto . . . 30

3.3 Definizione delle librerie generabili . . . 31

3.4 Esecuzione di ocaml-gi-gtk . . . 32

3.5 Struttura delle librerie generate . . . 33

4 Implementazione 36 4.1 Impostazione della generazione . . . 36

4.1.1 Esecuzione del generatore . . . 38

4.2 Conversione di tipi tra OCaml e C . . . 40

4.3 Enum e Flags . . . 41 4.3.1 Rappresentazione in lablgtk3 . . . 41 4.3.2 Generazione in ocaml-gi-gtk . . . 43 4.4 Oggetti . . . 45 4.4.1 Macro di conversione . . . 46 4.4.2 Segnali . . . 48 4.4.3 Propriet`a . . . 50 4.4.4 Metodi . . . 54 4.4.5 Costruttori . . . 61 4.4.6 Ereditariet`a . . . 65 4.4.7 Implementazione di interfacce . . . 66 4.4.8 Dipendenze cicliche . . . 67 4.5 Interfacce . . . 69 5 Valutazione 72 5.1 Limitazioni e lavori futuri . . . 72

5.2 Valutazione quantitativa . . . 73

5.3 Valutazione qualitativa . . . 74

(5)
(6)

2.1 Esempio di creazione di un nuovo tipo GObject (.h) [21] . . . 6

2.2 Esempio di creazione di un nuovo tipo GObject (.c) [21] . . . 6

2.3 Esempio di record in OCaml . . . 9

2.4 Esempio di variante in OCaml . . . 10

2.5 Esempi di polimorfismo parametrico. . . 11

2.6 Esempio di upcast esplicito . . . 12

2.7 Esempio di binding OCaml . . . 14

2.8 Binding per funzioni aventi n > 5 parametri . . . 15

2.9 Esempio di dichiarazione external . . . 15

2.10 Binding completo di una funzione C . . . 16

2.11 Esempi di varianti polimorfe per rappresentare la gerarchia di oggetti di GTK. . . 21

2.12 Estratto di file di basso livello in lablgtk (gtkButtonProps.ml) . . . 22

2.13 Estratto di file di alto livello in lablgtk (gButton.ml e ogtkButtonProps.ml) 23 2.14 Tipo BasicType di haskell-gi . . . 26

2.15 Tipo Type di haskell-gi . . . 26

2.16 Tipo API di haskell-gi . . . 27

2.17 Tipo Name di haskell-gi . . . 28

2.18 Struttura dei file nelle librerie generate da haskell-gi . . . 28

3.1 Struttura del progetto ocaml-gi-gtk . . . 30

3.2 Tipo associato ad una libreria generabile . . . 31

3.3 La funzione parseArg per il parsing degli input argument . . . 32

3.4 Messaggio usage di generate.sh . . . 33

(7)

3.5 Esempio di file dune-project (GTK) . . . 34

3.6 Esempio di file dune generato nella root del progetto . . . 34

3.7 Esempio di file dune generato nella subdirectory del progetto (GTK) . . . 34

4.1 Monadi per la generazione del codice . . . 37

4.2 Informazioni contenute dentro la stato di generazione di un modulo . . . 38

4.3 Funzione submoduleLocation di QualifiedNaming.hs . . . 39

4.4 Esempio di macro per la creazione di stub . . . 40

4.5 Esempio di enum in C . . . 41

4.6 Esempio di tabella lookup info in C . . . 42

4.7 API di haskell-gi per enums . . . 43

4.8 Estratto del contenuto di Enums.h . . . 44

4.9 Estratto del contenuto di Enums.ml . . . 45

4.10 API di haskell-gi per gli oggetti . . . 46

4.11 Esempio di macro di conversione nel file .h (GtkButton) . . . 47

4.12 Esempio di macro di conversione nel file .c (GtkButton) . . . 47

4.13 Macro Make_Val_optiondi lablgtk . . . 47

4.14 Esempio di generazione dei segnali nel file *.ml . . . 48

4.15 Esempio di segnali da PlacesSidebar.ml . . . 49

4.16 Esempio di generazione di una classe signals (GtkButton) . . . 50

4.17 Informazioni sulle propriet`a estratte dal file GIR . . . 50

4.18 Esempio di propriet`a generate (Button.ml) . . . 52

4.19 Esempio di propriet`a generate (ButtonG.ml) . . . 53

4.20 Funzioni di GObject per leggere/scrivere le propriet`a . . . 53

4.21 Informazioni relative ai metodi estratte dal file GIR . . . 55

4.22 Esempio di macro ML_Xin_Yout . . . 56

4.23 Tipo TypeRep per una rappresentazione intermedia dei tipi OCaml . . . 57

4.24 Estratto di metodi generati nel file di basso livello (GtkButton) . . . 58

4.25 Esempio di metodo generato avente un oggetto in input . . . 60

4.26 Esempio di class type generata . . . 60

4.27 Esempio di metodo avente un oggetto in output . . . 60

(8)

4.29 Esempio di classe finale dell’oggetto (GtkButton) . . . 61

4.30 Esempio di make params (GtkButton) . . . 61

4.31 Esempio di create (GtkButton) . . . 62

4.32 Esempio di costruttore (GtkButton) . . . 63

4.33 Esempio di costruttore aggiuntivo (GtkButton) . . . 64

4.34 Estratto di codice da Object.hs per la gestione dell’ereditariet`a . . . 65

4.35 Esempio di implementazione delle interfacce (GtkButton) . . . 67

4.36 Esempio di messaggio di errore restituito dal compilatore in presenza di cicli di dipendenze . . . 67

4.37 Esempio di Recursion.ml . . . 68

4.38 Esempio di file *G.ml dopo essere stato “spostato” in Recursion.ml . . . 69

4.39 Esempio di interfaccia Editable che dichiara il metodo save . . . 69

(9)

Introduzione

La sviluppo di sistemi per la creazione di interfacce grafiche `e un processo complicato che richiede ingenti sforzi, soprattutto se le applicazioni create attraverso questi sistemi devono essere indipendenti dalla piattaforma e quindi potute eseguire su pi`u sistemi

operativi. Per questo motivo la maggior parte delle applicazioni non implementano

da zero i meccanismi per la creazione di interfacce grafiche, ma usano librerie che ne facilitano la creazione mettendo a disposizione widget e funzionalit`a. Tra le pi`u popolari di queste librerie, particolarmente in ambiente Linux/Unix, vi `e GTK.

GTK `e una libreria scritta nel linguaggio C facendo uso di GObject, un sistema di tipi orientato agli oggetti per C. Una particolarit`a delle librerie scritte in C `e che esse sono facilmente usabili anche da altri linguaggi di programmazione, i quali spesso met-tono a disposizione del programmatore dei meccanismi per interfacciarsi con il codice C. Il processo di “allacciamento” tra codice di due diversi linguaggi di programmazione viene chiamato binding. GTK si presta particolarmente alla creazione di binding poich´e, facendo uso di GObject, dispone di GObject Introspection, un meccanismo di introspe-zione in grado di generare un file GIR, un formato XML che descrive l’intera API della libreria.

Tramite l’uso di questo file GIR, sono numerosi i linguaggi che dispongono di libre-rie in grado di generare automaticamente i binding per GTK. OCaml, il linguaggio di programmazione principalmente trattato in questa tesi, invece non dispone di una tale libreria. `E per`o presente lablgtk, una libreria che implementa i binding per GTK ma

(10)

traverso una scrittura manuale. Questa libreria mette a disposizione del programmatore una API di alto livello che ben si sposa con i costrutti idiomatici di OCaml. Purtroppo il costo per la manutenzione manuale dei binding per una libreria vasta come GTK `e molto elevato ed infatti non tutta la API di GTK viene coperta. Inoltre, GTK `e soggetta a dei frequenti cambi di versione, che si ripercuotono in necessarie modifiche a lablgtk.

L’obiettivo fissato da questa tesi `e quindi lo sviluppo di una libreria in grado di generare automaticamente i binding OCaml per GTK. Prima di iniziare lo sviluppo sono state analizzate alcune librerie equivalenti per altri linguaggi di programmazione. Tra queste, `e stata notata in particolare haskell-gi, quella per il linguaggio Haskell. Questa libreria `e suddivisa in due moduli principali: uno per effettuare il parsing del file GIR ed uno per la generazione del codice Haskell a partire dalle informazioni raccolte dal primo modulo. Il parsing del file GIR `e un compito tedioso e di poco interesse, per questo motivo `e stato scelto di procedere con lo sviluppo effettuando un fork di haskell-gi, riusando completamente il modulo di parsing ma riscrivendo il modulo di generazione del codice in modo da emettere in output codice OCaml.

Nello riscrivere il modulo di generazione del codice, si `e cercato di riusare il pi`u possibile i meccanismi di binding di lablgtk in modo da generare codice OCaml che fosse il pi`u idiomatico possibile ed anche per facilitare la conversione di applicazioni che attualmente fanno uso di lablgtk.

Al termine dello sviluppo `e stato ottenuto un prototipo in grado di generare l’84% dell’API di GTK, a confronto del 45% coperto da lablgtk. `E stato inoltre effettuato il porting di numerosi file di esempio di lablgtk, riscontrando una buona compatilibilit`a tra le due librerie.

Il resto della tesi sar`a suddiviso nei seguenti capitoli:

• Nel capitolo 2 verranno esplorati nel dettaglio i principali linguaggi di programma-zione, meccanismi e librerie usate nello sviluppo di questa tesi;

• Nel capitolo 3 verr`a fatta una descrizione ad alto livello di ocaml-gi-gtk, la libreria sviluppata in questa tesi, spiegando come `e stata impostata e come poterla eseguire; • Nel capitolo 4 si scender`a nei dettagli dell’implementazione di ocaml-gi-gtk spie-gando come `e stata gestita la generazione di codice OCaml per ogni tipo di dato

(11)

di GTK che `e stato gestito;

• Nel capitolo 5 si far`a una valutazione del lavoro svolto, evidenziandone i principali limiti e descrivendo possibili lavori futuri.

(12)

Background e strumenti usati

Lo sviluppo di una libreria per la generazione automatica di binding tra due linguaggi comporta l’impiego di diverse tecnologie. Tra queste troviamo innanzitutto i linguaggi protagonisti della conversione, in questo caso C e OCaml. Laddove il primo `e un linguag-gio popolare e spesso insegnato nei corsi di laurea in informatica, il secondo `e di nicchia e ne verr`a fatta un’introduzione in questo capitolo. Inoltre verr`a introdotta GTK, la libreria per cui verranno generati i binding. Infine sar`a trattata lablgtk, l’attuale libreria che implementa i binding di GTK per OCaml, e le librerie di generazione automatica per altri linguaggi, in particolare Haskell.

2.1

GTK

GTK [10] [22] `e uno dei pi`u popolari toolkit open source per creare interfacce grafiche. Nasce nel 1996 durante lo sviluppo di GIMP (GNU Image Manupulation Program) [5] ma diventa successivamente una libreria general-purpose. Gli elementi pi`u interessanti di GTK sono i widget, che sono dei componenti grafici (ad esempio bottoni, campi di testo, menu, etc.) che possono essere composti per creare l’interfaccia grafica di un’applicazione. Alcuni di questi widget vengono infatti chiamati Container e possono contenere altri widget. GTK fa in realt`a uso di diverse librerie di pi`u basso livello:

• Pango, gestisce il rendering dei font; • Cairo, una libreria di grafica 2D;

(13)

• GDK e GdkPixbuf (GIMP Drawing Kit), gestiscono il caricamento di immagini in vari formati;

• ATK (Accessibility Toolkit), gestisce l’accessibilit`a dell’applicazione;

Tutte queste librerie dipendono da GLib, la libreria di pi`u basso livello in questa gerar-chia.

Figura 2.1: Architettura di un’applicazione GTK [11].

2.1.1

GLib

GLib definisce delle strutture dati pensate per essere usate insieme a due altre li-brerie che fanno parte di GLib stessa, ovvero GIO e GObject. La prima fornisce al programmatore una API di alto livello per effettuare operazioni di input/output, ad esempio operazioni su file o operazioni di rete, ma quella di pi`u interesse per questa tesi `e GObject. Questa libreria fornisce degli strumenti per scrivere codice C orientato

(14)

agli oggetti. Questo comprende meccanismi di classi, interfacce, ereditariet`a ed anche un garbage collector basato su reference counting. Fornisce inoltre il supporto per una programmazione basata su eventi, fondamentale nel contesto delle interfacce grafiche. In particolare, GObject mette a disposizione delle macro per creare nuovi tipi di dato che ereditano da dei tipi di base definiti dalla libreria, ad esempio GObject, GInterface e GBoxed (rispettivamente per definire oggetti, interfacce o un tipo opaco). Le nuove classi ottenute possono essere usate come superclasse da nuove classi, ottenendo quindi una gerarchia di oggetti [8].

1 # i f n d e f _ m y _ a p p _ w i n d o w _ h _ 2 # d e f i n e _ m y _ a p p _ w i n d o w _ h _ 3 4 G _ B E G I N _ D E C L S 5 6 # d e f i n e M Y _ A P P _ T Y P E _ W I N D O W ( m y _ a p p _ w i n d o w _ g e t _ t y p e () ) 7 G _ D E C L A R E _ F I N A L _ T Y P E ( M y A p p W i n d o w , m y _ a p p _ w i n d o w , MY_APP , WINDOW , G t k A p p l i c a t i o n W i n d o w ) 8 9 M y A p p W i n d o w * m y _ a p p _ w i n d o w _ n e w (v o i d) ; 10 11 G _ E N D _ D E C L S 12 13 # e n d i f /* _ m y _ a p p _ w i n d o w _ h _ */

Listing 2.1: Esempio di creazione di un nuovo tipo GObject (.h) [21]

1 # i n c l u d e " my - app - w i n d o w . h " 2 3 s t r u c t _ M y A p p W i n d o w 4 { 5 G t k A p p l i c a t i o n W i n d o w p a r e n t _ i n s t a n c e ; 6 7 // i n s t a n c e v a r i a b l e s for s u b c l a s s go h e r e 8 }; 9 10 G _ D E F I N E _ T Y P E ( M y A p p W i n d o w , m y _ a p p _ w i n d o w , G T K _ T Y P E _ A P P L I C A T I O N _ W I N D O W ) 11 12 s t a t i c v o i d

(15)

13 m y _ a p p _ w i n d o w _ i n i t ( M y A p p W i n d o w * w i n d o w ) 14 { 15 // i n i t i a l i s a t i o n g o e s h e r e 16 } 17 18 s t a t i c v o i d 19 m y _ a p p _ w i n d o w _ c l a s s _ i n i t ( M y A p p W i n d o w C l a s s * c l a s s ) 20 { 21 // v i r t u a l f u n c t i o n o v e r r i d e s go h e r e 22 // p r o p e r t y and s i g n a l d e f i n i t i o n s go h e r e 23 } 24 25 M y A p p W i n d o w * 26 m y _ a p p _ w i n d o w _ n e w (v o i d) 27 { 28 r e t u r n g _ o b j e c t _ n e w ( M Y _ A P P _ T Y P E _ W I N D O W , N U L L ) ; 29 }

Listing 2.2: Esempio di creazione di un nuovo tipo GObject (.c) [21]

Tutte le librerie basate su GObject hanno anche la caratteristica di poter esportare la propria API in un file XML attraverso un meccanismo chiamato GObject Introspection.

2.1.2

GObject Introspection

GObject Introspection [9] `e un meccanismo in grado di generare dei file GIR (un formato XML) contenenti la descrizione completa dell’API di una libreria basata su GO-bject. Tuttavia alcune informazioni, come per esempio l’opzionalit`a di un parametro, non sono ottenibili dal solo sistema di GObject e devono essere integrate tramite l’uso di annotazioni in GTK-Doc (un formato particolare per commenti in C). Le possibili annotazioni possono essere trovate sulla wiki ufficiale [4]. Questi file GIR sono parti-colarmente utili per creare dei binding a queste librerie poich´e `e molto comune tra i linguaggi di programmazione avere la possibilit`a di interfacciarsi con codice C, ed aven-do a disposizione le informazioni relative ai tipi delle funzioni per cui vengono creati i binding, questi possono essere generati automaticamente.

(16)

2.2

OCaml

OCaml [19] `e un linguaggio di programmazione general purpose principalmente fun-zionale, ma con supporto anche al paradigma imperativo e ad oggetti. OCaml pone particolare enfasi sulla correttezza del codice, implementando un sistema di tipi statico accompagnato da un sistema di type inference Hindley-Milner. La type inference per-mette al programmatore di non dover quasi mai annotare esplicitamente le variabili con i relativi tipi, ma questi vengono automaticamente dedotti dal compilatore.

Nel seguito verranno introdotte delle feature caratteristiche di OCaml che sono state usate in questa tesi e che non sono comuni nei linguaggi di programmazioni pi`u popolari.

2.2.1

Moduli

I moduli nascono dall’esigenza di isolare in un unico punto un tipo di dato e le operazioni da usare su di esso. In OCaml ogni file definisce implicitamente un modulo che racchiude i dati e le operazioni definite all’interno del file. Gli altri file possono accedere ai tipi di dato ed alle operazioni contenute nel modulo tramite dot notation. Ad esempio per accedere alla funzionemap del modulo List della libreria standard verr`a

usata la notazione List.map. Alternativamente, tramite la keyword open ModuleName `e

possibile “aprire” un file per portare nello scope corrente tutti gli identificatori definiti dentro il modulo aperto. `E inoltre possibile definire moduli interni tramite le keyword

module ModuleName = struct ... end.

2.2.2

Algebraic Data Types (ADT)

I tipi di dato algebrici sono un costrutto di programmazione introdotto dalla famiglia di linguaggi funzionale ML, di cui OCaml fa parte. Nel paradigma funzionale sono il costrutto principale usato per descrivere il dominio delle applicazioni. Gli ADT si dividono in due tipi, prodotti (tuple e record) e coprodotti (varianti).

(17)

Tuple e Record

Le tuple sono un tipo di dato formato da una sequenza ordinata e finita di valori. Questo tipo di dato `e usato per raggruppare pi`u valori all’interno della stessa variabile. Ad esempio una tupla formata da 3 elementi (4, "hello", 3.14) ha tipo (int * string * float). Da notare che nel tipo `e usata la notazione * per denotare un prodotto tra i

tipi. Questo deriva dalla teoria degli insiemi, infatti l’insieme dei valori assumibili dalla tupla `e il prodotto cartesiano degli insiemi formati dagli elementi dei singoli tipi.

I record hanno la stessa funzione delle tuple, ovvero quella di trasportare pi`u valori in una singola variabile, ma permettono di associare un nome ad ogni componente della tupla. Al contrario delle tuple, il tipo del record deve essere dichiarato prima di poter definire una variabile di questo tipo.

1 t y p e p o i n t = { 2 x : f l o a t ; 3 y : f l o a t ; 4 } 5 6 let i s _ o r i g i n p = m a t c h p w i t h 7 | { x = 0 . 0 ; y = 0 . 0 } - > t r u e 8 | _ - > f a l s e 9 (* val i s _ o r i g i n : p o i n t - > b o o l = < fun > *) 10 11 let d i s t a n c e _ b e t w e e n p1 p2 = 12 s q r t (( p2 . x -. p1 . x ) +. ( p2 . y -. p1 . y ) ) 13 (* val d i s t a n c e _ b e t w e e n : p o i n t - > p o i n t - > f l o a t = < fun > *)

Listing 2.3: Esempio di record in OCaml

Varianti

Chiamate anche coprodotti o unioni taggate, le varianti sono un costrutto di OCaml usato per definire un tipo di dato che pu`o assumere un numero limitato di valori. Asso-migliano alle enum presenti in altri linguaggi di programmazione, ma differiscono poich`e i valori assumibili non sono interi, ma vengono chiamati costruttori. Questi possono contenere anche altri tipi di dato e possono avere una definizione ricorsiva.

(18)

1 t y p e b i n a r y _ t r e e = 2 | L e a f 3 | N o d e of int * b i n a r y _ t r e e * b i n a r y _ t r e e 4 5 let rec s i z e t r e e = m a t c h t r e e w i t h 6 | L e a f - > 0 7 | N o d e ( _ , l , r ) - > 1 + s i z e l + s i z e r 8 (* val s i z e : b i n a r y _ t r e e - > int = < fun > *) 9 10 let m y _ t r e e = 11 N o d e (0 , 12 N o d e (1 , Leaf , L e a f ) , 13 L e a f 14 ) 15 (* val m y _ t r e e : b i n a r y _ t r e e = ... *) 16 17 s i z e m y _ t r e e ;; 18 (* - : int = 2 *)

Listing 2.4: Esempio di variante in OCaml

2.2.3

Polimorfismo

Nel contesto dei linguaggi di programmazione il poliformismo `e un meccanismo per definire delle procedure che tramite una singola definizione possano operare su pi`u tipi di dato. OCaml fa uso di due tipi di polimorfismo: parametrico e row. Il polimorfismo ad hoc invece non `e supportato da OCaml poich`e renderebbe indecidibile l’algoritmo di type inference, ma `e presente eccezionalmente in alcuni operatori della libreria standard come quello di uguaglianza (=).

Polimorfismo parametrico

Il polimorfismo parametrico, presente anche in altri linguaggi come Java (tramite ge-nerics) e C++ (tramite template), permette di definire delle funzioni che possono operare su pi`u tipi di dato. Al contrario di questi linguaggi, dove la variabile di tipo deve

(19)

esse-re esplicitamente introdotta, il compilatoesse-re di OCaml `e in grado di determinare il tipo pi`u generico che una funzione pu`o assumere. Le variabili di tipo vengono rappresentate come atomi preceduti dal simbolo apostrofo ’ (es: ’a o ’b) e vengono lette nel

linguag-gio naturale come lettere greche. Queste variabili vengono quantificate universalmente all’esterno della funzione ed istanziate nel tipo concreto una volta che la funzione viene applicata ai suoi argomenti.

1 let id x = x 2 (* val id : ’ a - > ’ a = < fun > *) 3 4 let c o m p o s e f g x = f ( g x ) 5 (* val c o m p o s e : ( ’ a - > ’ b ) - > ( ’ c - > ’ a ) - > ’ c - > ’ b = < fun > *) 6

7 let s u m _ t h e n _ m u l t i p l y = c o m p o s e (fun x - > x * 4) (fun x - > x + 1) 8 (* val s u m _ t h e n _ m u l t i p l y : int - > int = < fun > *)

9 10 t y p e ’ a b i n a r y _ t r e e = 11 | L e a f 12 | N o d e of ’ a * b i n a r y _ t r e e * b i n a r y _ t r e e 13 14 let rec s i z e t r e e = m a t c h t r e e w i t h 15 | L e a f - > 0 16 | N o d e ( _ , l , r ) - > 1 + s i z e l + s i z e r

17 (* val s i z e : ’ a b i n a r y _ t r e e - > int = < fun > *)

Listing 2.5: Esempi di polimorfismo parametrico.

Polimorfismo row

OCaml supporta inoltre il polimorfismo row, che permette di realizzare la tecnica conosciuta come duck typing nei linguaggi dinamici in un sistema di tipi statico. Questo typing prende il nome di structural typing e permette di confrontare due variabili in base alle loro propriet`a e non in base al nome del loro tipo (nominal typing). Il polimorfismo row in OCaml `e realizzato attraverso una combinazione di polimorfismo parametrico e variabili row. Procediamo nella spiegazione tramite esempi:

(20)

2 (* val f : < s a y _ h e l l o : u n i t - > s t r i n g ; .. > - > u n i t = < fun > *)

f `e una funzione che prende in input una variabile x, e nel corpo della funzione chiama un metodosay_hellosu tale variabile. Il compilatore inferisce che x abbia quindi un tipo

che includa il metodosay_helloed utilizza zucchero sintattico tramite..per indicare che

x pu`o contenere anche altri campi. Il tipo completo di f sarebbeval f : ’a. < say_hello : unit -> string; ’a > -> unit = <fun> dove’a`e una variabile row.

1 let d o n a l d = o b j e c t 2 m e t h o d s a y _ h e l l o () = " Hi , i ’ m D o n a l d ! " 3 end 4 (* val d o n a l d : < s a y _ h e l l o : u n i t - > s t r i n g > = < obj > *) 5 6 let g o o f y = o b j e c t 7 m e t h o d s a y _ h e l l o () = " Hi , i ’ m G o o f y ! " 8 m e t h o d s o m e t h i n g _ e l s e () = () 9 end 10 (* val g o o f y : < s a y _ h e l l o : u n i t - > s t r i n g ; s o m e t h i n g _ e l s e : u n i t - > u n i t > = < obj > *) 11 12 f d o n a l d ;; 13 (* P r i n t s : Hi , i ’ m D o n a l d ! *) 14 15 f g o o f y ;; 16 (* P r i n t s : Hi , i ’ m G o o f y ! *)

Entrambi gli oggettidonaldegoofyvengono accettati da f , dove il primo unifica’acon il

tipo vuoto ed il secondo unifica’aconsomething_else : unit -> unit. Il polimorfismo row

introduce anche un concetto di subtyping strutturale in OCaml. Nell’esempio precedente il tipo digoofy`e sottotipo del tipo didonald, in quanto il primo pu`o essere usato in tutti

i contesti nei quali pu`o essere usato il secondo. Il cast tra tipi `e possibile ma deve essere sempre esplicito tramite l’operatore(:>) ed `e possibile effettuare solo cast da una

sottoclasse verso una superclasse.

1 let f ( x : < s a y _ h e l l o : u n i t - > s t r i n g >) =

2 p r i n t _ e n d l i n e ( x # s a y _ h e l l o () ) 3

(21)

5 (* P r i n t s : Hi , i ’ m D o n a l d ! *) 6 7 f g o o f y ;; 8 (* E r r o r : T h i s e x p r e s s i o n has t y p e 9 < s a y _ h e l l o : u n i t - > s t r i n g ; s o m e t h i n g _ e l s e : u n i t - > u n i t > 10 but an e x p r e s s i o n was e x p e c t e d of t y p e < s a y _ h e l l o : u n i t - > s t r i n g > 11 The s e c o n d o b j e c t t y p e has no m e t h o d s o m e t h i n g _ e l s e *) 12 13 f ( g o o f y : > < s a y _ h e l l o : u n i t - > s t r i n g >) ;; 14 (* P r i n t s : Hi , i ’ m G o o f y ! *)

Listing 2.6: Esempio di upcast esplicito

2.2.4

Varianti Polimorfe

Le varianti polimorfe [3] sono un costrutto di programmazione esclusivo di OCaml e sono essenzialmente delle varianti che fanno uso di polimorfismo row. I costrutto-ri delle vacostrutto-rianti polimorfe devono essere preceduti dal simbolo ‘ e non devono essere

necessariamente dichiarati come parte di un tipo prima di farne uso. Ad esempio:

1 let a n i m a l _ s a y _ h i = f u n c t i o n 2 | ‘ DOG - > " w o o f "

3 | ‘ CAT - > " m e o w "

4 (* val a n i m a l _ s a y _ h i : [ < ‘ CAT | ‘ DOG ] - > s t r i n g = < fun > *) 5

6 let a n i m a l _ s a y _ h i _ 2 = f u n c t i o n 7 | ‘ DOG - > " w o o f "

8 | ‘ CAT - > " m e o w " 9 | _ - > " ... "

10 (* val a n i m a l _ s a y _ h i _ 2 : [ > ‘ CAT | ‘ DOG ] - > s t r i n g = < fun > *)

L’input delle due funzioni viene inferito rispettivamente come [< ‘CAT | ‘DOG ] e [> ‘ CAT | ‘DOG ], dove | assume lo stesso significato che ha nelle varianti, ovvero quello di

enumerare i possibili costruttori. < e> invece sono zucchero sintattico per una variabile

(22)

2.2.5

Garbage collector

OCaml ha una gestione automatica della memoria attraverso l’uso di un garbage col-lector generazionale. Il garbage colcol-lector sfrutta infatti l’ipotesi generazionale: secondo questa, la maggior parte delle variabili vengono allocate per poco tempo prima di essere deallocate, ma quelle che rimangono in memoria tendono a rimanerci per molto tempo. OCaml divide la memoria in due heap, uno chiamato minor in cui le variabili vengono inizialmente allocate, ed uno chiamato major in cui vengono spostate le variabili che sopravvivono dopo una scansione del GC.

2.3

Meccanismi di interfacciamento tra C e OCaml

OCaml dispone di meccanismi di foreign function interface con il codice C. Questi permettono di far interagire del codice OCaml con del codice C e risultano fondamentali per effettuare dei binding OCaml per una libreria C. I metodi di interfacciamento tra OCaml e C sono descritti in un capitolo del manuale di OCaml [16] e vengono qui riassunti e riportati in quanto fondamentali per questa tesi.

2.3.1

Binding a delle primitive

I binding vengono effettuati esclusivamente tra funzioni di OCaml e funzioni di C. Le funzioni C vengono chiamate primitive. I binding per le primitive aventi n <= 5 parametri sono implementate da funzioni C che ricevono in input n parametri di tipo

value e restituiscono un valore di tipo value. Il tipo value `e una rappresentazione per i

valori di OCaml. Per esempio il binding ad una primitiva C che somma due interi pu`o essere definita come:

1 C A M L p r i m v a l u e

2 sum (v a l u e x , v a l u e y ) 3 {

4 ... 5 }

(23)

I binding alle primitive con ariet`a n > 5 devono essere invece implementati da due funzioni C:

• una avente 2 parametri (value *argv, int argn) usata da ocamlc, il compliatore

bytecode;

• una avente tutti gli n parametri usata da ocamlopt, il compilatore nativo.

1 C A M L p r i m v a l u e 2 s u m _ b y t e c o d e ( v a l u e * argv , int a r g n ) 3 { 4 ... 5 } 6 7 C A M L p r i m v a l u e 8 s u m _ n a t i v e ( v a l u e v1 , v a l u e v2 , v a l u e v3 , v a l u e v4 , v a l u e v5 , . . . ) 9 { 10 ... 11 }

Listing 2.8: Binding per funzioni aventi n > 5 parametri

Una volta definite in C queste funzioni, che come vedremo servono a realizzare la conver-sione tra i tipi OCaml e C, `e possibile richiamarle da OCaml attraverso una dichiarazione external :

1 e x t e r n a l sum : int - > int - > int - > int - > int - > int - > int = "

s u m _ b y t e c o d e " " s u m _ n a t i v e "

Listing 2.9: Esempio di dichiarazione external

In questo esempio `e stata creata una funzione OCaml sum avente il tipo annotato. La

annotazione `e necessaria ed occorre prestare attenzione nell’usare i tipi corretti. La funzione sumrealizza quindi un binding con le funzioni C sum_bytecode esum_native. Nel

caso in cui si effettui un binding per una funzione avente n <= 5 parametri va assegnata la sola funzione C definita.

(24)

1. decodificare i valori OCaml in valori C, e codificare i valore di return come un valore OCaml;

2. calcolare effettivamente la funzione.

La funzione che effettua la conversione viene chiamata stub.

1 C A M L p r i m v a l u e 2 s u m _ s t u b ( v a l u e v1 , v a l u e v2 ) 3 { 4 int x , y , res ; 5 x = I n t _ v a l ( x ) ; 6 y = I n t _ v a l ( y ) ; 7 res = sum ( x , y ) ; 8 r e t u r n V a l _ i n t ( res ) ; 9 } 10

11 int sum (int x , int y ) { 12 r e t u r n x + y ;

13 }

Listing 2.10: Binding completo di una funzione C

2.3.2

Il tipo value

Tutti i valori di OCaml sono rappresentati in C con il tipo value, definito nel file caml/mlvalues.h. Questo tipo pu`o indicare:

• un integer unboxed;

• un puntatore ad un blocco dentro lo heap di OCaml;

• un puntatore ad un oggetto fuori dallo heap di OCaml (ad esempio allocato in C tramitemalloc).

Integer

(25)

Blocchi nello heap

I blocchi dentro lo heap sono rappresentati tramite una struttura contenente un header, che indica la grandezza in word del blocco, ed un tag, una enumerazione che descrive il contenuto del blocco. Il tag pu`o assumere i seguenti valori:

1. Da 0 aNo_scan_tag - 1: un array di oggetti OCaml, dove ogni oggetto `e un value;

2. Closure_tag: una closure. La prima word `e un puntatore al codice, le restanti word

contengono l’ambiente;

3. String_tag: una stringa o una sequenza di byte;

4. Double_tag: un double;

5. Double_array_tag: un array o un record di numeri double;

6. Abstract_tag: un tipo di dato astratto;

7. Custom_tag: un tipo di dato astratto con funzioni di finalizzazione, comparazione,

hashing, serializzazione e deserializzazione definte dall’utente.

Blocchi fuori dallo heap

Ogni puntatore word-aligned che punta ad un indirizzo fuori dallo heap pu`o essere castato in modo safe al (e dal) tipovalue. Bisogna fare attenzione a non usare deallocare questi

blocchi di memoria usando free dopo che un puntatore a questi `e stato restituito ad

OCaml tramite un cast avalue. OCaml infatti non sar`a a conoscenza della deallocazione

e incorrer`a in errori a runtime.

2.3.3

Rappresentazione di tipi di dato OCaml

I tipi di dato atomici di OCaml vengono rappresentati nel tipo value tramite:

• int, integer unboxed;

(26)

• float, blocco Double_tag;

• bytes, blocco String_tag;

• string, blocco String_tag;

• int32, blocco Custom_tag;

• int64, blocco Custom_tag;

• nativeint, blocco Custom_tag;

Tuple e record

Le tuple ed i record sono rappresentati da puntatori a blocchi con tag 0. I campi dei record sono nello stesso ordine della dichiarazione del tipo. I record i cui campi hanno tutti il tipo statico float vengono rappresentati come array di float, con tag Double_array_tag.

Inoltre i record che contengono un solo campo vengono chiamati unboxable e possono essere rappresentati in due modi:

• boxed, un blocco con tag 0 o Double_array_tag;

• unboxed, usando direttamente il valore del campo. La scelta viene fatta, in ordine decrescente di priorit`a, tramite:

• la presenza di un attributo [@@boxed]o [@@unboxed] nella dichiarazione di tipo;

• un’opzione del compilatore (-unboxed-types o -no-unboxed-types);

• la rappresentazione di default, che nella versione corrente di OCaml e’ boxed. Array

Gli array di float hanno una rappresentazione unboxed, e sono rappresentati tramite puntatori a blocchi con tag Double_array_tag. Gli array di interi e di altri tipi di dato

(27)

Varianti

Le varianti sono rappresentate da integer unboxed per i costruttori costanti, altrimenti da blocchi il cui tag `e una numerazione dei costruttori non-costanti. L’enumerazione inizia da 0 ed `e separata per i costruttori costanti e non-costanti. Un costruttore non-costante avente n parametri `e rappresentato da un blocco di grandezza n.

Oggetti

Gli oggetti sono rappresentati da blocchi con tag Object_tag. Il primo campo del blocco

fa riferimento alla classe dell’oggetto (ed ai suoi metodi). Il secondo campo contiene un ID usato per la comparazione. I campi rimanenti contengono i valori delle variabili dell’oggetto.

Varianti polimorfe

Le varianti polimorfe differiscono dalle varianti dal fatto che i costruttori non vengono numerati partendo da 0, ma vengono identificati da un hash (un intero OCaml), calco-lato dalla funzione C hash_variant definita nel file <caml/mlvalues.h>. A differenza delle

varianti, i parametri del costruttore non vengono espansi. Ad esempio, ‘Costr(v, w) `e

rappresentato da un blocco di grandezza 2, dove il primo campo contiene la rappresen-tazione della coppia (v, w), laddove una variante sarebbe rappresentata con un blocco

di grandezza 3, contenente ve w rispettivamente nel secondo e terzo campo.

2.3.4

Cooperare con il garbage collector

I blocchi nello heap non pi`u usati vengono deallocati dal garbage collector, per questo motivo bisogna mettere un atto qualche accorgimento nelle funzioni C che operano su questi valori:

1. Una funzione che ha parametri o variabili locali di tipo valuedeve iniziare con una

chiamata ad una delle macro CAMLparamo CAMLlocal, e terminare con una chiamata

(28)

• CAMLparam0 ... CAMLparam5, da usare all’inizio della funzione, passando il

nu-mero adeguato di parametri di tipovalue;

• CAMLxparam ... CAMLxparam5, da usare se la funzione ha pi`u di 5 parametri di

tipovalue, usare questa macro con il numero di parametri restanti;

• CAMLlocal1 ... CAMLlocal5, possono essere usate solo dopo una chiamata a CAMLparamper dichiarare una variabile locale;

• CAMLlocalN (x, n), per dichiarare un array x contenente n valori di tipovalue;

• CAMLreturn0, se la funzione ha tipovoid, bisogna inserire una chiamata a questa

macro al posto di return, anche quando esso `e implicito;

• CAMLreturn, se il valore v da restituire ha tipo value, usare CAMLreturn (v);

• CAMLreturnT, se il valore v da restituire ha tipo t, usare CAMLreturnT (t, v).

2. Gli assegnamenti ai campi di blocchi strutturati devono essere fatti tramite la macro Store_field (per i blocchi normali), o Store_double_field (per gli array di

float e per i record contenenti solo float). Una chiamata a Store_field (b, n, v)

assegna il valore v nel campo numero n del blocco b.

3. Le variabili globali contenenti valori di tipo value devono essere registrate con

il garbage collector usando la funzione caml_register_global_root. Una variabile

globale v pu`o essere registrata tramite caml_register_global_root (&v) e rimossa

tramitecaml_remove_global_root(&v).

2.3.5

Callback

`

E possibile chiamare funzioni OCaml (che vengono chiamate closure) da codice C. Per farlo si usano le seguenti funzioni:

• caml_callback(f, a)applica la funzione f al valore a e restituisce il valore restituito

da f ;

• caml_callback2(f, a, b) (e caml_callback3(f, a, b, c)) applicano la funzione f ai

(29)

• caml_callbackN(f, n, args)applica la funzione f agli n parametri contenuti

nell’ar-ray args.

2.4

lablgtk

lablgtk [15] `e la libreria che viene attualmente usata per creare applicazioni GTK usando OCaml. Nasce come libreria di binding scritti a mano per la versione 1 di GTK e recentemente `e stata aggiornata alla versione 3. Questa libreria `e la principale fonte di ispirazione di questa tesi riguardo a come impostare i binding per GTK.

2.4.1

Binding alle classi di GTK

`

E particolarmente interessante come questa libreria rappresenti nel type system di OCaml le gerarchie di classi di GTK. I widget ed in generale tutti gli oggetti di GTK vengono istanziati da C e risiedono nello heap di C. Gli autori di questa libreria hanno deciso di rappresentare i valori restituiti dal costruttore di una classe tramite un tipo opaco. Nel file src/gobject.ml viene quindi dichiarato un tipo type -’a obj, dove obj `e

quindi un costruttore di tipo polimorfo. Il simbolo - serve per dichiarare che ’a obj `e

controvariante. Questo significa che se ’a `e sottotipo di ’b, allora ’a obj `e supertipo di ’b obj. Questa relazione di subtyping viene sfruttata per rappresentare correttamente le

gerarchie di oggetti di GTK facendo uso di varianti polimorfe per istanziare il parametro diobj. 1 t y p e w i d g e t = [ ‘ giu | ‘ w i d g e t ] 2 t y p e c o n t a i n e r = [ w i d g e t | ‘ c o n t a i n e r ] 3 t y p e bin = [ c o n t a i n e r | ‘ bin ] 4 t y p e b u t t o n = [ bin | ‘ b u t t o n ] 5 t y p e t o g g l e _ b u t t o n = [ b u t t o n | ‘ t o g g l e b u t t o n ]

Listing 2.11: Esempi di varianti polimorfe per rappresentare la gerarchia di oggetti di GTK.

Il tipo delle varianti polimorfe viene definito secondo la gerarchia di GTK. In questo esempio widget `e sottotipo di button (in GTK la relazione `e invece al contrario), ma

(30)

viene sfruttata la controvarianza di objper invertire la relazione. Pertanto button obj `e

sottotipo di widget obj come in GTK.

Un’altra particolarit`a interessante di lablgtk3 `e come gli autori hanno deciso di strutturare i binding agli oggetti. Ogni oggetto `e definito in tre diversi file:

• in gtk*.ml sono presenti i binding di “basso livello”. Qui sono definiti i binding alle propriet`a, segnali e metodi. Non viene fatto uso delle funzionalit`a ad oggetti di OCaml;

• in ml gtk*.c vengono definite le funzioni di binding vere e proprie usate dal file gtk*.ml;

• in g*.ml sono dichiarate le classi OCaml che fanno uso dei pre-metodi definiti nel file gtk*.ml. Queste classi hanno un parametro di tipo obj usato per rendere

disponibile ai metodi l’oggetto a cui fanno riferimento.

1 m o d u l e B u t t o n = s t r u c t 2 let c a s t w : Gtk . b u t t o n obj = t r y _ c a s t w " G t k B u t t o n " 3 m o d u l e P = s t r u c t 4 let l a b e l : ([ > ‘ b u t t o n ] , _ ) p r o p e r t y = P r i v a t e P r o p s . l a b e l 5 ... 6 let x a l i g n : ([ > ‘ b u t t o n ] , _ ) p r o p e r t y = { n a m e =" x a l i g n "; c o n v = f l o a t } 7 end 8 m o d u l e S = s t r u c t 9 o p e n G t k S i g n a l 10 let c l i c k e d = { n a m e =" c l i c k e d "; c l a s s e = ‘ b u t t o n ; m a r s h a l l e r = m a r s h a l _ u n i t } 11 ... 12 let r e l e a s e d = { n a m e =" r e l e a s e d "; c l a s s e = ‘ b u t t o n ; m a r s h a l l e r = m a r s h a l _ u n i t } 13 end 14 let c r e a t e pl : Gtk . b u t t o n obj = O b j e c t . m a k e " G t k B u t t o n " pl 15 let m a k e _ p a r a m s ~ c o n t pl ? l a b e l ? u s e _ s t o c k ? u s e _ u n d e r l i n e ? r e l i e f = 16 let pl = ( 17 m a y _ c o n s P . l a b e l l a b e l ( 18 m a y _ c o n s P . u s e _ s t o c k u s e _ s t o c k ( 19 m a y _ c o n s P . u s e _ u n d e r l i n e u s e _ u n d e r l i n e (

(31)

20 m a y _ c o n s P . r e l i e f r e l i e f pl ) ) ) ) in 21 c o n t pl

22 end

Listing 2.12: Estratto di file di basso livello in lablgtk (gtkButtonProps.ml)

1 c l a s s v i r t u a l b u t t o n _ p r o p s = o b j e c t 2 val v i r t u a l obj : _ obj

3 m e t h o d l a b e l = get B u t t o n . P . l a b e l obj 4 m e t h o d s e t _ l a b e l = set B u t t o n . P . l a b e l obj 5 ... 6 m e t h o d x a l i g n = get B u t t o n . P . x a l i g n obj 7 m e t h o d s e t _ x a l i g n = set B u t t o n . P . x a l i g n obj 8 end 9 10 c l a s s b u t t o n _ s k e l obj = o b j e c t ( s e l f ) 11 i n h e r i t bin obj 12 i n h e r i t b u t t o n _ p r o p s 13 end 14 15 c l a s s b u t t o n _ s i g n a l s obj = o b j e c t 16 i n h e r i t c o n t a i n e r _ s i g n a l s _ i m p l ( obj : [ > b u t t o n ] obj ) 17 i n h e r i t b u t t o n _ s i g s 18 end 19 20 c l a s s b u t t o n obj = o b j e c t 21 i n h e r i t b u t t o n _ s k e l ( obj : Gtk . b u t t o n obj ) 22 m e t h o d c o n n e c t = new b u t t o n _ s i g n a l s obj 23 end 24 25 let b u t t o n ? l a b e l = 26 B u t t o n . m a k e _ p a r a m s [] ? l a b e l ~ c o n t :( 27 p a c k _ r e t u r n (fun p - > new b u t t o n ( B u t t o n . c r e a t e p ) ) )

(32)

2.4.2

Limiti di lablgtk

Il principale problema di lablgtk `e che i binding sono scritti manualmente. Questo comporta innanzitutto un enorme sforzo da parte degli autori per coprire tutta la API di GTK, la quale infatti `e coperta solo in parte dai binding di lablgtk. Un’ulteriore conseguenza della scrittura manuale dei binding `e che possono facilmente essere commessi errori. Va inoltre considerato che GTK dipende da altre librerie basate su GObject le cui funzionalit`a sono attualmente coperte in minima parte lablgtk. Esistono inoltre librerie basate su GTK che implementano widget aggiuntivi (es: GtkSourceView). Anche per queste librerie i binding sono attualmente scritti a mano riportando i medesimi limiti, superabili tramite uno strumento di generazione automatica dei binding.

2.5

haskell-gi

L’obiettivo di questa tesi `e l’implementazione di una libreria che sia in grado generare automaticamente dei binding di GTK per OCaml, e librerie analoghe sono gi`a esistenti per altri linguaggi. Tra le pi`u popolari troviamo PyGObject per Python [18], gjs per Javascript [6], gtk-rs [12] per Rust e haskell-gi [14] per Haskell. Quest’ultima `e parti-colarmente interessante poich´e Haskell ha molte caratteristiche in comune con OCaml. Entrambi sono linguaggi funzionali con un sistema di tipi simile ed entrambi condividono la presenza di un garbage collector. Per questo motivo haskell-gi `e stata analizzata a fondo per individuare idee o parti di codice che potessero essere riutilizzate al fine di guidare l’implementazione di una libreria analoga per OCaml. Inoltre haskell-gi `e in gra-do di generare dei binding Haskell non solo per GTK, ma per ogni libreria che supporti la GObject Introspection. Tra queste troviamo anche tutte le librerie dalle quali GTK dipende.

haskell-gi ha tre moduli principali:

• base, una libreria a s`e stante, implementa le operazioni di pi`u basso livello per effettuare le conversioni tra i tipi di C (e GLib) e quelli Haskell. Il codice generato dal modulo di CodeGen fa uso di questa libreria;

(33)

• GIR, si occupa di effettuare il parsing dei file GIR e di esporre al modulo di CodeGen le informazioni necessarie per la generazione del codice;

• CodeGen, tramite le informazioni ottenute dal modulo GIR, genera una libreria Haskell contenente i binding per la libreria C che si sta generando.

Di queste, `e risultato di particolare interesse il modulo GIR. Le informazioni esposte da questo modulo sono infatti utilizzabili anche per generare codice OCaml. Gli altri due moduli sono invece troppo specifici e legati alla generazione di codice Haskell, che ha un meccanismo di interfacciamento con C completamente differente da quello di OCaml [13]. Tuttavia, il modulo CodeGen `e comunque di interesse per quanto riguarda l’architettura e la strutturazione della libreria, che pu`o essere riusata modificando le parti specifiche di generazione del codice per generare codice OCaml.

La scelta che `e stata effettuata `e quindi quella di effettuare un fork di haskell-gi, mantenendo intatto il modulo GIR, sostituendo il modulo base con un equivalente per OCaml, e modificando ampiamente il modulo CodeGen adattandolo per OCaml. ocaml-gi-gtk, la libreria sviluppata in questa tesi, `e quindi scritta in Haskell, scelta atipica considerando che si deve inserire nell’ecosistema di OCaml. Questa scelta presenta infatti qualche controindicazione, come la difficolt`a di attrarre futuri contributori alla libreria, che provenendo dall’ecosistema di OCaml potrebbero non conoscere Haskell ed i suoi tool. A fronte di questa barriera di entrata, troviamo per`o il beneficio di condividere delle parti di codice con una libreria ben consolidata e mantenuta, permettendo cos`ı una prototipazione rapida di una libreria equivalente per OCaml. Nel caso in cui ocaml-gi-gtk sia ben ricevuta dalla community sar`a sempre possibile effettuarne una conversione in OCaml.

Segue ora una descrizione di alcune feature di haskell-gi che sono state riusate in questa tesi e che verranno citate nelle sezioni seguenti.

2.5.1

Type

Ad ogni variabile presente in GTK `e associato il relativo tipo. Nel file GIR questa informazione `e presente e denotata dal tag <type name c:type />. Il parser di haskell-gi

(34)

BasicType viene usato per rappresentare i tipi di dato base di GLib, ovvero i tipi che trovano una diretta corrispondenza con un tipo di dato base di C, ma che sono stati ridefiniti per questioni di portabilit`a e di facilit`a d’uso [7].

1 d a t a B a s i c T y p e = T B o o l e a n - - ^ g b o o l e a n 2 | T I n t - - ^ g i n t 3 | T U I n t - - ^ g u i n t 4 | T L o n g - - ^ g l o n g 5 | T U L o n g - - ^ g u l o n g 6 | T I n t 8 - - ^ g i n t 8 7 | T U I n t 8 - - ^ g u i n t 8 8 | T I n t 1 6 - - ^ g i n t 1 6 9 | T U I n t 1 6 - - ^ g u i n t 1 6 10 | T I n t 3 2 - - ^ g i n t 3 2 11 | T U I n t 3 2 - - ^ g u i n t 3 2 12 | T I n t 6 4 - - ^ g i n t 6 4 13 | T U I n t 6 4 - - ^ g u i n t 6 4 14 | T F l o a t - - ^ g f l o a t 15 | T D o u b l e - - ^ g d o u b l e 16 | T U n i C h a r - - ^ g u n i c h a r 17 | T G T y p e - - ^ G T y p e 18 | T U T F 8 - - ^ g c h a r * , e n c o d e d as UTF -8 19 | T F i l e N a m e - - ^ g c h a r * , e n c o d i n g a f i l e n a m e 20 | T P t r - - ^ g p o i n t e r 21 | T I n t P t r - - ^ g i n t p t r 22 | T U I n t P t r - - ^ g u i n t p t r 23 d e r i v i n g (Eq, Show, Ord)

Listing 2.14: Tipo BasicType di haskell-gi

Il tipo Type invece contiene i BasicType e tutti gli altri tipi non di base di GLib. Un

costruttore particolare diType`eTInterface. Questo `e usato per tutti i restanti tipi che non

hanno trovato una definizione dai costruttori precedenti. Questi sono tipi complessi come ad esempio classi, interfacce, struct ed enum. Per ottenere il tipo specifico `e necessario usare la funzione findAPIByName definita nel file Code.hs. Questa restituisce il tipo API,

che contiene dei costruttori dai quali `e possibile estrarre ulteriori informazioni relative al tipo specifico.

(35)

1 d a t a T y p e 2 = T B a s i c T y p e B a s i c T y p e 3 | T E r r o r - - ^ G E r r o r 4 | T V a r i a n t - - ^ G V a r i a n t 5 | T P a r a m S p e c - - ^ G P a r a m S p e c 6 | T C A r r a y B o o l Int Int T y p e - - ^ Z e r o t e r m i n a t e d , A r r a y F i x e d 7 - - Size , A r r a y Length , E l e m e n t T y p e 8 | T G A r r a y T y p e - - ^ G A r r a y 9 | T P t r A r r a y T y p e - - ^ G P t r A r r a y 10 | T B y t e A r r a y - - ^ G B y t e A r r a y 11 | T G L i s t T y p e - - ^ G L i s t 12 | T G S L i s t T y p e - - ^ G S L i s t 13 | T G H a s h T y p e T y p e - - ^ G H a s h T a b l e 14 | T G C l o s u r e (M a y b e T y p e ) - - ^ G C l o s u r e c o n t a i n i n g the g i v e n API ( if k n o w n )

15 | T I n t e r f a c e N a m e - - ^ A r e f e r e n c e to s o m e API in the GIR 16 d e r i v i n g (Eq, Show, Ord)

Listing 2.15: Tipo Type di haskell-gi

1 d a t a API 2 = A P I C o n s t C o n s t a n t 3 | A P I F u n c t i o n F u n c t i o n 4 | A P I C a l l b a c k C a l l b a c k 5 | A P I E n u m E n u m e r a t i o n 6 | A P I F l a g s F l a g s 7 | A P I I n t e r f a c e I n t e r f a c e 8 | A P I O b j e c t O b j e c t 9 | A P I S t r u c t S t r u c t 10 | A P I U n i o n U n i o n 11 d e r i v i n g S h o w

Listing 2.16: Tipo API di haskell-gi

Il tipo API ha inoltre la particolarit`a di rappresentare un’unit`a di generazione del codice in haskell-gi. Questo significa che ogni variabile di tipo API verr`a considerato come un modulo a s´e stante.

(36)

2.5.2

Moduli

Il tipo Name che `e possibile estrarre dal costruttore di TInterface `e molto usato

all’interno della libreria. Questo, in aggiunta al nome del tipo contiene il namespace, ovvero la libreria a cui appartiene questo tipo, che pu`o essere non solo GTK, ma anche una delle sue dipendenze.

1 d a t a N a m e = N a m e { n a m e s p a c e :: Text , n a m e :: T e x t } 2 d e r i v i n g (Eq, Ord, S h o w)

Listing 2.17: Tipo Name di haskell-gi

Ad ogni modulo preso in considerazione da haskell-gi non sempre corrisponde un file generato in output. Questo infatti `e vero solo per interfacce, oggetti e struct. Le restanti API vengono raggruppate per tipo in un unico file.

1 GI 2 | - - Gtk 3 | | - - C a l l b a c k s . hs 4 | | - - C o n f i g . hs 5 | | - - C o n s t a n t s . hs 6 | | - - E n u m s . hs 7 | | - - F l a g s . hs 8 | | - - F u n c t i o n s . hs 9 | | - - I n t e r f a c e s 10 | | | - - *. hs 11 | | - - O b j e c t s 12 | | | - - *. hs 13 | | - - S t r u c t s 14 | | | - - *. hs 15 | - - Gtk . hs

(37)

Struttura di ocaml-gi-gtk

ocaml-gi-gtk [17] `e la libreria scritta in questa tesi. Come discusso nel capitolo prece-dente, questa `e stata scritta partendo da haskell-gi ed `e pertanto una libreria Haskell che genera i binding di GTK per OCaml. In realt`a, esattamente come per haskell-gi, GTK non `e l’unica libreria generabile, ma lo sono tutte quelle che usano GObject Introspec-tion. Ci`o nonostante, uno degli obiettivi di questa tesi era quello di generare dei binding che fossero il pi`u possibile simili a quelli attualmente disponibili in lablgtk3. Questo ha un duplice beneficio: rende il pi`u semplice possibile la conversione di applicazioni attual-mente scritte usando lablgtk3 (ad esempio Matita [1]) e ocaml-gi-gtk pu`o usare lablgtk3 come libreria riusando alcuni meccanismi di conversione tra OCaml e C gi`a collaudati in questa libreria.

3.1

Reperibilit`

a del codice

ocaml-gi-gtk `e una libreria open source ed `e disponibile su GitHub all’indirizzo https: //github.com/illbexyz/ocaml-gi-gtk. Facendo uso delle librerie lablgtk e haskell-gi, entrambe distribuite secondo la licenza LGPL, `e stata mantenuta la stessa licenza anche per ocaml-gi-gtk.

(38)

3.2

Struttura del progetto

ocaml-gi-gtk `e configurato come un progetto Stack [20]. Stack `e un build system per Haskell e si occupa anche di installare e gestire le dipendenze del progetto. Il progetto `e strutturato come segue:

1 | - - S e t u p . hs 2 | - - src 3 | | - - *. hs 4 | - - app 5 | | - - M a i n . hs 6 | - - base - o c a m l 7 | | - - c 8 | | | - - *. h 9 | | | - - *. c 10 | | - - g i l a b l g t k 3 11 | - - e x a m p l e s 12 | - - b i n d i n g s 13 | - - o v e r r i d e s 14 | - - g e n e r a t e . sh 15 | - - ocaml - gi - gtk . c a b a l 16 | - - p a c k a g e . y a m l 17 | - - s t a c k . y a m l

Listing 3.1: Struttura del progetto ocaml-gi-gtk I file principali sono:

• src, contiene i file sorgenti Haskell della libreria;

• app, contiene solo Main.hs, che `e configurato come il main file dell’eseguibile; • base-ocaml, `e il corrispettivo del modulo base di haskell-gi, ovvero contiene delle

funzioni di conversione usate dalle librerie generate. Nello specifico contiene una cartella c, dove sono presenti dei file C che verranno importati da quelli generati

dalla libreria per effettuare delle operazioni di conversione tra i tipi, e gilablgtk3, che `e una versione di lablgtk3 “ridotta” per usare solo le funzionalit`a utili a questo progetto.

(39)

• examples, contiene delle piccole applicazioni che fanno uso delle librerie generate e ne testano alcune funzionalit`a. Questi esempi sono gli stessi che sono forniti in lablgtk3, convertiti per usare le API delle librerie generate, che differiscono per alcuni particolari;

• bindings, dentro questa cartella verranno generate le librerie;

• generate.sh, `e uno shell script che facilita l’esecuzione di ocaml-gi-gtk;

• overrides, contiene gli overrides di haskell-gi. Sono dei file che sovrascrivono delle propriet`a contenute nei file GIR, in quanto `e possibile che queste siano incomplete o incorrette. Ne esiste uno per ogni libreria generabile;

• package.yaml, contiene le configurazioni del progetto Stack.

3.3

Definizione delle librerie generabili

Per generare una libreria, questa dev’essere prima configurata e resa nota ad ocaml-gi-gtk. In Main.hs sono contenute le definizioni di tutte le librerie generabili, ed ognuna contiene le seguenti informazioni:

1 d a t a L i b r a r y = L i b r a r y { 2 n a m e :: Text , 3 v e r s i o n :: Text ,

4 o v e r r i d e s F i l e :: M a y b e F i l e P a t h 5 }

Listing 3.2: Tipo associato ad una libreria generabile

I valoriname eversion sono usati dal modulo GIR di haskell-gi per ottenere il giusto file

GIR tra quelli installati nel sistema. Pertanto i valori da assegnare a questi campi sono da trovare nei file pkg.info contenuti nella cartella bindings di haskell-gi. overridesFile

richiede invece il percorso di un file (opzionale) che pu`o essere usato per sovrascrivere delle propriet`a contenute nel file GIR. Oltre a definire la libreria, questa deve essere anche aggiunta alla funzione parseArg, la funzione che si occupa di effettuare il parsing

(40)

1 p a r s e A r g :: S t r i n g - > IO L i b r a r y 2 p a r s e A r g " g l i b " = r e t u r n g l i b L i b 3 p a r s e A r g " g o b j e c t " = r e t u r n g o b j e c t L i b 4 p a r s e A r g " gio " = r e t u r n g i o L i b 5 p a r s e A r g " atk " = r e t u r n a t k L i b 6 p a r s e A r g " c a i r o " = r e t u r n c a i r o L i b 7 p a r s e A r g " p a n g o " = r e t u r n p a n g o L i b 8 p a r s e A r g " g d k p i x b u f " = r e t u r n g d k P i x b u f L i b 9 p a r s e A r g " gdk " = r e t u r n g d k L i b 10 p a r s e A r g " gtk " = r e t u r n g t k L i b 11 p a r s e A r g " g t k s o u r c e " = r e t u r n g t k S o u r c e L i b 12 p a r s e A r g " - h " = p r i n t U s a g e > > e x i t S u c c e s s 13 p a r s e A r g " - v " = p r i n t V e r s i o n > > e x i t S u c c e s s 14 p a r s e A r g _ = p r i n t U s a g e > > e x i t S u c c e s s

Listing 3.3: La funzione parseArg per il parsing degli input argument

Infine per ogni libreria generabile esiste un file*_includes.hdentro abase-ocaml/c. Questi

file contengono delle istruzioni di #include per importare gli header delle librerie a cui

fanno riferimento. Questi file verranno inclusi dai file C generati da ocaml-gi-gtk. `E stato scelto di definire questi file perch´e per alcune librerie `e necessario includere diversi header e talvolta anche definire delle costanti tramite #define.

3.4

Esecuzione di ocaml-gi-gtk

Attualmente ocaml-gi-gtk `e eseguibile solo in ambiente UNIX. Per eseguire facilmente ocaml-gi-gtk `e stato predisposto uno shell script generate.sh. Questo script si occupa di

settare delle variabili di ambiente necessarie alla compilazione della libreria e di lanciare la generazione e compilazione delle librerie. Le variabili di ambiente da definire sono:

• BASE_OCAML_C, deve essere settata al percorso della directory contenente gli header C di base-ocaml. Di default `e _project_root_/base-ocaml/c.

• GI_INCLUDES, ogni libreria generata contiene una directory include al proprio

(41)

da questa libreria. Questa variabile d’ambiente dovr`a contenere il percorso a tali directory per ogni libreria generabile, ed ogni percorso deve essere preceduto dal simbolo-I.

Il motivo della presenza di queste variabili di ambiente verr`a spiegato nella sezione 3.5. Lo script pu`o ricevere in input il nome di una o pi`u librerie da generare. `E presente anche l’opzione -fper generare tutte le librerie disponibili. Un’altra opzione disponibile

`

e -i, che serve per installare nel sistema la libreria appena generata. L’operazione di

installazione `e necessaria perch`e le librerie generabili hanno delle dipendenze tra loro. Quindi la compilazione (ed installazione) deve partire dalla libreria di pi`u basso livello, proseguendo fino ad arrivare a GTK, quella di pi`u alto livello. Per questo motivo il modo raccomandato di eseguire ocaml-gi-gtk, almeno la prima volta, `e quello di usare

./generate.sh -f -i, che compila ed installa le libreria nell’ordine corretto.

1 U s a g e : g e n e r a t e . sh [ O P T I O N ] LIB [ LIB . . . ] 2

3 A v a i l a b l e l i b s are : g l i b g o b j e c t gio atk c a i r o p a n g o g d k p i x b u f gdk gtk 4 5 O p t i o n s : 6 -h , ( h e l p ) D i s p l a y t h i s h e l p and e x i t 7 -f , ( f u l l ) G e n e r a t e e v e r y a v a i l a b l e l i b r a r y . The LIB a r g u m e n t s w i l l be i g n o r e d . 8 -i , ( i n s t a l l ) A f t e r the l i b r a r y g e n e r a t i o n , it w i l l i n s t a l l it u s i n g ’ d u n e install ’

Listing 3.4: Messaggio usage di generate.sh

3.5

Struttura delle librerie generate

Le librerie vengono generate all’interno della directory bindings come dei progetti

dune [2]. Dune `e un build system per OCaml che permette di descrivere come compilare il progetto attraverso dei file con nomedune. I filedune vengono scritti attraverso notazione

S-expression e, tramite keyword specifiche di dune, `e possibile specificare: metadati relativi al progetto, dipendenze, script da eseguire prima della compilazione, come linkare OCaml con codice C, ed altre propriet`a.

(42)

I file che troviamo all’interno di ogni libreria generata da ocaml-gi-gtk sono:

• dune-project, specifica che questa `e la root del progetto dune e specifica il nome della libreria;

• LibName, dove il nome varia a seconda del nome della libreria generata, `e una directory che contiene tutti i file OCaml che implementano i binding;

• include, directory che contiene tutti gli header C relativi ai file generati;

• tools, directory copiata da _project_root_/base-ocaml/tools, contiene delle

istru-zioni necessarie a dune per trovare le librerie di sistema relative a GTK; • dune, contiene le istruzioni necessarie per usare tools.

Inoltre dentro alla directory LibName `e presente un’ulteriore filedune. Questo file

istrui-sce il build system su quali file devono essere compilati e come devono essere linkati tra loro i file OCaml con quelli C. Le variabili di ambiente viste nella sezione precedente 3.4 vengono qui riportate (non espanse) per indicare al linker dove trovare gli header necessari. Inoltre, se la libreria corrente ha delle dipendenze verso altre librerie nella gerarchia di GTK, queste vengono indicate come dipendenze.

1 ( l a n g d u n e 2 . 0 ) 2 ( p a c k a g e

3 ( n a m e G I G t k ) )

Listing 3.5: Esempio di file dune-project (GTK)

1 ( env 2 ( _

3 ( b i n a r i e s

4 ./ t o o l s / d u n e _ c o n f i g . exe ) ) )

Listing 3.6: Esempio di file dune generato nella root del progetto

1 ( r u l e

2 ( t a r g e t s

3 cflag - gtk + -3.0. s e x p 4 clink - gtk + -3.0. s e x p )

(43)

5 ( a c t i o n ( run d u n e _ c o n f i g - pkg gtk + -3.0 - v e r s i o n 3 . 1 8 ) ) ) 6 ( l i b r a r y 7 ( n a m e G I G t k ) 8 ( p u b l i c _ n a m e G I G t k ) 9 ( l i b r a r i e s g i l a b l g t k 3 G I A t k G I G L i b G I G O b j e c t G I G d k G I G d k P i x b u f G I G i o G I P a n g o G I c a i r o ) 10 ( f l a g s : s t a n d a r d - w 6 7 9 10 27 32 33 34 35 36 50 52 no strict -s e q u e n c e ) 11 ( c _ l i b r a r y _ f l a g s (: i n c l u d e clink - gtk + -3.0. s e x p ) ) 12 ( f o r e i g n _ s t u b s 13 ( l a n g u a g e c ) 14 ( n a m e s A b o u t D i a l o g A c c e l G r o u p ... E n u m s ) 15 ( i n c l u d e _ d i r s %{ p r o j e c t _ r o o t }/ i n c l u d e ) 16 ( f l a g s (: i n c l u d e cflag gtk + 3.0. s e x p ) I $ B A S E _ O C A M L _ C $ G I _ I N C L U D E S -Wno - d e p r e c a t e d - d e c l a r a t i o n s ) ) )

(44)

Implementazione

L’implementazione di ocaml-gi-gtk `e iniziata effettuando un fork di haskell-gi ed analizzando come questo impostasse la generazione dei binding per Haskell per guidare la generazione dei binding per OCaml.

4.1

Impostazione della generazione

Le strutture costruite per impostare la generazione di codice di haskell-gi sono state mantenute e modificate per essere rese compatibili con la generazione di file OCaml. In particolare queste strutture si trovano dentro il file Code.hs, che contiene i meccanismi di generazione di pi`u basso livello. Gli altri file di pi`u alto livello che contengono la logica di generazione del codice usano le funzioni qui definite. Nello specifico vengono definite delle monadi che definiscono un contesto di computazione per la generazione del codice.

(45)

1 - - | The b a s e t y p e for the c o d e g e n e r a t o r m o n a d . 2 t y p e B a s e C o d e G e n e x c T y p e a 3 = R e a d e r T C o d e G e n C o n f i g ( S t a t e T ( CGState , M o d u l e I n f o ) ( E x c e p t e x c T y p e ) ) a 4 5 - - | C o d e g e n e r a t o r s t h a t c a n n o t t h r o w e r r o r s . 6 t y p e C o d e G e n a = f o r a l l e . B a s e C o d e G e n e a 7 8 - - | C o d e g e n e r a t o r s t h a t can t h r o w e r r o r s . 9 t y p e E x c C o d e G e n a = B a s e C o d e G e n C G E r r o r a

Listing 4.1: Monadi per la generazione del codice

Queste monadi vengono implementate tramite uno stack di monad transformer per combinare gli effetti delle monadi Reader, State ed Except. Rispettivamente servono per definire:

• una configurazione read-only disponibile a tutte le funzioni che operano dentro la monade. Questa conterr`a informazioni che non cambiano durante la generazione di un modulo, come il nome del modulo che sta venendo generato o la hashmap costruita dal parser che fa corrispondere i tipi Name ai tipi API visti nella sezione 2.5;

• uno stato della computazione disponibile a tutte le funzioni che operano dentro

la monade. Questo conterr`a informazioni che vengono modificate man mano che

si procede nella generazione di un modulo, come ad esempio il codice che viene generato, aggiunto man mano ad una sequenza;

• la possibilit`a di fallire la computazione. Viene definito anche un tipo CGError

che enumera i possibili errori. Le computazioni che possono dare luogo ad er-rori sono quelle dentro la monade ExcCodeGene vanno gestite attraverso la funzione handleCGExc.

(46)

1 d a t a M o d u l e I n f o = M o d u l e I n f o { 2 m o d u l e P a t h :: M o d u l e P a t h - - ^ F u l l m o d u l e n a m e : [" Gtk " , " L a b e l "]. 3 , m o d u l e C o d e :: C o d e - - ^ Low l e v e l O C a m l c o d e . 4 , g C o d e :: C o d e - - ^ H i g h l e v e l O C a m l c o d e 5 , t C o d e :: C o d e - - ^ T y p e d e c l a r a t i o n c o d e 6 , c C o d e :: C o d e - - ^ . c stubs ’ c o d e 7 , h C o d e :: C o d e - - ^ . h stubs ’ c o d e 8 , c D e p s :: D e p s - - ^ Set of d e p e n d e n c i e s for t h i s m o d u l e ( c - s i d e ) 9 , s u b m o d u l e s :: M . Map T e x t M o d u l e I n f o - - ^ I n d e x e d by the r e l a t i v e 10 - - m o d u l e n a m e . 11 } d e r i v i n g (S h o w)

Listing 4.2: Informazioni contenute dentro la stato di generazione di un modulo Per ogni tipo di file che viene creato (*.ml, *G.ml, *T.ml, *.c, *.h) viene mantenuto nello stato il codice generato. Per modificare queste variabili vengono definite dentro a Code.hs delle funzioniline che appendono una nuova linea di codice alla sequenza finora

generata. Per ogni tipo di file esiste una funzione differente (line, gline, tline, cline, hline).

Un’altra variabile interessante `e cDeps. Questa `e stata aggiunta per tenere traccia dell’insieme di header C dai quali il modulo corrente dipende. Tali file vengono aggiunti dalla funzioneaddCDepe verranno inclusi nell’intestazione del file *.c del modulo corrente.

4.1.1

Esecuzione del generatore

La generazione del codice ha inizio nella funzione genBindingsdi GI.hs. Qui vengono

effettuate delle configurazioni e viene richiamata la funzione genLibraryCodeper eseguire

il parsing e la generazione della libreria. Per ogni modulo individuato dal parser viene eseguita la funzione genAPI di CodeGen.hs:

1 g e n A P I :: N a m e - > API - > C o d e G e n () 2 g e n A P I _n ( A P I C o n s t _c ) = r e t u r n () 3 g e n A P I _n ( A P I F u n c t i o n _f ) = r e t u r n () 4 g e n A P I n ( A P I E n u m e ) = g e n E n u m n e 5 g e n A P I n ( A P I F l a g s f ) = g e n F l a g s n f 6 g e n A P I _n ( A P I C a l l b a c k _c ) = r e t u r n ()

(47)

7 g e n A P I n ( A P I S t r u c t s ) = g e n S t r u c t n s 8 g e n A P I n ( A P I U n i o n u ) = g e n U n i o n n u 9 g e n A P I n ( A P I O b j e c t o ) = g e n O b j e c t n o 10 g e n A P I n ( A P I I n t e r f a c e i ) = g e n I n t e r f a c e n i

Le funzionigen*chiamate da questa funzione sono quelle di alto livello che contengono

la logica per la generazione di ogni tipo di modulo. Quelle che contengonoreturn ()non

sono state gestite.

Una volta terminata la generazione di ogni modulo viene restituita una variabile di tipoModuleInfo. I moduli generati sono disposti secondo una struttura ad albero e

conte-nuti dentro questa variabile all’interno del camposubmodules. Al primo livello dell’albero

vengono creati dei collegamenti a dei nodi di tipo ModuleInfo e sono suddivisi a seconda

del tipo di modulo. Tale suddivisione `e effettuata dalla funzione submoduleLocation del

file QualifiedNaming.hs.

1 - - | C o n s t r u c t the s u b m o d u l e p a t h w h e r e the g i v e n API e l e m e n t w i l l 2 - - l i v e . T h i s is the p a t h r e l a t i v e to the r o o t for the c o r r e s p o n d i n g 3 - - n a m e s p a c e . I . e . the " GI . Gtk " p a r t is not p r e p e n d e d . 4 s u b m o d u l e L o c a t i o n :: N a m e - > API - > M o d u l e P a t h 5 s u b m o d u l e L o c a t i o n _ ( A P I C o n s t _ ) = " C o n s t a n t s " 6 s u b m o d u l e L o c a t i o n _ ( A P I F u n c t i o n _ ) = " F u n c t i o n s " 7 s u b m o d u l e L o c a t i o n _ ( A P I C a l l b a c k _ ) = " C a l l b a c k s " 8 s u b m o d u l e L o c a t i o n n ( A P I E n u m _ ) = " E n u m s " 9 s u b m o d u l e L o c a t i o n n ( A P I F l a g s _ ) = " E n u m s " 10 s u b m o d u l e L o c a t i o n n ( A P I I n t e r f a c e _ ) = " " /. u p p e r N a m e n 11 s u b m o d u l e L o c a t i o n n ( A P I O b j e c t _ ) = " " /. u p p e r N a m e n 12 s u b m o d u l e L o c a t i o n n ( A P I S t r u c t _ ) = " " /. u p p e r N a m e n 13 s u b m o d u l e L o c a t i o n n ( A P I U n i o n _ ) = " " /. u p p e r N a m e n

Listing 4.3: Funzione submoduleLocation di QualifiedNaming.hs

A questa suddivisione corrisponder`a il path che avr`a ogni modulo una volta che verranno scritti su disco i file generati. Ad esempio per ogni modulo di tipo Object ed Interface, viene creato un file separato, mentre i moduli di tipo Enum e Flag vengono generati tutti dentro gli stessi file Enums.*.

(48)

La scrittura su disco di ogni modulo viene effettuata dalla funzione writeModuleInfo

di Code.hs. Questa si occupa di controllare la presenza di codice dentro i campi*Code di ModuleInfo e nel caso ve ne sia, viene scritto il file su disco.

4.2

Conversione di tipi tra OCaml e C

Come spiegato nella sezione 2.3, per effettuare i binding tra OCaml e C `e necessario definire delle funzioni C chiamate stubs, che siano in grado di convertire i tipi OCaml in tipi C e viceversa. L’approccio usato per generare gli stubs `e lo stesso adottato da lablgtk. Nel filewrappers.h sono definite delle macroML_Xdove x `e il numero di parametri

che prende in input la funzione f di cui si vuole creare uno stub. La macro ha sempre come primo parametro il nome di f e come ultimo parametro il nome di una funzione per convertire da C ad OCaml il valore restituito da f . Tra questi due parametri sono attesi anche altri x parametri, dove ognuno `e il nome di una funzione per convertire da OCaml a C i valori che saranno passati ad f come parametro. La macro genera una funzione che riceve in input i valori OCaml che saranno usati da f , e si occupa di chiamare la funzione f con i parametri convertiti. Infine converte il valore restituito da f da C a OCaml e lo restituisce. In lablgtk il nome della funzione generata assume il nome di f con un prefisso ml_. In ocaml-gi-gtk questa convenzione `e stata modificata usando il

prefisso ml_gi, in modo da non creare conflitti tra i nomi delle funzioni generate dalle

due librerie.

1 # d e f i n e M L _ 2 ( cname , conv1 , conv2 , c o n v ) \

2 C A M L p r i m v a l u e m l _ g i ## c n a m e ( v a l u e arg1 , v a l u e a r g 2 ) \ 3 { r e t u r n c o n v ( c n a m e ( c o n v 1 ( a r g 1 ) , c o n v 2 ( a r g 2 ) ) ) ; }

Listing 4.4: Esempio di macro per la creazione di stub

In ocaml-gi-gtk le funzioni necessarie a convertire i tipi tra OCaml e C vengono speci-ficate nel file Conversions.hs. Le funzioni in questione sonoocamlValueToCecToOCamlValue,

entrambe prendono in input il tipoTypeda convertire e restituiscono il nome della

funzio-ne C che effettua la conversiofunzio-ne. Per i tipi di base vengono usate le funzioni di conversiofunzio-ne della libreria di interfacciamento con C di OCaml o delle macro definite da lablgtk. Per i

Figura

Figura 2.1: Architettura di un’applicazione GTK [11].
Tabella 5.1: Valutazione quantitativa dei binding generati

Riferimenti

Documenti correlati

Ci sono funzioni che non rispettano le istanze che non appartengono alla classe C delle funzioni primitive ricorsive, un esempio ` e stato gi` a mostrato prima, la funzione

Supponiamo di voler estendere le espressioni aritmetiche di IMP includendo un nuovo operatore “^” con il significato di elevamento a potenza. La sintassi di Aexp di IMP verrà

Negli anni sessanta, Christopher Strachey e Dana Scott riuscirono a superare le limitazioni della semantica operazionale introducendo una semantica più astratta basata sull’utilizzo

La sintassi specifica sia la struttura lessicale del linguaggio (come costruire le parole a partire dai caratteri dell’alfabeto) sia le regole per la costruzione delle

Si noti che nel comando if, comunque sia valutato b in σ’, esso viene interrotto dopo l’esecuzione di c e dunque in questo caso &lt;w,σ&gt; è valutato σ’ sse &lt;if,σ&gt;.

Passo induttivo: supponendo che l’asserto sia vero per un albero di derivazione alto n e per n cicli di valutazioni nella denotazione del while (con n &gt; 0, quindi anche per

L'iterazione ed il singolo ciclo terminano non appena viene incontrata una espressione booleana che valuta a falso”. Verificare che le due semantiche

Ma siccome stiamo valutando l’equivalenza dei due comandi if nel senso di un utilizzo alternativo degli stessi (nello stesso istante), qualsiasi scelta venga effettuata tra c 0 e c