9 exercices pour construire un évaluateur d'expressions arithmétiques
Laurent Thiry — Programmation en C
« Chaque expert a commencé par écrire Hello World.
Puis il a oublié un point-virgule. Et ça a marché quand même… enfin, non. »
(+ 2 (* 3 4))Créez un fichier hello.c qui affiche "Hello World".
#include <stdio.h>, main(), printf./hello"Hello <votre_prénom>""Hello World" par défaut"Hello Alice, Bob, Charlie!""Hello Alice!"/* hello.c — solution complète */ #include <stdio.h> #include <string.h> int main(int argc, char **argv) { /* b) argument unique */ if (argc == 2) { printf("Hello %s!\n", argv[1]); return 0; } /* c) arguments multiples */ if (argc > 2) { printf("Hello"); for (int i = 1; i < argc; i++) { printf("%s%s", i == 1 ? " " : ", ", argv[i]); } printf("!\n"); return 0; } /* a) défaut */ printf("Hello World!\n"); return 0; }
gcc -Wall -Wextra -std=c99 -o hello hello.c. L'option -Wall active tous les warnings. Le C ne vous tient pas la main, mais -Wall vous offre un gilet de sauvetage.Calculez la factorielle d'un nombre entier.
for ou whilefactorielle sous forme récursive/* a) itératif */ long long factorielle_iter(int n) { long long res = 1; for (int i = 2; i <= n; i++) res *= i; return res; } /* b) récursif */ long long factorielle_rec(int n) { if (n <= 1) return 1; return n * factorielle_rec(n - 1); } /* c) Fibonacci */ void fibonacci(int n) { long long a = 0, b = 1; for (int i = 0; i < n; i++) { printf("%lld ", a); long long tmp = a + b; a = b; b = tmp; } printf("\n"); }
Extrayez le calcul dans des fonctions réutilisables.
factoriellemain pour plusieurs valeurslong long puissance(int a, int b) qui calcule ablong long combinaison(int n, int k) = n! / (k! × (n-k)!)/* a) factorielle */ long long factorielle(int n) { if (n < 0) return -1; long long res = 1; for (int i = 2; i <= n; i++) res *= i; return res; } /* b) exponentiation rapide */ long long puissance(int a, int b) { if (b == 0) return 1; long long moitie = puissance(a, b / 2); return (b % 2 == 0) ? moitie * moitie : moitie * moitie * a; } /* c) combinaison */ long long combinaison(int n, int k) { if (k < 0 || k > n) return 0; if (k > n - k) k = n - k; /* optimisation */ long long res = 1; for (int i = 1; i <= k; i++) { res = res * (n - k + i) / i; } return res; }
Découvrez sexpression.h — la bibliothèque qui permet de manipuler des S-expressions.
sexplore.c qui inclut "sexpression.h"(+ 2 (* 3 4))sexp_print — le résultat doit être (+ 2 (* 3 4))int compte_ndeuds(Sexp *e) qui compte récursivement les nœuds d'une S-expression(+ 2 (* 3 4)) : combien de nœuds ?#include "sexpression.h" #include <stdio.h> /* c) compteur récursif */ int compte_ndeuds(Sexp *e) { if (!e) return 0; if (e->type == SEXPR_CONS) return 1 + compte_ndeuds(e->car) + compte_ndeuds(e->cdr); return 1; /* atom */ } int main() { /* a) entier + liste */ Sexp *e = sexp_int(42); sexp_print(e); printf("\n"); Sexp *lst = sexp_cons(sexp_int(1), sexp_cons(sexp_int(2), sexp_cons(sexp_int(3), NULL))); sexp_print(lst); printf("\n"); /* b) arbre (+ 2 (* 3 4)) */ Sexp *arbre = sexp_cons(sexp_sym("+"), sexp_cons(sexp_int(2), sexp_cons(sexp_cons(sexp_sym("*"), sexp_cons(sexp_int(3), sexp_cons(sexp_int(4), NULL))), NULL))); sexp_print(arbre); printf("\n"); printf("Nœuds : %d\n", compte_ndeuds(arbre)); sexp_free(e); sexp_free(lst); sexp_free(arbre); return 0; }
Sexp peut être SEXPR_INT, SEXPR_SYM, SEXPR_CONS, SEXPR_FLOAT, SEXPR_CELL ou SEXPR_NATIVE. Le champ e->type permet de savoir à quoi on a affaire.Utilisez sexp_parse pour transformer une chaîne en S-expression.
parseur.c qui lit une expression depuis la ligne de commande"(+ 2 (* 3 4))", "(lambda (x) x)""(+ 2 (* 3 4)" (parenthèse manquante)sexp_parse retourne NULL"" (chaîne vide)expr.txt contenant (+ 10 20 30)#include <stdio.h> #include <stdlib.h> #include "sexpression.h" /* c) lire un fichier */ char *lit_fichier(const char *path) { FILE *f = fopen(path, "r"); if (!f) return NULL; fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f); char *buf = malloc(sz + 1); fread(buf, 1, sz, f); buf[sz] = 0; fclose(f); return buf; } int main(int argc, char **argv) { const char *input; char *buf = NULL; if (argc >= 3) { /* c) depuis fichier */ buf = lit_fichier(argv[2]); input = buf ? buf : ""; } else if (argc > 1) { /* a) depuis argv */ input = argv[1]; } else { input = "(+ 2 3)"; } Sexp *expr = sexp_parse(input); /* b) gestion d'erreur */ if (!expr) { fprintf(stderr, "Erreur : expression invalide\n"); free(buf); return 1; } printf("Parsé : "); sexp_print(expr); printf("\n"); sexp_free(expr); free(buf); return 0; }
Évaluez récursivement une expression arithmétique parsée : (+ 2 (* 3 4)) → 14
SEXPR_INT → retourner sa valeurSEXPR_CONS → c'est (op arg1 arg2 ...) : évaluer chaque arg, appliquer l'opérateur"Erreur : division par zéro"exit(1))% (modulo)^ (puissance) — utilisez votre fonction puissance(% 10 3) → 1, (^ 2 10) → 1024long long eval(Sexp *e) { if (e->type == SEXPR_INT) return e->entier; char *op = e->car->symbole; Sexp *args = e->cdr; long long val = eval(args->car); args = args->cdr; while (args) { long long v = eval(args->car); if (op[0] == '+') val += v; else if (op[0] == '-') val -= v; else if (op[0] == '*') val *= v; else if (op[0] == '/') { if (v == 0) { /* b) */ fprintf(stderr, "Division par zéro\n"); return 0; } val /= v; } else if (op[0] == '%') val %= v; /* c) modulo */ else if (op[0] == '^') val = puissance(val, v); /* c) puissance */ args = args->cdr; } return val; }
(+ 2 (* 3 4)) est représentée en interne comme cons(+, cons(2, cons(cons(*, cons(3, cons(4, NULL))), NULL))). Le car du CONS est le premier élément, le cdr est le reste.Enveloppez votre parseur + évaluateur dans une boucle interactive.
exit pour sortir(+ 1 2) → (* 3 4) → (- 10 5)history pour les afficher!n pour ré-exécuter la n-ième entrée de l'historique.lisptest.lisp avec (+ 1 2), (* 3 4)#include <stdio.h> #include <string.h> #include <stdlib.h> #include "sexpression.h" /* b) historique circulaire */ #define HIST_SIZE 10 char *history[HIST_SIZE]; int hist_idx = 0, hist_count = 0; void hist_add(const char *line) { free(history[hist_idx]); history[hist_idx] = strdup(line); hist_idx = (hist_idx + 1) % HIST_SIZE; if (hist_count < HIST_SIZE) hist_count++; } const char *hist_get(int n) { if (n < 0 || n >= hist_count) return NULL; int idx = (hist_idx - hist_count + n) % HIST_SIZE; return history[idx]; } int main(int argc, char **argv) { /* c) mode batch */ if (argc > 1) { FILE *f = fopen(argv[1], "r"); if (!f) { perror(argv[1]); return 1; } char line[1024]; while (fgets(line, sizeof(line), f)) { line[strcspn(line, "\n")] = 0; if (line[0] == 0 || line[0] == ';') continue; Sexp *e = sexp_parse(line); if (e) { printf("> %lld\n", eval(e)); sexp_free(e); } } fclose(f); return 0; } /* a) REPL interactif */ char ligne[1024]; while (1) { printf("λ > "); if (!fgets(ligne, sizeof(ligne), stdin)) break; ligne[strcspn(ligne, "\n")] = 0; if (strcmp(ligne, "exit") == 0) break; if (strcmp(ligne, "history") == 0) { for (int i = 0; i < hist_count; i++) printf("%d: %s\n", i, hist_get(i)); continue; } if (ligne[0] == '!') { int n = atoi(ligne + 1); const char *prev = hist_get(n); if (prev) { strcpy(ligne, prev); printf("→ %s\n", ligne); } else { printf("Pas d'entrée %d\n", n); continue; } } if (ligne[0] == 0) continue; hist_add(ligne); Sexp *expr = sexp_parse(ligne); if (!expr) { printf("Erreur de syntaxe\n"); continue; } long long r = eval(expr); printf("= %lld\n", r); sexp_free(expr); } for (int i = 0; i < HIST_SIZE; i++) free(history[i]); return 0; }
Défis supplémentaires pour les plus rapides. Solutions fournies pour vérification.
Affichez les n premières lignes du triangle de Pascal en utilisant votre fonction combinaison.
Exemple pour n=5 :
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1
Écrivez mini-wc.c qui compte les lignes, mots et caractères d'un fichier (comme wc sous Unix).
Utilisez fgetc et un automate à états pour compter les mots.
/* Triangle de Pascal */ void pascal(int n) { for (int i = 0; i < n; i++) { for (int s = 0; s < n - i - 1; s++) printf(" "); for (int j = 0; j <= i; j++) printf("%lld ", combinaison(i, j)); printf("\n"); } } /* mini-wc */ int main(int argc, char **argv) { FILE *f = argc > 1 ? fopen(argv[1], "r") : stdin; int lignes = 0, mots = 0, carac = 0, dans_mot = 0, c; while ((c = fgetc(f)) != EOF) { carac++; if (c == '\n') lignes++; if (c == ' ' || c == '\n' || c == '\t') { dans_mot = 0; } else if (!dans_mot) { mots++; dans_mot = 1; } } printf("%d %d %d %s\n", lignes, mots, carac, argc > 1 ? argv[1] : ""); return 0; }
let.c et calc.c peuvent vous servir d'inspiration.Écrivez un programme qui lit une chaîne et vérifie si les parenthèses sont correctement équilibrées.
Exemples :
"(+ 2 (* 3 4))" → OK"(+ 2 (* 3 4)" → Erreur (parenthèse ouvrante non fermée)")( 1 2 (" → Erreur (parenthèse fermante avant ouvrante)Indice
Ajoutez les opérateurs =, <, > à l'évaluateur arithmétique du TD1.
Ils doivent retourner 0 (faux) ou 1 (vrai).
Testez : (= (+ 2 3) 5) → 1, (> 10 (* 2 3)) → 1
# et des . alternés, sans utiliser de tableau.
En 2 heures, vous êtes passés de printf("Hello World")
à un évaluateur d'expressions arithmétiques S-expressions.