Come è di norma per molti altri linguaggi, la libreria standard principale di Hybris è composta da moduli dinamici che vengono caricati dall'interprete in base alla scelta dell'utente, tramite la direttiva import. Questi moduli, di default1) vengono installati nella directory
/usr/lib/hybris/library/
E seguono un alberatura di directory ben precisa, che rappresenta il loro “namespace”. Ad esempio, il comando :
import std.io.console;
Caricherà il modulo :
/usr/lib/hybris/library/std/io/console.so
e così via.
Per sviluppare un modulo, è necessaria una buona conoscenza del C/C++, un ambiente di sviluppo che soddisfi i requisiti per la compilazione e la conoscenza di alcune API che il kit di sviluppo di Hybris offre .
Andiamo ora a vedere un modulo molto semplice che esporterà la funzione say_hello .
#include <hybris.h> HYBRIS_DEFINE_FUNCTION(say_hello); HYBRIS_EXPORTED_FUNCTIONS() { { "say_hello", say_hello, H_REQ_ARGC(0) }, { "", NULL } }; HYBRIS_DEFINE_FUNCTION(say_hello){ printf( "Hello world from my first Hybris module!\n" ); return H_DEFAULT_RETURN; }
Analiziamo i vari elementi che compongono il modulo.
#include <hybris.h>
Con questa direttiva includiamo l'header hybris.h necessario per poter utilizzare le API del kit di sviluppo dei moduli.
HYBRIS_DEFINE_FUNCTION(say_hello);
Dichiariamo con questa macro, il prototipo della funzione che vogliamo esportare.
HYBRIS_EXPORTED_FUNCTIONS() { { "say_hello", say_hello, H_REQ_ARGC(0) }, { "", NULL } };
Questa lista contiene la descrizione vera e propria della funzione che stiamo esportando :
HYBRIS_DEFINE_FUNCTION(say_hello){ printf( "Hello world from my first Hybris module!\n" ); return H_DEFAULT_RETURN; }
Questa è la funzione vera e propria, che stamperà il messaggio “Hello world from my first Hybris module!”. Ogni funzione deve restituire un valore quindi, in questo caso, utiliziamo la macro H_DEFAULT_RETURN ad indicare che stiamo restituendo un valore di default non dovendo restituire nulla in particolare.
Per compilare il modulo appena creato, utilizzeremo i seguenti comandi :
g++ -c -export-dynamic -fPIC hello.cpp g++ -shared -lc -o hello.so hello.o -lhybris
Dove :
Per installare un modulo, bisogna prima decidere il suo namespace, ovvero in quale alberatura di directory si troverà e di conseguenza quali namespace passare alla direttiva import per caricarlo. Nel nostro caso, ipotiziamo il namespace “test”, di conseguenza il file del modulo andrà copiato in :
/usr/lib/hybris/library/test/hello.so
E potrà essere richiamato ed usato come nel seguente esempio :
import test.hello; say_hello();
Hello world from my first Hybris module!
Nel precedente modulo, abbiamo considerato una semplice funzione che non accetta argomenti in ingresso, ne restituisce alcun valore (se non quello di default) in output. Consideriamo ora un esempio più elaborato, un modulo “math” che esporta la funzione “add” la quale eseguirà l'addizione tra due numeri e ne restituirà il risultato. In questo caso la funzione, accettando due parametri, deve essere impostata per accettare anche due tipologie diverse (delle quali parleremo in un paragrafo successivo) per ogni parametro, ovvero ognuno dei due parametri potrà essere o un numero intero o un numero reale.
#include <hybris.h> HYBRIS_DEFINE_FUNCTION(add); HYBRIS_EXPORTED_FUNCTIONS() { { "add", add, H_REQ_ARGC(2), { H_REQ_TYPES( otInteger, otFloat ), H_REQ_TYPES( otInteger, otFloat ) } }, { "", NULL } }; HYBRIS_DEFINE_FUNCTION(add){ // prendo gli argomenti passati alla funzione dallo script Object *arg_0, *arg_1; arg_0 = ob_argv(0); arg_1 = ob_argv(1); // prelevo il valore double dai due argomenti, in base alla loro // tipologia double a, b; if( ob_is_int( arg_0) ){ a = (double)ob_int_val( arg_0 ); } else{ a = ob_float_val( arg_0 ); } if( ob_is_int( arg_1 ) ){ b = (double)ob_int_val( arg_1 ); } else{ b = ob_float_val( arg_1 ); } // eseguo la somma e restituisco un numero reale come risultato return (Object *)gc_new_float( a + b ); }
Come è possibile vedere, l'esempio è leggermente diverso dal modulo precedente, andiamo ad analizzare le differenze principali .
{ "add", add, H_REQ_ARGC(2), { H_REQ_TYPES( otInteger, otFloat ), H_REQ_TYPES( otInteger, otFloat ) } }
Mentre prima utilizzavamo H_REQ_ARGC(0) e basta, poichè la funzione non richiedeva dei parametri in ingresso, questa volta la funzione richiede due parametri in ingresso ( H_REQ_ARGC(2) ), ognuno dei quali può rispettivamente essere un numero intero o un numero reale H_REQ_TYPES( otInteger, otFloat ) .
// prendo gli argomenti passati alla funzione dallo script
Object *arg_0,
*arg_1;
arg_0 = ob_argv(0);
arg_1 = ob_argv(1);
Ogni oggetto di Hybris, è rappresentato dalla struttura “Object” (definita nel file types.h) che naturalmente non può essere direttamente usato come un tipo C/C++. In questo blocco di codice, tramite la macro ob_argv( n ) dove n rappresenta l'n-esimo argomento della funzione che ci interessa, preleviamo i due argomenti della funzione add e li mettiamo dentro le variabili arg_0 (il primo argomento) e arg_1 (il secondo).
// prelevo il valore double dai due argomenti, in base alla loro // tipologia double a, b; if( ob_is_int( arg_0) ){ a = (double)ob_int_val( arg_0 ); } else{ a = ob_float_val( arg_0 ); } if( ob_is_int( arg_1 ) ){ b = (double)ob_int_val( arg_1 ); } else{ b = ob_float_val( arg_1 ); }
Qui invece eseguiamo la trasformazione da struttura Object ad una variabile di tipologia double, quindi compatibile con il C/C++, per effettuare comodamente la nostra addizione. Dal momento che ognuno degli argomenti può essere sia un intero che un numero reale, utiliziamo la macro ob_is_int per determinare se il numero è intero, ed in tal caso ne preleviamo il valore int tramite la macro ob_int_val e lo castiamo a double. Viceversa, se la variabile è di tipo reale (quindi ob_is_int restituirà false), preleviamo direttamente il suo valore tramite la macro ob_float_val. A questo punto, abbiamo i valori double dentro le variabili a e b e possiamo procedere ad effettuare l'addizione e a restituire un oggetto reale.
return (Object *)gc_new_float( a + b );
Il cast ad “Object *” è necessario poichè, a prescindere dalla sua tipologia (reale, intero, stringa, ecc), il valore di ritorno di ogni funzione deve essere riportato ad una tipologia base comune, appunto la struttura Object. La macro gc_new_float richiederà al garbage collector di creare un nuovo oggetto di tipo reale, inizializzandolo con il valore della somma “a + b”.
Se il modulo avesse la necessità di eseguire determinate operazioni, ad esempio inizializzare delle variabili globali, durante la fase di caricamento, deve esportare la funzione hybris_module_init nel seguente modo :
extern "C" void hybris_module_init( vm_t * vm ){ printf( "Hello World!\n" ); }
La struttura vm_t identifica un istanza alla virtual machine stessa e non va assolutamente toccata, a meno che non si è perfettamente consapevoli di cosa si sta facendo. Se esportata quindi, questa funzione verrà eseguita all'avvio del modulo.
Se il modulo avesse la necessità di definire alcune variabili costanti (che quindi sono visibili globalmente nello script e che non possono essere modificate), come ad esempio, nel caso del modulo math precedentemente analizzato, il valore del pi greco, può usare la macro HYBRIS_DEFINE_CONSTANT all'interno della funzione di inizializzazione appena vista, come ad esempio :
extern "C" void hybris_module_init( vm_t * vm ){ HYBRIS_DEFINE_CONSTANT( vm, "PI", gc_new_float(3.14159) ); }
Dove
bool ob_is_type_in( Object *o, ... );
Restituisce true se l'oggetto 'o' è di una delle tipologie specificate nei parametri successivi, altrimenti restituisce false.
const char *ob_typename( Object * o );
Restituisce la tipologia dell'oggetto 'o' sotto forma di stringa.
Object* ob_clone( Object *o );
Crea un clone dell'oggetto 'o'.
size_t ob_get_size( Object *o );
Restituisce la grandezza dell'oggetto 'o' o, nel caso sia una collezione, il numero degli elementi che contiene.
int ob_cmp( Object *o, Object * cmp );
Esegue un confronto tra l'oggetto 'o' e 'cmp', restituisce :
long ob_ivalue( Object *o );
Esegue, se possibile, il cast ad int dell'oggetto e ne restituisce il valore intero.
double ob_fvalue( Object *o );
Esegue, se possibile, il cast a double dell'oggetto e ne restituisce il valore double.
bool ob_lvalue( Object *o );
Restituisce la rappresentazione logica dell'oggetto.
string ob_svalue( Object *o );
Restituisce la rappresentazione sotto forma di stringa dell'oggetto.
void ob_print( Object *o, int tabs = 0 );
Stampa l'oggetto 'o' sullo stdout, precedendolo con 'tabs' tabulazioni.
Object *ob_to_string( Object *o );
Converte, se possibile, l'oggetto 'o' in un oggetto stringa.
Object *ob_to_int( Object *o );
Converte, se possibile, l'oggetto 'o' in un oggetto intero.
Object *ob_cl_push( Object *a, Object *b );
Crea un clone di 'b' e richiama ob_cl_push_reference per aggiungere in coda il clone alla collezione 'a'.
Object *ob_cl_push_reference( Object *a, Object *b );
Aggiunge in coda l'oggetto 'b' alla collezione 'a'.
Object *ob_cl_pop( Object *o );
Rimuove l'ultimo elemento della collezione 'o' e lo restituisce.
Object *ob_cl_remove( Object *a, Object *b );
Rimuove l'elemento all'indice (o chiave se una mappa) 'b' dalla collezione 'a'.
Object *ob_cl_at( Object *a, Object *b );
Restituisce l'elemento all'indice (o chiave se una mappa) 'b' dalla collezione 'a'.
Object *ob_cl_set( Object *a, Object *b, Object *c );
Crea un clone di 'c' e richiama ob_cl_set_reference( a, b, clone ) per inserire il clone all'indice/chiave 'b' nella collezione 'a'.
Object *ob_cl_set_reference( Object *a, Object *b, Object *c );
Inserisce l'oggetto 'c' all'indice/chiave 'b' dentro la collezione 'a'.