let.c — le langage complet
8 exercices pour ajouter cellules, objets, natives, bibliothèque et persistance
Laurent Thiry — Programmation en C
« Au début, on écrit printf("Hello World").
À la fin, on écrit son propre langage de programmation.
Entre les deux, il y a du café, des crashes, et des révélations. »
cell, cell-ref, mutationsqrt, random en CLe type SEXPR_CELL encapsule un entier modifiable.
(cell-ref (cell 42)) → 42(swap! a b) qui échange le contenu de deux cellules(let a (cell 1)) (let b (cell 2)) (swap! a b) (cell-ref a) → 2/* a) + b) cellules en C */ Sexp *cell = sexp_cell(42); printf("%lld\n", sexp_cell_ref(cell)); /* 42 */ sexp_cell_set(cell, 100); printf("%lld\n", sexp_cell_ref(cell)); /* 100 */ /* c) swap! en LISP via let.c */ ;; (let swap! ;; (lambda (a b) ;; (let tmp (cell-ref a)) ;; (set-cell! a (cell-ref b)) ;; (set-cell! b tmp)))
(set! x 42) change ce que x désigne ; (set-cell! c 42) change le contenu de la boîte.Un objet est une fermeture qui reçoit des messages et retourne des valeurs.
(make-compteur) qui renvoie un objet compteur(compteur 'inc), (compteur 'dec), (compteur 'val)(make-compte solde-initial)'depot montant, 'retrait montant, 'solde'historique au compte bancaire(compte 'historique) → ((depot 100) (retrait 30) (depot 50));; a) compteur (let make-compteur (lambda () (let compteur (cell 0)) (lambda (msg) (if (eq? msg 'inc) (set-cell! compteur (+ (cell-ref compteur) 1))) (if (eq? msg 'dec) (set-cell! compteur (- (cell-ref compteur) 1))) (if (eq? msg 'val) (cell-ref compteur))))) ;; b) + c) compte bancaire avec historique (let make-compte (lambda (solde-initial) (let solde (cell solde-initial)) (let hist '()) (lambda (msg . args) (if (eq? msg 'depot) (begin (set-cell! solde (+ (cell-ref solde) (car args))) (set! hist (cons (cons 'depot args) hist)))) (if (eq? msg 'retrait) (if (>= (cell-ref solde) (car args)) (begin (set-cell! solde (- (cell-ref solde) (car args))) (set! hist (cons (cons 'retrait args) hist))) (display "Solde insuffisant\n"))) (if (eq? msg 'solde) (cell-ref solde)) (if (eq? msg 'historique) hist))))
Découvrez le fichier prolog.lisp qui contient 21 fonctions prêtes à l'emploi.
./let et tapez (load "prolog.lisp")(map square '(1 2 3)), (filter odd? '(1 2 3 4))range pour générer une liste : (range 1 10)prime?(prime? n) qui teste si n est premierrange, filter et lengthfilter avec votre fonction prime?;; a) chargement (load "prolog.lisp") ;; b) prime? (let prime? (lambda (n) (if (< n 2) 0 (= 0 (length (filter (lambda (d) (= 0 (% n d))) (range 2 (sqrt n)))))))) ;; c) tous les premiers ≤ 100 (filter prime? (range 2 100))
map, filter, reduce, range, odd?, even?, square, cube, abs, max, min, length, reverse, append, nth, take, drop, member?, zip, flatten, range.Ajoutez des fonctions implémentées en C directement accessibles depuis LISP.
native_sqrt en C : signature type(sqrt 144) → 12.0native_random qui retourne un entier aléatoire entre 0 et n-1(time) qui retourne l'horodatage Unix (secondes depuis 1970)(sleep n) qui suspend l'exécution pendant n secondes/* a) sqrt native */ #include <math.h> #include <time.h> #include <unistd.h> Sexp *native_sqrt(Sexp *args, void *env) { double v = (double)sexp_car(args)->entier; return sexp_float(sqrt(v)); } /* b) random */ Sexp *native_random(Sexp *args, void *env) { int n = sexp_car(args)->entier; return sexp_int(rand() % n); } /* c) time + sleep */ Sexp *native_time(Sexp *args, void *env) { return sexp_int(time(NULL)); } Sexp *native_sleep(Sexp *args, void *env) { sleep(sexp_car(args)->entier); return NULL; } /* enregistrement */ env_bind(env, sexp_sym("sqrt"), sexp_native(&native_sqrt)); env_bind(env, sexp_sym("random"), sexp_native(&native_random)); env_bind(env, sexp_sym("time"), sexp_native(&native_time)); env_bind(env, sexp_sym("sleep"), sexp_native(&native_sleep));
Ajoutez la capacité de sauvegarder et restaurer l'état de votre environnement.
(save "etat.lisp") qui écrit tous les bindings dans un fichier(let x 42) par ligne(load "etat.lisp") qui lit et évalue chaque ligneload préserve l'ordre des définitions.let-autosave.lisp toutes les 10 modifications/* a) save */ void native_save(Env *env, const char *path) { FILE *f = fopen(path, "w"); for (Sexp *b = env->bindings; b; b = b->cdr) { Sexp *pair = b->car; fprintf(f, "(let %s %lld)\n", pair->car->symbole, pair->cdr->entier); } fclose(f); } /* b) load : évalue chaque ligne */ Sexp *native_load(Sexp *args, void *env) { FILE *f = fopen(sexp_car(args)->symbole, "r"); if (!f) { perror("load"); return NULL; } char line[1024]; while (fgets(line, sizeof(line), f)) { eval(sexp_parse(line), env); } fclose(f); return NULL; }
save écrit uniquement les valeurs entières. Si vous avez des fermetures ou des listes, il faudrait les sérialiser récursivement. Challenge accepté ?Écrivez un jeu complet : l'ordinateur choisit un nombre, le joueur doit le deviner.
(random 100) pour générer un nombre entre 0 et 99(display) et (read) pour les entrées/sorties;; a) + b) jeu basique avec compteur (load "prolog.lisp") (let mystere (random 100)) (let tentatives 0) (display "Devinez le nombre (0-99)\n") (let fini 0) (while (not fini) (let essai (read)) (set! tentatives (+ tentatives 1)) (if (= essai mystere) (begin (display "Gagné !\n") (display (concat "Tentatives : " tentatives "\n")) (set! fini 1)) (if (< essai mystere) (display "Trop petit\n") (display "Trop grand\n")))) ;; c) mode deux joueurs (display "J1 : choisissez un nombre\n") (let mystere (read)) (display "J2 : devinez\n") ...
Défis supplémentaires pour enrichir votre langage. Solutions fournies.
Ajoutez (display val) et (print val) pour afficher depuis LISP.
display affiche sans retour à la ligne, print avec.
Ajoutez les opérateurs logiques avec court-circuit.
(and (> 3 2) (< 5 10)) → vrai
(quote (1 2 3)) retourne la liste sans l'évaluer.
(eq? 'a 'a) compare des symboles.
Calculez le nombre d'or φ par itération : φ = 1 + 1/(1 + 1/(1 + ...))
Implémentez une fonction (phi n) avec n itérations.
;; display / print (Côté C) Sexp *native_display(Sexp *args, void *env) { sexp_print(sexp_car(args)); return NULL; } ;; and / or en C (court-circuit) if (!strcmp(op, "and")) { Sexp *cur = args; while (cur) { if (!eval(cur->car, env)) return sexp_int(0); cur = cur->cdr; } return sexp_int(1); } ;; nombre d'or en LISP (let phi (lambda (n) (let f (lambda (acc k) (if (= k 0) acc (f (+ 1 (/ 1 acc)) (- k 1))))) (f 1 n)))
let.c si vous êtes bloqué.Ajoutez une fonction (type-of val) qui retourne le type d'une valeur :
(type-of 42) → 'integer(type-of 3.14) → 'float(type-of '(1 2)) → 'pair(type-of (lambda (x) x)) → 'closureIndice
Écrivez une fonction qui affiche les S-expressions formatées avec indentation.
Exemple :
(let fib
(lambda (n)
(if (< n 2) n
(+ (fib (- n 1))
(fib (- n 2))))))
Chaque niveau d'imbrication ajoute 2 espaces.
(json->string '((a . 1) (b . (2 3)))) → '{"a":1,"b":[2,3]}'. Gérer les entiers, symboles (→ strings), listes (→ arrays) et paires (→ objects).Ce que vous avez construit, du premier caractère au langage complet :
Le cours 6 explore la suite : compilateur calc.c, transpileur LISP→Python, macros, continuation passing style…
Fichiers concernés :
tabulator.c, evalenv.c, table.c, mapk.c, filterk.c, project.c, calc.c, transpile.c
Votre langage est complet : cellules mutables, objets par messages,
bibliothèque standard, fonctions natives, persistance.
printf("Hello World"). On finit avec un langage de programmation. Entre les deux, il y a eu du café, des segfaults, et des révélations. »