Skip to content

Entries tagged "scheme".

Découverte de Tinyscheme

Tinyscheme est une implémentation de Scheme qui implémente la plupart des fonctionnalitées du R5RS facilement embarquable dans des programmes en C, au même titre que Guile qui, lui, est beaucoup plus complet. Néanmoins on ne souhaite pas spécialement avoir toutes les fonctionnalitées que propose Guile et on peut souvent se limiter à Tinyscheme, qui est plus simple à utiliser.

Un autre avantage de Tinyscheme par rapport à Guile est qu'on peut simplement inclure Tinyscheme avec son programme plutôt que de l'ajouter en dépendance et devoir l'installer soi-même sur son système d'exploitation (il n'est notamment pas dans les ports FreeBSD)

Installation

Vous pouvez installer Tinyscheme via votre gestionnaire de paquet ou bien plus simplement le placer dans un sous dossier de votre projet par exemple. Vous pourrez alors linker le tout facilement grâce au paramètre -L de gcc:

gcc foo.c -o foo -Ltinyscheme-1.39/

Parcourez aussi les fichiers scheme.h et scheme-private.h (que vous incluerez bien évidemment dans chaque fichier utilisant tinyscheme), ils vous en apprendront bien plus sur tinyscheme que ce que billet vous apprendra.

Initialisation

Avant toute chose, il faut connaître les deux types utilisés par Tinyscheme:

  • un pointer représente un « objet » Scheme, c'est-à-dire n'importe quelle valeur manipulable en Scheme ;
  • un scheme représente l'environnement Scheme dans son état actuel.

L'initialisation de Tinyscheme se fait via la fonction scheme_init_new, qui retourne un nouveau scheme * que l'on passera par la suite en premier argument à toute fonction de Tinyscheme. Pour finaliser cette initialisation, il faut aussi charger le fichier init.scm qui définit une grosse partie du langage (en effet, il est beaucoup plus simple d'implémenter Scheme en Scheme qu'en C). Pour charger ce fichier on passe par scheme_load_file. Il est aussi utile de définir stdout comme port de sortie en passant par la fonction scheme_set_output_port.

On peut donc définir une fonction initialize_scheme qui fera tout ça pour nous:

scheme *
initialize_scheme()
{
        FILE *init;
        scheme *sc;

        sc = scheme_init_new();
        if ((init = fopen("init.scm", "r")) == NULL) {
                perror("fopen");
                return NULL;
        }
        scheme_load_file(sc, init);
        fclose(init);

        scheme_set_output_port_file(sc, stdout);
        return sc;
}

Il faudra aussi appeler scheme_deinit à la fin de notre programme pour arrêter proprement Tinyscheme.

Évaluer du code scheme

Pour charger un fichier Scheme il suffit d'utiliser la fonction scheme_load_file de la même façon qu'on l'a utilisée pour charger init.scm Pour directement évaluer du code Scheme depuis le C on utilisera scheme_load_string:

scheme_load_string(sc, "(display \"Hello, world!\") (newline)");

Manipuler des données

Il est très simple de créer des objets Scheme depuis le code C. Pour cela, regarder les fonctions du type mk_* dans scheme.h. On peut aussi définir des variables via scheme_define, une variable étant caractérisée par:

  • sa portée
  • son nom (un symbole)
  • sa valeur

Ainsi, pour faire (define foo 42):

pointer value = mk_integer(sc, 42);
scheme_define(sc, sc->global_env, mk_symbol(sc, "foo"), value);

Définir des fonctions

Une fonction se définit comme une variable en Scheme, par exemple:

(define 1+
  (lambda (x)
    (+ 1 x)))

On utilisera donc toujours scheme_define pour définir une fonction et il suffit d'utiliser mk_foreign_func pour récupérer un pointer à partir d'une fonction définie en C. Cette fonction doit retourner un pointer et prendre en argument un scheme * représentant l'état actuel de l'interpréteur ainsi qu'un pointer qui contient les arguments passés à cette fonction.

Grâce à cela, on peut donc ajouter de nouvelles fonctions qu'il serait impossibles de définir en Scheme, par exemple getenv (redéfinir 1+ en C n'aurait aucun intérêt vu la simplicité de cette définition en Scheme) :

pointer
scm_getenv(scheme *sc, pointer args)
{
        char *value;
        if (args == sc->NIL 
              || !is_string(pair_car(args))
              || (value = getenv(string_value(pair_car(args)))) == NULL)
                return sc->F;
        return mk_string(sc, value);
}

La valeur sc->NIL est assez explicite, et sc->F représente le booléen #f (sc->T représente quant à lui #t). Il faut bien évidemment informer Tinyscheme de l'existence de cette fonction:

scheme_define(sc, sc->global_env,
              mk_symbol(sc, "getenv"), mk_foreign_func(sc, scm_getenv))

Conclusion

Ce qui a été présenté ici couvre − je pense − la plupart des fonctionnalitées de Tinyscheme utilisées par des programmes tels que Tiny-Fu, le système de script-fu de GIMP.

Il est donc très facile d'embarquer Tinyscheme dans vos applications, et il est aussi simple d'écrire des extensions pour Tinyscheme (il suffira de définir un ensemble de fonctions et de compiler le tout en -shared). Pour des exemples concrets d'extensions, regardez TSX ainsi que l'extension regex.

La documentation de Tinyscheme étant inexistante (excepté les fichiers Manual.txt et hack.txt inclus avec Tinyscheme) si vous souhaitez plus d'exemples, il est essentiel de se plonger dans les sources de Tiny-Fu, de TSX ainsi que d'autres logiciels utilisant Tinyscheme si nécessaire.