Du REPL à l'environnement de développement complet
📖 Document complet du cours · 🎯 prolog.lisp
Laurent Thiry — Programmation en C
« Un langage sans bibliothèque standard, c'est comme un couteau
suisse sans lame : techniquement c'est un outil, mais vous allez
avoir du mal à couper votre fromage. »
save & load — persistanceprolog.lisp — la bibliothèque standardCours 5 : un interprète LISP complet avec environnements, closures, cellules, objets, listes, transpileur Python.
Ce qui manque :
save — sérialiser l'environnement(save "f.lisp") écrit l'environnement courant dans un fichier LISP lisible :
(let x 42)
(let y (cell 10))
(function fact (n)
(if (eq? n 1) 1
(* n (fact (- n 1)))))
load et lisible par un humain. C'est du « code as data » à l'envers : les données deviennent du code.load — lire et exécuter un fichier(load "f.lisp") lit un fichier, le découpe en S-expressions et les évalue :
; prolog.lisp — bibliothèque (function fact (n) (if (< n 2) 1.0 (* n (fact (- n 1)))))
$ ./let '(load "prolog.lisp")' $ ./let '(load "session.lisp")' $ ./let > (load "config.lisp")
load, c'est le bouton « continuer » de votre partie de LISP. Vous avez sauvegardé hier soir ? Vous chargez ce matin et tout est là. Sauf que c'est du code, pas une sauvegarde de Skyrim.prolog.lisp — la bibliothèque standardChargé automatiquement au démarrage de let. C'est notre noyau :
(function fact (n)
(if (< n 2) 1.0
(* n (fact (- n 1)))))
(function puissance (x n)
(if (eq? n 0) 1
(* x (puissance x (- n 1)))))
(function range (s st stp)
(if (> s stp) (quote ())
(cons s (range (+ s st) st stp))))
(function length (xs)
(if (null? xs) 0
(+ 1 (length (cdr xs)))))
(function map (f vs)
(if (null? vs) (quote ())
(cons (f (car vs)) (map f (cdr vs)))))
(function foldl (f i xs)
(if (null? xs) i
(foldl f (f i (car xs)) (cdr xs))))
(function cos (x) (cos-iter x 0 10 1))
(function compose (f g) (lambda (x) (f (g x)))) (function deriv (f h) (lambda (x) ...))
(function any (pred xs)
(if (null? xs) 0
(if (pred (car xs)) 1
(any pred (cdr xs)))))
(function all (pred xs)
...)
/etc de votre OS LISP : au démarrage, tout est chargé, tout est prêt. Sauf qu'au lieu de configurer des démons, on définit map, filter et foldl.Quand le LISP pur ne suffit pas, on appelle le C :
typedef Sexp *(*NativeFn)(Sexp *args, Env **env);
Sexp *sexp_native(const char *name, void *fn);
sqrtstatic Sexp *native_sqrt(Sexp *a, Env **e) { Sexp *v = evaluer_env(a->car, e); double x = (v->type == SEXPR_FLOAT) ? v->reel : v->entier; sexp_free(v); return sexp_float(sqrt(x)); }
is_closure() / is_native(). Le LISP ne fait pas la différence.static void register_native(Env *env, const char *name, NativeFn fn) { env->bindings = sexp_cons( sexp_cons(sexp_sym(name), sexp_cons( sexp_native(name, fn), NULL)), env->bindings); }
main()register_native(env, "random", native_random); register_native(env, "sqrt", native_sqrt); register_native(env, "sin", native_sin); /* charge la bibliothèque */ FILE *f = fopen("prolog.lisp", "r"); if (f) { fclose(f); file_load("prolog.lisp", &env); }
| Native | Usage | Implémentation C | Retour |
|---|---|---|---|
random | (random) | rand()/RAND_MAX | Float [0, 1) |
sqrt | (sqrt x) | sqrt(x) (math.h) | Float |
sin | (sin x) | sin(x) (math.h) | Float |
sin comme si c'était une fonction LISP — mais derrière, c'est du C pur, rapide, sans récursion. Le meilleur des deux mondes.La réciproque : quand un traitement s'exprime mieux en LISP, on l'appelle depuis le C :
tabulator.c — tabulation via popenint main(int argc, char *argv[]) { char cmd[8192]; snprintf(cmd, sizeof(cmd), "./let '(map (lambda (x) " "(cons x %s)) (range %s %s %s))'", argv[1], argv[2], argv[3], argv[4]); FILE *fp = popen(cmd, "r"); /* ... parse la sortie ... */ }
$ ./tabulator '(+ (* x x) (* 3 x))' -2 0.5 2 (-2 . -2) (-1.5 . -2.25) (-1 . -2) (-0.5 . -1.25) (0 . 0) (0.5 . 0.75) (1 . 4) (1.5 . 6.75) (2 . 10)
let) génère la plage de valeurs avec range et applique la formule avec map. Chacun fait ce qu'il sait faire.sexpression.o)Pour des appels fréquents, on lie statiquement la bibliothèque :
#include "sexpression.h" int main() { Sexp *expr = sexp_parse("(+ 1 2)"); Sexp *res = evaluer(expr, NULL); sexp_print(res); // 3 sexp_free(res); sexp_free(expr); return 0; }
| Programme | Rôle |
|---|---|
evalenv.c | Évaluateur avec environnement |
mapk.c | Map sur une liste |
filterk.c | Filtre avec prédicat LISP |
table.c | Zip + eval table |
project.c | Projection d'expressions |
template.c | Évaluation d'expressions nommées |
popen crée un sous-processus à chaque appel. C'est lent. Pour des appels fréquents, préférez la liaison directe. Le tabulator s'en sort parce qu'il ne fait qu'un seul appel.Du parseur au programme utilisateur : 6 couches, 3 fichiers C, 1 bibliothèque LISP.
./let, définissez des variables, (save "test.lisp"). Quittez, rechargez avec (load "test.lisp").(map (lambda (x) (* x x)) (iota 10)). Que donne (foldl + 0 (iota 100)) ?(random), (sqrt 144), (sin 0). Que se passe-t-il avec (sqrt -1) ?deriv pour calculer la dérivée de x² en x=3. L'approximation avec h=0.001 donne quoi ?range + map.cos à let.c. Comparez avec la version LISP de prolog.lisp.(integral f a b n) par la méthode des rectangles. Testez sur x² de 0 à 1..let_session.
Vous avez ajouté la persistance, une bibliothèque standard,
des fonctions natives et l'embedding à votre interprète LISP.
Votre langage a maintenant une API, un noyau et une FFI.
./let '(save "x.lisp")' · ./tabulator