Cours 6

Persistance, bibliothèque

& fonctions natives en C

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

1

🗺️ Plan du cours

Au programme aujourd'hui : on ajoute un disque dur à notre REPL, on lui donne une bibliothèque standard digne de ce nom, et on construit un pont entre le C et le LISP. Bref, on industrialise.
2

1. Où en sommes-nous ?

Cours 5 : un interprète LISP complet avec environnements, closures, cellules, objets, listes, transpileur Python.

Ce qui manque :

💾
Persistance
save / load
📚
Bibliothèque
prolog.lisp
FFI
C → LISP
🧩
Embedding
LISP dans C
Notre interprète LISP était jusqu'ici comme un ordinateur sans disque dur : puissant, mais incapable de retenir quoi que ce soit entre deux sessions. On va lui ajouter un SSD. Un SSD en C, évidemment — on n'est pas chez Apple.
3

2. save — sérialiser l'environnement

(save "f.lisp") écrit l'environnement courant dans un fichier LISP lisible :

Chaîne d'environnements

[2] ((x 42) (y (cell 10)))
↑ parent
[1] ((fact fun …))
↑ parent
[0] () ← globale

Fichier généré

(let x 42)
(let y (cell 10))
(function fact (n)
  (if (eq? n 1) 1
    (* n (fact (- n 1)))))
Format texte : chaque liaison est écrite sous forme d'expression LISP valide. Le fichier est à la fois exécutable par load et lisible par un humain. C'est du « code as data » à l'envers : les données deviennent du code.
4

2.1 load — lire et exécuter un fichier

(load "f.lisp") lit un fichier, le découpe en S-expressions et les évalue :

📄 f.lisp
🔍 Découpage
🧮 sexp_parse
⚙️ evaluer_env
✅ Résultat

Gestion des commentaires

; prolog.lisp — bibliothèque
(function fact (n)
  (if (< n 2) 1.0
    (* n (fact (- n 1)))))

Utilisation

$ ./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.
5

3. prolog.lisp — la bibliothèque standard

Chargé automatiquement au démarrage de let. C'est notre noyau :

🔢 Arithmétique

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

📋 Listes

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

⚙️ HOF

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

🧮 Trigonométrie

(function cos (x)
  (cos-iter x 0 10 1))

🔗 Composition

(function compose (f g)
  (lambda (x) (f (g x))))
(function deriv (f h)
  (lambda (x) ...))

✅ Prédicats

(function any (pred xs)
  (if (null? xs) 0
    (if (pred (car xs)) 1
      (any pred (cdr xs)))))
(function all (pred xs)
  ...)
prolog.lisp, c'est le dossier /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.
6

4. Fonctions natives — le pont C → LISP

Quand le LISP pur ne suffit pas, on appelle le C :

📄 (random)
⚙️ evaluer_env
🔍 is_native
💻 native_random()
✅ Sexp (float)

Signature

typedef Sexp *(*NativeFn)(Sexp *args, Env **env);

Type C

Sexp *sexp_native(const char *name, void *fn);

Exemple : sqrt

static 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));
}
Polymorphisme par type : closures et natives sont appelées de la même façon — le dispatch se fait par is_closure() / is_native(). Le LISP ne fait pas la différence.
7

4.1 Enregistrement des natives

register_native

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

Dans 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); }

Table des natives disponibles

NativeUsageImplémentation CRetour
random(random)rand()/RAND_MAXFloat [0, 1)
sqrt(sqrt x)sqrt(x) (math.h)Float
sin(sin x)sin(x) (math.h)Float
Les fonctions natives, c'est le laissez-passer diplomatique entre le monde LISP et le monde C. Le LISP peut appeler 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.
8

5. Embedding — LISP depuis le C

La réciproque : quand un traitement s'exprime mieux en LISP, on l'appelle depuis le C :

5.1 tabulator.c — tabulation via popen

Code C (48 lignes)

int 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 ... */
}

Exécution

$ ./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)
Division du travail : le C gère la ligne de commande et le formatage ; le LISP (via let) génère la plage de valeurs avec range et applique la formule avec map. Chacun fait ce qu'il sait faire.
9

5.2 Liaison directe (via sexpression.o)

Pour des appels fréquents, on lie statiquement la bibliothèque :

Code minimal

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

Programmes qui utilisent cette approche

ProgrammeRôle
evalenv.cÉvaluateur avec environnement
mapk.cMap sur une liste
filterk.cFiltre avec prédicat LISP
table.cZip + eval table
project.cProjection d'expressions
template.cÉvaluation d'expressions nommées
⚠️ popen vs liaison directe : 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.
10

6. Architecture complète de l'environnement

🔝 Programme utilisateur — votre code LISP
📚 prolog.lisp — map, filter, fold, range…
💾 save / load — persistance
Fonctions natives — random, sqrt, sin
🧩 let.c (interprète) — closures, envs, cellules
📦 sexpression.c/h — parseur, types

Du parseur au programme utilisateur : 6 couches, 3 fichiers C, 1 bibliothèque LISP.

11

Exercices

Niveau ★☆☆

✏️ Ex. 1 — save/load (★) : Lancez ./let, définissez des variables, (save "test.lisp"). Quittez, rechargez avec (load "test.lisp").
✏️ Ex. 2 — prolog (★) : Calculez (map (lambda (x) (* x x)) (iota 10)). Que donne (foldl + 0 (iota 100)) ?
✏️ Ex. 3 — natives (★) : Testez (random), (sqrt 144), (sin 0). Que se passe-t-il avec (sqrt -1) ?

Niveau ★★☆

✏️ Ex. 4 — dérivée (★★) : Utilisez deriv pour calculer la dérivée de x² en x=3. L'approximation avec h=0.001 donne quoi ?
✏️ Ex. 5 — tabulateur manuel (★★) : Générez une table de sin(x) sur [0, π] pas 0.1 avec range + map.
✏️ Ex. 6 — ajouter une native (★★) : Ajoutez cos à let.c. Comparez avec la version LISP de prolog.lisp.

Niveau ★★★

✏️ Ex. 7 — intégration (★★★) : Écrivez (integral f a b n) par la méthode des rectangles. Testez sur x² de 0 à 1.
✏️ Ex. 8 — session auto (★★★) : Modifiez let.c pour sauvegarder automatiquement l'env dans .let_session.
12

Félicitations — cours 6 terminé !

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.

📄 Fichier clé
cours6-persistance.html
📚 Concept du jour
Persistance + FFI + Bibliothèque
🛠️ Commandes
./let '(save "x.lisp")' · ./tabulator
Vous avez maintenant un langage de programmation avec bibliothèque standard, persistance, FFI et embedding. Bref, vous avez réinventé Python. En C. En 880 lignes. Ne dites pas à Guido.
« 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. »
13