Des listes au chatbot en 5 étapes
Laurent Thiry — Programmation en C
« Les templates, c'est comme les sandwichs : vous préparez le pain et la garniture. »
Chaque étape ajoute une couche sans changer le moteur fondamental.
typedef struct Sexp { int type; // SEXPR_INT, SEXPR_SYM, SEXPR_CONS int entier; char *symbole; struct Sexp *car; struct Sexp *cdr; } Sexp;
sexp_int(42)sexp_sym("x")sexp_cons(a, b)(1 2 3) = cons(1, cons(2, cons(3, NULL)))
static Sexp *parse_depuis(Parseur *p) { if (strcmp(p->token, "(") == 0) { prochain_token(p); Sexp *tete = NULL, *cur = NULL; while (strcmp(p->token, ")") != 0) { Sexp *item = parse_depuis(p); Sexp *cell = sexp_cons(item, NULL); if (!tete) { tete = cell; cur = cell; } else { cur->cdr = cell; cur = cell; } } prochain_token(p); return tete; } return est_nombre(tok) ? sexp_int(atoi(tok)) : sexp_sym(tok); }
sexp_parse("(+ 2 (* 3 4))") → arbre Sexp
Sexp *zip(Sexp *a, Sexp *b) { if (!a || !b) return NULL; Sexp *ca = a->car, *cb = b->car; Sexp *ac = (ca->type == SEXPR_INT) ? sexp_int(ca->entier) : sexp_sym(ca->symbole); Sexp *bc = (cb->type == SEXPR_INT) ? sexp_int(cb->entier) : sexp_sym(cb->symbole); return sexp_cons( sexp_cons(ac, sexp_cons(bc, NULL)), zip(a->cdr, b->cdr)); }
zip((x y z), (1 2 3)) → ((x 1) (y 2) (z 3))
Sexp *cherche(Sexp *key, Sexp *alist) { while (alist && alist->type == SEXPR_CONS) { Sexp *e = alist->car; if (e->car->type == SEXPR_SYM && key->type == SEXPR_SYM && strcmp(e->car->symbole, key->symbole) == 0) return e->cdr->car; alist = alist->cdr; } return NULL; }
cherche(x, ((x 1)(y 2))) → 1evaluer((+ x y), env) → évalue récursivementMoteur complet en ~50 lignes : zip + cherche + evaluer
int main(int argc, char **argv) { int k = atoi(argv[1]); Sexp *liste = sexp_parse("(1 2 3 4 5 6 7 8 9 10)"); Sexp *res = NULL, *last = NULL; Sexp *cur = liste; while (cur) { Sexp *nv = sexp_int(cur->car->entier * k); Sexp *cell = sexp_cons(nv, NULL); if (!res) { res = cell; last = cell; } else { last->cdr = cell; last = cell; } cur = cur->cdr; } sexp_print(res); sexp_free(res); return 0; }
./mapk 3 → (3 6 9 12 15 18 21 24 27 30)
// alist.c : zip + cherche Sexp *cles = sexp_parse("(x y z)"); Sexp *vleurs = sexp_parse("(10 20 30)"); Sexp *env = zip(cles, vleurs); // env = ((x 10) (y 20) (z 30)) // Chercher la valeur de y Sexp *val = cherche(sexp_sym("y"), env); // val->entier == 20
C'est notre environnement. Toute la suite repose là-dessus.
// evalenv.c Sexp *env = zip(sexp_parse("(x y)"), sexp_parse("(10 20)")); Sexp *expr = sexp_parse(argv[1]); Sexp *res = evaluer(expr, env); sexp_print(res); // (+ x y) → 30
Usage : ./evalenv '(+ x y)' → 30
Une table = liste de listes :
Sexp *filter_table(Sexp *table, Sexp *col, Sexp *op, Sexp *val) { Sexp *headers = table->car; Sexp *result = NULL, *last = NULL; for (Sexp *r = table->cdr; r; r = r->cdr) { Sexp *env = zip(headers, r->car); // Construit l'expression (op col val) Sexp *expr = sexp_parse("..."); Sexp *v = evaluer(expr, env); if (v->entier != 0) { /* garder la ligne */ } sexp_free(env); } return result; }
filter_table(t, "(and (> x 1) (< y 5))")// project.c Sexp *exprs = sexp_parse("((+ x y) (- x y))"); for (chaque ligne) { Sexp *env = zip(headers, row); for (chaque expression dans exprs) { Sexp *val = evaluer(expr, env); // val = resultat calcule } }
Résultat : une table sans noms de colonnes.
((somme (+ x y)) (diff (- x y))) ? On aurait des colonnes nommées. C'est exactement ce qu'on va faire avec les templates.Template : motif avec des expressions à évaluer.
Pour chaque ligne de la table, on remplit le motif :
make template-run.
while (tmpl) { Sexp *nom = tmpl->car; // x tmpl = tmpl->cdr; // saute nom tmpl = tmpl->cdr; // saute 'vaut' Sexp *expr = tmpl->car; // (x) ou (+ x y) // (x) → x pour references aux variables if (expr->type == SEXPR_CONS && !expr->cdr && expr->car->type == SEXPR_SYM) expr = expr->car; // Copie defensive !! sexp_cons(sexp_copie(nom), sexp_cons(sexp_copie(expr), NULL)); // saute 'et' entre deux clauses if (tmpl->car->type == SEXPR_SYM && strcmp(..., "et") == 0) tmpl = tmpl->cdr; }
Résultat : ((x (x)) (xy (+ x y)))
Sexp *eval_template(Sexp *table, Sexp *tmpl) { Sexp *paires = paires_template(tmpl); Sexp *result = NULL; for (chaque ligne) { Sexp *env = zip(headers, row); Sexp *row_out = NULL; for (chaque paire (nom . expr)) { Sexp *val = evaluer(expr, env); // ajoute (nom vaut val) + 'et' } sexp_free(env); result = ...; } return result; }
si : (x vaut (x) si (> x 2)). La clause n'est produite que si la condition est vraie.Principe : un masque avec des (placeholders) extrait les valeurs.
Collecté sur toutes les lignes → table :
Identité : template ∘ untemplate = renommage de colonnes.
while (tmpl) { if (tmpl->car->type == SEXPR_CONS // liste && !tmpl->car->cdr // 1 element && tmpl->car->car->type == SEXPR_SYM) { // C'est un placeholder ! sexp_copie(tmpl->car->car); } tmpl = tmpl->cdr; }
Puis on parcourt masque et ligne en parallèle pour extraire.
_ comme symbole « joker » qui matche n'importe quelle valeur sans l'extraire. Utile pour (je veux _ s il vous plait).Ingrédients :
inverse_templateeval_template| Pattern | → | Réponse |
|---|---|---|
(bonjour je m appelle (n)) | → | (bonjour (n) !) |
(quel age as tu) | → | (je suis né en 2026) |
(au revoir) | → | (a bientôt (n) !) |
while (1) { printf("> "); fgets(buf, ...); Sexp *entree = sexp_parse(buf); for (chaque regle (pattern . reponse)) { Sexp *vars = inverse_template(entree, pattern); if (vars) { // match ! Sexp *answer = eval_template(vars, reponse); sexp_print(answer); break; } } }
15 lignes de boucle, le reste est dans les templates.
(_) → (je n ai pas compris).(vous avez dit (x) ? repondez : (x)) — répète l'entrée. Utile pour le small talk.fread + sexp_parse = 3 lignes.(defun double (x) (* x 2)). Stockez les fonctions dans un environnement global../filterk --not 5 → garde les éléments ≥ 5.(stats (min (+ x y)) (max (+ x y)))../sql 'SELECT (+ x y) AS sum FROM t WHERE (> x 2)'.
Traduisez en appels à filter_table + project_table.
(defun double (x) (* x 2)).
Stockez les fonctions dans un environnement global.
3 types de données. 6 fonctions. Des pages de possibilités.