# AP2 · 5 — Choisir ce qui sera exécuté

## Notre question

Notre langage sait calculer, nommer et appeler. Nous voulons maintenant qu'un
programme décide quel calcul exécuter. Prédisez les résultats et les erreurs :

```text
case True { True -> 7.0 False -> 1.0 / 0.0 }
case False { True -> 1.0 / 0.0 False -> 8.0 }
case 1.0 { True -> 7.0 False -> 8.0 }
```

Le premier doit donner `7.0`, le deuxième `8.0` : la division par zéro se
trouve chaque fois dans la branche qui n'est pas choisie, et elle ne doit pas
être calculée. Le troisième est une erreur, car une condition doit être
booléenne ; aucun nombre ne vaut `True`. Nous gardons une seule forme de
contrôle, `case`, avec exactement les branches `True`, puis `False`. Ce n'est
pas encore le pattern matching général d'Elixir ou de Gleam.

> **Repères.** [Slides 46 à 53](https://ap-2026.noce.fr/slides/AP2.html#46) : booléens, comparaisons, évaluation stricte ou paresseuse, `case` du langage. Elixir : `case`, `assert_in_delta` pour les flottants, expliqués au point d'emploi. Poly, chap. 2 « Le pattern matching » et chap. 5, § 5.8 « Décider : booléens et `case` ».

> 💡 Cette étape permet d'approfondir notre interpréteur.
> Selon notre rythme, nous pourrons la travailler ensemble ou la
> garder pour la suite. Le travail à rendre sera annoncé en cours ;
> les prolongements non demandés restent des bonus facultatifs.
> **Sauvegardez-le dès maintenant sous `AP2/ap2-05-controle.livemd`.**

## Reprendre votre travail

Ouvrez votre notebook `ap2-04-fermetures.livemd` à côté de celui-ci. Une nouvelle
session Livebook ne connaît aucun de vos modules précédents. Copiez vos
cellules **dans les trois cellules indiquées**, puis évaluez-les de haut
en bas. Conservez vos versions dans ce notebook : il ne chargera aucun
fichier ni aucun code sur Internet.

Après la mise en commun, repartez de votre version discutée en classe,
même si elle ne couvre pas encore toutes les extensions. Le cœur de cette
étape utilise des AST écrits à la main et peut avancer sans parser terminé.
Si votre évaluateur bloque encore, construisez d'abord une version limitée
aux cas utilisés ci-dessous ; signalez cette limite dans votre explication.

```elixir
# Copiez ici votre module ArithmeticOps (les opérations sur les valeurs).
```

```elixir
# Copiez ici Bindings puis Interpreter ; modifiez Interpreter dans cette cellule.
```

```elixir
# Pour le parcours depuis le texte : copiez vos modules de lecture des littéraux,
# ArithmeticInput et ArithmeticParser. Cette cellule peut attendre le prolongement.
```

Les vérifications de ce notebook utilisent `assert`, fourni par Elixir via
ExUnit : une condition fausse arrête la cellule et affiche la valeur obtenue
à côté de celle attendue. Évaluez la cellule d'import une fois, puis
réévaluez votre code avant ses tests après chaque modification.

```elixir
import ExUnit.Assertions
```

Cette première vérification confirme que votre évaluateur recopié répond encore sur un arbre de l'étape 2 :

```elixir
assert Interpreter.eval({:binary, :plus, {:number, 2.0}, {:number, 3.0}}) == {:ok, 5.0}
```

Copiez aussi votre module `Bindings` avec `Interpreter`, comme à l'étape 4.

## Ajouter les valeurs puis écrire le choix

Dans `Interpreter`, le cas `eval({:bool, value}, _env)` rend `{:ok, value}`, comme
le cas du nombre : `True` et `False` du langage cible deviennent les booléens
Elixir `true` et `false`. Dans `ArithmeticOps`, ajoutez `:lt` et `:eq` par
analogie avec `:plus` : les comparaisons Elixir `left < right` et `left == right`
rendent un booléen, donc deux nombres produisent désormais un booléen. Nous
réservons `==` aux nombres ; l'égalité sur les fonctions ou les booléens reste
hors du langage, et le garde `is_float/1` de `apply/3` s'en charge déjà.

Écrivez ensuite l'algorithme complet pour `{:case, condition, yes, no}`.
Évaluez d'abord la condition dans l'environnement courant, puis choisissez
**un seul** sous-arbre à évaluer. Propagez les erreurs et rendez
`{:error, :expected_boolean}` si la condition n'est pas booléenne. Le `case`
d'Elixir convient bien pour écrire ce choix : une branche par forme du
résultat de la condition. Avant de coder, tracez les nœuds effectivement
visités dans le premier exemple.

```elixir
assert ArithmeticOps.apply(:lt, 2.0, 3.0) == {:ok, true}
assert ArithmeticOps.apply(:eq, 2.0, 3.0) == {:ok, false}
assert ArithmeticOps.apply(:eq, true, true) == {:error, :type_mismatch}
division = {:binary, :divide, {:number, 1.0}, {:number, 0.0}}
assert Interpreter.eval({:case, {:bool, true}, {:number, 7.0}, division}) == {:ok, 7.0}
assert Interpreter.eval({:case, {:bool, false}, division, {:number, 8.0}}) == {:ok, 8.0}
assert Interpreter.eval({:case, {:number, 1.0}, {:number, 7.0}, {:number, 8.0}}) ==
  {:error, :expected_boolean}
assert Interpreter.eval({:case, {:name, "missing"}, {:number, 7.0}, {:number, 8.0}}) ==
  {:error, {:undefined_name, "missing"}}
assert Interpreter.eval({:case, {:bool, true}, division, {:number, 8.0}}) ==
  {:error, :division_by_zero}
```

## Peut-on remplacer `case` par une fonction ordinaire ?

Imaginez la fonction ci-dessous. Son résultat pourrait-il être `7.0` ?
À quel moment ses trois arguments sont-ils évalués ? Faites d'abord une prédiction,
puis testez la version AST. Notre appel de fonction est *strict*, c-à-d. que les
valeurs des arguments sont calculées avant l'entrée dans le corps, comme en
Elixir et en Python.

```text
let choose = fn(condition, yes, no) {
  case condition { True -> yes False -> no }
}
choose(True, 7.0, 1.0 / 0.0)
```

```elixir
choose = {:fn, ["condition", "yes", "no"],
  {:case, {:name, "condition"}, {:name, "yes"}, {:name, "no"}}}
assert Interpreter.eval({:call, choose,
  [{:bool, true}, {:number, 7.0}, division]}) == {:error, :division_by_zero}
```

## Raccorder le programme au texte

Reprenez le prolongement syntaxique de l'étape 3. Pour arriver au programme final,
voici les ajouts à écrire ; un seul nouveau lecteur de primaire est nécessaire.
Si vous avez terminé le cœur, vous pouvez avancer sur cette intégration pendant
que nous reprenons les traces d'exécution.

1. Lisez les mots entiers, majuscules comprises, avec
   `Regex.run(~r/^[A-Za-z_][A-Za-z0-9_]*/, source)`, avant de reconnaître `True`
   et `False` en `{:bool, true}` et `{:bool, false}`. Ainsi `Truex` est refusé,
   et non découpé en `True` suivi de `x`. Les autres noms gardent la règle
   minuscule `[a-z][a-z0-9_]*` ; un mot comme `Price` est une erreur du lexer.
   Le mot `case` devient `:case`.
2. Ajoutez les tokens `:lt`, `:eq` et `:arrow` pour `<`, `==` et `->`.
   Reconnaissez `==` avant `=` et `->` avant `-` : les clauses d'un préfixe plus
   long passent avant celles du préfixe court, sinon elles ne seraient jamais
   essayées.
3. Placez les comparaisons au-dessus de la somme :
   `comparison := sum ("<" sum)*`, puis
   `expression := comparison ("==" comparison)*`.
   Réutilisez l'idée de lecture avec accumulation déjà utilisée pour `+`.
4. Ajoutez un primaire booléen, puis
   `case expression { True -> expression False -> expression }`.
   Consommez chaque mot et délimiteur attendu. Renvoyez
   `{:case, condition, yes, no}`, sans évaluer aucune branche pendant le parsing.

Les corps des branches sont des expressions ; utilisez `{ sequence }` si vous
voulez y écrire des `let`. L'arbre produit conserve exactement les mêmes formes
que nos exemples manuels et servira aussi à la compilation d'AP3 et au typeur du mini-projet.

> 💡 `assert_in_delta valeur, 32.4, 1.0e-9` vérifie que deux flottants sont
> égaux à `1.0e-9` près. Les nombres de notre langage sont des flottants
> binaires : ici `36.0 * 0.9` tombe juste sur `32.4`, mais `0.1 + 0.2` donne
> `0.30000000000000004`, et une tolérance évite un test fragile dès que le
> calcul change. Le `1.0e-9` est une notation d'Elixir ; notre langage, lui,
> refuse les exposants. La chaîne `"""` sur plusieurs lignes est celle de
> l'étape 4.

```elixir
# À lancer lorsque votre parcours depuis le texte est raccordé.
source = """
let rate = 0.9
let discounted = fn(total) { total * rate }
let price = 12.0
let quantity = 3.0
let total = price * quantity
case total < 30.0 {
  True -> total
  False -> discounted(total)
}
"""
{:ok, tokens} = ArithmeticInput.lex(source)
{:ok, tree} = ArithmeticParser.parse(tokens)
{:ok, result} = Interpreter.eval(tree)
assert_in_delta result, 32.4, 1.0e-9
```

### Pour aller plus loin

Écrivez votre propre petit programme : un tarif, un seuil, une fonction fabriquée
par une autre fonction, etc. Faites-le fonctionner sur un AST, puis depuis le texte
si votre parser est prêt. Expliquez quelle règle lui permet de fonctionner.

Autre piste : faites prendre à `choose` deux fonctions sans paramètre, appelées
seulement dans la branche choisie. Peut-on ainsi différer les calculs sans ajouter
une nouvelle forme de contrôle ? Testez votre proposition.

## Prolongements facultatifs

Trois prolongements, à prendre dans l'ordre. Le premier assouplit notre `case`
en quelques lignes du parser ; le deuxième ajoute une nouvelle forme au
langage ; le troisième montre qu'elle n'était qu'un raccourci, et la traduit
en formes du socle.

### Les branches `True` et `False` dans un ordre quelconque

Notre grammaire impose `True` puis `False`. Rien n'empêche pourtant de lire
aussi l'ordre inverse :

```text
case total < 30.0 {
  False -> discounted(total)
  True -> total
}
```

Les deux branches restent obligatoires, chacune une seule fois : `True` deux
fois, ou une seule branche, est toujours une erreur de syntaxe. La grammaire
gagne une alternative :

```text
primary := ...
         | "case" expression "{" "True" "->" expression
                                 "False" "->" expression "}"
         | "case" expression "{" "False" "->" expression
                                 "True" "->" expression "}"
```

Le point important est ailleurs : quel que soit l'ordre du texte, le parser
rend le même arbre `{:case, condition, yes, no}`, où `yes` est toujours la
branche `True`. Ainsi l'évaluateur n'a rien à changer, pas plus que le
compilateur d'AP3 ou le typeur. Modifiez seulement votre lecteur du `case` :
lisez le premier booléen, puis exigez l'autre. Avant de lancer les tests,
prédisez ce que rend `case False { False -> 8.0 True -> 1.0 / 0.0 }`.

```elixir
# À lancer si votre parser accepte les deux ordres.
lire = fn source ->
  {:ok, tokens} = ArithmeticInput.lex(source)
  ArithmeticParser.parse(tokens)
end

assert lire.("case c { True -> 2.0 False -> 1.0 }") ==
  {:ok, {:case, {:name, "c"}, {:number, 2.0}, {:number, 1.0}}}
assert lire.("case c { False -> 1.0 True -> 2.0 }") == lire.("case c { True -> 2.0 False -> 1.0 }")
{:ok, tree} = lire.("case False { False -> 8.0 True -> 1.0 / 0.0 }")
assert Interpreter.eval(tree) == {:ok, 8.0}
for source <- ["case c { True -> 1.0 True -> 2.0 }", "case c { False -> 1.0 False -> 2.0 }",
               "case c { False -> 1.0 }"] do
  assert {:error, _} = lire.(source)
end
```

### Un `case` sur les nombres

Notre `case` ne choisit qu'entre `True` et `False`. Ce prolongement lui apprend
à choisir selon la valeur d'un nombre, avec un cas par défaut écrit `_` :

```text
let places = 2.0
case places {
  1.0 -> 12.0
  2.0 -> 20.0
  _ -> places * 9.0
}
```

Une place coûte 12.0, deux places 20.0, et au-delà chaque place coûte 9.0 :
ce programme rend `20.0`. Voici les règles de cette nouvelle forme.

- Chaque branche commence par une *étiquette*, un nombre écrit tel quel,
  suivie de `->` et d'une expression. Une étiquette est une valeur, pas un
  motif : ni nom, ni calcul, ni garde.
- La dernière branche, `_ -> expression`, est obligatoire. C'est le cas par
  défaut, choisi quand aucune étiquette n'est égale à la valeur. Grâce à elle,
  toute valeur trouve une branche, sans qu'il faille vérifier que les
  branches couvrent tous les cas. Rust et Gleam font cette vérification
  pour leurs motifs avant l'exécution ; Elixir, lui, lève `CaseClauseError`
  quand aucune branche ne convient.
- Le *sujet*, ici `places`, est évalué une seule fois et doit valoir un
  nombre, sinon le résultat est `{:error, :expected_number}`. Les étiquettes
  sont comparées dans l'ordre avec `==`, et la première égale l'emporte. Comme
  pour le `case` booléen, seule la branche choisie est évaluée.

Vous reconnaissez peut-être le `match` de Python réduit aux littéraux
(`case 1.0:` … `case _:`), ou le `switch` de JavaScript avec son `default`.
La grammaire gagne une alternative :

```text
primary := ...
         | "case" expression "{" "True" "->" expression
                                 "False" "->" expression "}"
         | "case" expression "{" (number "->" expression)*
                                 "_" "->" expression "}"
```

Après l'accolade ouvrante, le token suivant suffit à choisir entre les deux
formes : `True`, ou `False` si vous avez fait le prolongement précédent,
annonce le `case` booléen ; un nombre ou `_` annonce le nouveau.

Représentez ce choix par un nouveau nœud, plutôt que d'élargir `{:case, …}`,
que le compilateur d'AP3 et le typeur du mini-projet attendent tel quel :
`{:switch, sujet, [{étiquette, branche}, …], défaut}`, où chaque étiquette est
un flottant Elixir. Commencez par l'évaluateur, sur des arbres écrits à la main.
Une fonction auxiliaire récursive parcourt la liste des branches, comme au
chapitre 4 du poly, et rend l'arbre à évaluer ; le garde `when label == value`
compare une étiquette à la valeur du sujet.

```elixir
# À lancer si vous tentez ce prolongement : des arbres écrits à la main.
division = {:binary, :divide, {:number, 1.0}, {:number, 0.0}}
tarifs = [{1.0, {:number, 12.0}}, {2.0, {:number, 20.0}}]
assert Interpreter.eval({:switch, {:number, 2.0}, tarifs, division}) == {:ok, 20.0}
assert Interpreter.eval({:switch, {:number, 5.0}, tarifs, {:number, 45.0}}) == {:ok, 45.0}
assert Interpreter.eval({:switch, {:number, 3.0}, [], {:number, 0.0}}) == {:ok, 0.0}
assert Interpreter.eval({:switch, {:number, 1.0}, [{1.0, {:number, 7.0}}, {2.0, division}],
  division}) == {:ok, 7.0}
assert Interpreter.eval({:switch, {:bool, true}, tarifs, {:number, 0.0}}) ==
  {:error, :expected_number}
assert Interpreter.eval({:switch, {:name, "missing"}, tarifs, {:number, 0.0}}) ==
  {:error, {:undefined_name, "missing"}}
```

Pour lire cette forme depuis le texte, deux ajouts suffisent.

1. Dans le lexer, le mot `_` est déjà lu par la regex des mots, puis refusé
   comme nom. Reconnaissez-le seul comme un mot-clé, qui donne le token
   `:wildcard`. Un nom comme `_x` reste refusé.
2. Dans le parser, après `case expression {`, regardez le token suivant.
   Devant un booléen, lisez le `case` booléen comme avant. Sinon, lisez des
   branches `nombre -> expression` jusqu'au `_`, puis `-> expression }`.
   Une branche `_` absente, ou placée avant une étiquette, est une erreur de
   syntaxe.

```elixir
# À lancer si votre lexer et votre parser lisent aussi ce prolongement.
run = fn source ->
  with {:ok, tokens} <- ArithmeticInput.lex(source),
       {:ok, tree} <- ArithmeticParser.parse(tokens) do
    Interpreter.eval(tree)
  end
end

for {places, attendu} <- [{1.0, 12.0}, {2.0, 20.0}, {5.0, 45.0}] do
  source = "let places = #{places} case places { 1.0 -> 12.0 2.0 -> 20.0 _ -> places * 9.0 }"
  assert run.(source) == {:ok, attendu}
end
assert run.("case 3.0 { _ -> 0.0 }") == {:ok, 0.0}
assert run.("case True { 1.0 -> 0.0 _ -> 1.0 }") == {:error, :expected_number}
assert run.("case True { True -> 1.0 False -> 0.0 }") == {:ok, 1.0}
assert ArithmeticInput.lex("_") == {:ok, [:wildcard]}
assert {:error, _} = ArithmeticInput.lex("_x")
for source <- ["case 1.0 { 1.0 -> 0.0 }", "case 1.0 { _ -> 0.0 1.0 -> 2.0 }",
               "case 1.0 { x -> 0.0 _ -> 1.0 }"] do
  {:ok, tokens} = ArithmeticInput.lex(source)
  assert {:error, _} = ArithmeticParser.parse(tokens)
end
```

Pour finir, trois questions, à traiter dans votre cellule de réponses.

- Prédisez le résultat de `case 0.1 + 0.2 { 0.3 -> 1.0 _ -> 0.0 }`, puis
  vérifiez. Pour quels nombres ce `case` est-il sûr ?
- Que rend votre version pour `case 1.0 { 1.0 -> 7.0 1.0 -> 8.0 _ -> 0.0 }` ?
  Faudrait-il refuser ce programme ?
- Le lexer découpe `-1.0` en deux tokens, `:minus` puis `{:number, 1.0}` :
  c'est le parser qui en fait un nombre négatif, avec le moins unaire. Une
  étiquette ne peut donc pas être négative. Imaginons qu'on l'autorise, et
  lisons `case x { 1.0 -> y -1.0 -> z _ -> 0.0 }`. Après `1.0 ->`, votre
  parser lit une expression et la prolonge tant qu'il voit un opérateur ; or
  `-` en est un. La branche de `1.0` est-elle `y`, ou `y - 1.0` ? Que se
  passe-t-il au `->` suivant ? En Elixir, un retour à la ligne sépare deux
  branches ; quel séparateur pourrait-on ajouter à notre langage ?

### Du sucre syntaxique : traduire plutôt qu'interpréter

Notre `case` sur les nombres n'ajoute rien que le langage ne sache déjà
exprimer. Le programme des places s'écrit avec les seules formes du socle :
un `let`, des comparaisons `==` et des `case` booléens emboîtés.

```text
let _sujet = places
case _sujet == 1.0 {
  True -> 12.0
  False -> case _sujet == 2.0 {
    True -> 20.0
    False -> places * 9.0
  }
}
```

Chaque étiquette devient une comparaison ; sa branche devient la branche
`True` de cette comparaison, et la suite de la liste sa branche `False`,
jusqu'au défaut. Une forme qui se traduit ainsi en formes plus simples
s'appelle du *sucre syntaxique* : elle rend le texte plus agréable à écrire,
sans rien ajouter à ce que le langage sait calculer.

Elixir en est plein. `if` n'y est pas une forme de base, mais une *macro* que
le compilateur réécrit en `case` avant l'exécution. La cellule suivante
affiche cette réécriture :

```elixir
quote(do: if(c, do: 1, else: 2)) |> Macro.expand_once(__ENV__) |> Macro.to_string() |> IO.puts()
```

La réécriture fixe aussi le sens de `if` : seuls `false` et `nil` mènent à la
branche `else`, donc `if 0` exécute la branche `do`.

Nous allons procéder de même. Plutôt que d'apprendre `:switch` à
l'évaluateur, au compilateur d'AP3 et au typeur, une *passe* de traduction
remplace chaque nœud `:switch` par des formes du socle. Le parser continue de
produire `:switch` ; la passe se place entre lui et les moteurs :

```text
texte → lexer → parser → traduction → évaluateur, compilateur ou typeur
```

Une seule fonction connaît le sucre, et tous les moteurs qui la suivent en
profitent sans changer une ligne. Les vrais compilateurs sont faits ainsi :
une suite de passes, chacune simplifiant l'arbre pour la suivante.

#### 1. Traduire un `case` sur les nombres

Complétez `chain/2` dans le module ci-dessous. C'est une récursion sur la
liste des branches, comme au chapitre 4 du poly. La liste vide donne le
défaut. Une liste `[{label, branch} | rest]` donne un `case` booléen : sa
condition compare `{:name, "_sujet"}` à l'étiquette avec `:eq`, sa branche
`True` est `branch`, et sa branche `False` est la traduction de `rest`.
`expand/3`, fournie, place la chaîne sous le `let` qui calcule le sujet.
Laissez `desugar/1` pour l'étape 2.

```elixir
defmodule Sugar do
  # case sujet { étiquette -> branche … _ -> défaut }, traduit en formes du socle.
  def expand(subject, clauses, default) do
    {:let, "_sujet", subject, chain(clauses, default)}
  end

  # À COMPLÉTER (1) : une chaîne de case booléens, un maillon par étiquette.
  defp chain(_clauses, default), do: default

  # À COMPLÉTER (2) : parcourir tout l'arbre et traduire chaque :switch.
  def desugar(tree), do: tree
end
```

```elixir
# À lancer si vous tentez la traduction : chain/2, sur des arbres écrits à la main.
x = {:name, "x"}
tarifs = [{1.0, {:number, 12.0}}, {2.0, {:number, 20.0}}]
assert Sugar.expand(x, [], {:number, 0.0}) == {:let, "_sujet", x, {:number, 0.0}}
assert Sugar.expand(x, tarifs, {:number, 0.0}) ==
  {:let, "_sujet", x,
   {:case, {:binary, :eq, {:name, "_sujet"}, {:number, 1.0}}, {:number, 12.0},
    {:case, {:binary, :eq, {:name, "_sujet"}, {:number, 2.0}}, {:number, 20.0},
     {:number, 0.0}}}}
for {places, attendu} <- [{1.0, 12.0}, {2.0, 20.0}, {5.0, 45.0}] do
  defaut = {:binary, :multiply, {:number, places}, {:number, 9.0}}
  assert Interpreter.eval(Sugar.expand({:number, places}, tarifs, defaut)) == {:ok, attendu}
end
division = {:binary, :divide, {:number, 1.0}, {:number, 0.0}}
assert Interpreter.eval(Sugar.expand({:number, 1.0}, [{1.0, {:number, 7.0}}], division)) ==
  {:ok, 7.0}
```

Deux choix de `expand` méritent une explication.

- **Pourquoi un `let` ?** Sans lui, il faudrait recopier l'expression du sujet
  dans chaque comparaison. Notre langage est pur : la recalculer donnerait la
  même valeur, mais une fois par étiquette essayée. Dans un langage qui
  affiche ou qui lit, ce serait faux, car l'effet se produirait plusieurs fois.
- **Pourquoi le nom `_sujet` ?** Le nom introduit par la traduction ne doit
  masquer aucun nom du programme. Imaginez que `expand` ait choisi `s`, et
  prédisez ce que rendrait la traduction de
  `let s = 5.0 case 1.0 { 1.0 -> s _ -> 0.0 }`. Pour le vérifier, remplacez un
  instant `"_sujet"` par `"s"` dans `Sugar`, réévaluez le module et la cellule
  suivante, puis rétablissez-le. Le lexer refuse tout nom qui commence par
  `_` : aucun programme ne peut écrire `_sujet`, et la traduction ne capture
  donc aucun nom. Une traduction qui respecte ainsi les noms du programme est
  dite *hygiénique* ; les macros d'Elixir le sont aussi.

```elixir
# Le nom choisi par la traduction ne capture aucun nom du programme.
programme = {:let, "s", {:number, 5.0},
  Sugar.expand({:number, 1.0}, [{1.0, {:name, "s"}}], {:number, 0.0})}
assert Interpreter.eval(programme) == {:ok, 5.0}
```

#### 2. Une passe sur tout l'arbre

Un `:switch` peut se cacher n'importe où : dans le corps d'une fonction, dans
un argument, dans une branche d'un autre `case`, ou dans un autre `:switch`.
Écrivez `desugar/1`, qui parcourt tout l'arbre et rend un arbre sans aucun
`:switch`. Sa structure est celle de votre évaluateur : une clause par forme
de nœud.

- Les feuilles `{:number, _}`, `{:bool, _}` et `{:name, _}` restent telles
  quelles.
- `:neg`, `:binary`, `:let`, `:fn`, `:call` et `:case` gardent leur forme ;
  seuls leurs enfants sont traduits. N'oubliez ni le corps d'une fonction, ni
  les arguments d'un appel, qui forment une liste d'arbres.
- `{:switch, sujet, branches, défaut}` devient `expand(…)`, après traduction du
  sujet, de chaque branche et du défaut, qui peuvent eux-mêmes contenir un
  `:switch`.

Remplacez la clause fournie, qui rend tout arbre inchangé, par une clause par
forme : si une forme vous échappe, Elixir lèvera `FunctionClauseError` au lieu
de laisser passer un `:switch` en silence.

```elixir
# À lancer quand desugar/1 est écrite.
switch = {:switch, {:name, "n"}, [{1.0, {:number, 12.0}}], {:number, 0.0}}
traduit = Sugar.expand({:name, "n"}, [{1.0, {:number, 12.0}}], {:number, 0.0})
assert Sugar.desugar(switch) == traduit
assert Sugar.desugar({:fn, ["n"], switch}) == {:fn, ["n"], traduit}
assert Sugar.desugar({:call, {:name, "f"}, [switch, {:number, 1.0}]}) ==
  {:call, {:name, "f"}, [traduit, {:number, 1.0}]}
assert Sugar.desugar({:let, "n", {:number, 1.0}, {:binary, :plus, switch, {:neg, switch}}}) ==
  {:let, "n", {:number, 1.0}, {:binary, :plus, traduit, {:neg, traduit}}}
assert Sugar.desugar({:case, {:bool, true}, switch, {:number, 0.0}}) ==
  {:case, {:bool, true}, traduit, {:number, 0.0}}
# un switch dans un switch : sujet, branches et défaut sont traduits aussi
assert Sugar.desugar({:switch, switch, [{12.0, switch}], switch}) ==
  Sugar.expand(traduit, [{12.0, traduit}], traduit)
```

#### 3. Comparer les deux chemins

Votre évaluateur sait déjà exécuter `:switch`. Pour chaque programme, la
cellule suivante compare son résultat direct et celui de sa traduction : ils
doivent coïncider. Tester ainsi deux implémentations l'une contre l'autre est
une méthode courante pour valider un compilateur.

Avant de la lancer, prédisez les deux résultats, direct et traduit, de
`case True { 1.0 -> 0.0 _ -> 1.0 }`, puis de `case True { _ -> 1.0 }`.

```elixir
# À lancer si votre parser lit le case sur les nombres et si desugar/1 est écrite.
deux_chemins = fn source ->
  {:ok, tokens} = ArithmeticInput.lex(source)
  {:ok, tree} = ArithmeticParser.parse(tokens)
  {Interpreter.eval(tree), Interpreter.eval(Sugar.desugar(tree))}
end

for source <- [
  "let places = 2.0 case places { 1.0 -> 12.0 2.0 -> 20.0 _ -> places * 9.0 }",
  "let places = 5.0 case places { 1.0 -> 12.0 2.0 -> 20.0 _ -> places * 9.0 }",
  "let f = fn(n) { case n { 0.0 -> 1.0 _ -> n * 2.0 } } f(0.0) + f(3.0)",
  "case 1.0 { 1.0 -> 7.0 _ -> 1.0 / 0.0 }",
  "case case 2.0 { 2.0 -> 1.0 _ -> 0.0 } { 1.0 -> 10.0 _ -> 20.0 }"
] do
  {direct, traduit} = deux_chemins.(source)
  assert direct == traduit
end
assert deux_chemins.("case True { 1.0 -> 0.0 _ -> 1.0 }") ==
  {{:error, :expected_number}, {:error, :type_mismatch}}
assert deux_chemins.("case True { _ -> 1.0 }") == {{:error, :expected_number}, {:ok, 1.0}}
```

Les deux chemins divergent sur les erreurs : la traduction ne connaît plus de
sujet, seulement des comparaisons. Le sucre fixe donc le sens de la forme,
comme la réécriture de `if` en Elixir. Quel comportement préférez-vous ? Où
faudrait-il intervenir pour que la traduction rende, elle aussi,
`{:error, :expected_number}` ? Notez vos réponses.

Voici enfin ce que rapporte la traduction : le compilateur d'AP3 et le typeur
du mini-projet ne connaissent pas `:switch`, mais acceptent sa traduction sans
une ligne de plus, puisqu'elle n'emploie que des formes du socle. Le typeur
refuse même un sujet booléen avant toute exécution, car `==` exige deux
nombres. Pour le constater en AP3, recopiez `Sugar` dans le carnet et appelez
`Sugar.desugar` juste avant le compilateur.

## Avant de reprendre les slides

Montrez pourquoi la fonction stricte `choose` ne remplace pas directement `case`.
Dans le programme final, repérez un rôle du parser, un rôle de l'environnement
et un rôle de la fermeture. Ajoutez votre programme personnel et les limites que
vous lui connaissez. Enfin, choisissez une règle du langage que vous aimeriez
changer et expliquez dans quelle brique intervenir. Retenez que `case` a sa
propre règle d'évaluation, qui ne visite que la condition et la branche choisie :
c'est ce qu'une fonction ordinaire, dans un langage strict, ne peut pas faire.

Écrivez vos réponses dans une cellule Markdown de ce notebook, avec votre
contre-exemple et ce qu'il vous a appris. Sauvegardez ce notebook dans `AP2/`.
Pour le rendu unique du cycle AP1–AP3, je préciserai en cours les étapes
à terminer et le résultat minimum attendu. Les numéros indiquent l'ordre
de progression, pas une liste de devoirs. Les prolongements non demandés
restent facultatifs et peuvent apporter un bonus.
L'échéance commune est le 6 décembre 2026 à 23 h 59 (Paris) ; il n'y a pas
de devoir intermédiaire. AP3 s'ouvrira par un quiz de dix minutes sur
cette séance : relisez le chapitre 5 du poly, « Construire un langage ».

Si un bout de syntaxe Elixir vous manque en route, les chapitres 1 à 4 du
[poly](https://ap-2026.noce.fr/poly/poly-elixir.pdf) se consultent au besoin ; ils ne sont pas un prérequis de cette
étape.

Pour toute question, vous pouvez me contacter par email :
[aurelien.noce@minesparis.psl.eu](mailto:aurelien.noce@minesparis.psl.eu).
