# AP1 · 5 — Faire vivre une pile, puis une calculatrice

## Notre question

Quel état faut-il conserver pour calculer `"1 2 3 * +"` ? Une pile, que
chaque token transforme. Nous écrivons les transitions, puis la boucle
qui relie nos briques sans cacher les erreurs.

> **Repères.** Poly, chap. 4, section 4.5 « La calculatrice à pile d'AP1 ». Le cours se fait ici, dans le carnet.

> 💡 Durée indicative : 35 min, reprise collective comprise. Ce carnet
> est autonome : aucune cellule d'un autre carnet n'est nécessaire. Je le
> projette au tableau ; écrivez une prédiction avant d'exécuter, puis
> expliquez un résultat qui vous a surpris. **Sauvegardez-le dès maintenant
> sous `AP1/ap1-05-pile-calculatrice.livemd`.**

Les cellules `assert` sont des vérifications fournies : si une condition
est fausse, Livebook montre le résultat obtenu et celui attendu.
Évaluez cet import une fois, puis réévaluez votre code avant ses tests
après chaque modification.

```elixir
import ExUnit.Assertions
```

## La brique du carnet précédent

`Token.read/1` est la fonction que vous avez écrite dans le carnet 4.
Une nouvelle session Livebook ne la connaît pas : recopiez votre version
dans la cellule ci-dessous, ou évaluez celle que nous fournissons, qui
respecte le même contrat.

```elixir
defmodule Token do
  def read("+"), do: {:ok, :plus}
  def read("-"), do: {:ok, :minus}
  def read("*"), do: {:ok, :multiply}

  def read(source) do
    case Integer.parse(source) do
      {number, ""} -> {:ok, {:number, number}}
      _other -> {:error, {:invalid_token, source}}
    end
  end
end
```

```elixir
assert Token.read("12") == {:ok, {:number, 12}}
assert Token.read("12x") == {:error, {:invalid_token, "12x"}}
```

## Quel état faut-il conserver ?

Nous lisons les morceaux de gauche à droite. Un nombre est ajouté sur
une *pile*. Une opération retire les deux nombres du sommet, les combine
et empile le résultat. Nous représentons le sommet par la tête d'une
liste. Voici la trace de `"8 3 -"` ; complétez celle de `"1 2 3 * +"`
dans une cellule de texte avant de programmer.

| Morceau lu | Pile après lecture, sommet à gauche |
|---|---|
| aucun | `[]` |
| `8` | `[8]` |
| `3` | `[3, 8]` |
| `-` | `[5]` |

Le sommet contient l'opérande **droit**, reçu le dernier. Une addition
ne révèle pas une inversion ; une soustraction la révèle immédiatement.
Notre état est la pile entière, pas seulement le dernier résultat.
Le motif `[right, left | rest]` récupère les deux premières valeurs et
la liste qui reste ; il ne correspond ni à `[]` ni à une liste d'un
seul élément.

## Écrire une transition

`Stack.step(token, stack)` applique un token déjà reconnu. Elle renvoie
`{:ok, nouvelle_pile}` ou `{:error, :not_enough_operands}`. Le nombre
et l'addition sont fournis. Écrivez la soustraction et la multiplication,
puis un cas d'erreur pour une opération sans deux valeurs disponibles.
Les tokens sont garantis par `Token.read/1` ; aucun autre type n'arrive ici.

```elixir
defmodule Stack do
  def step({:number, number}, stack), do: {:ok, [number | stack]}
  def step(:plus, [right, left | rest]), do: {:ok, [left + right | rest]}
  # Écrivez les deux autres opérations et le cas d'erreur.
end
```

```elixir
assert Stack.step({:number, 12}, [4]) == {:ok, [12, 4]}
assert Stack.step(:plus, [3, 2, 10]) == {:ok, [5, 10]}
assert Stack.step(:minus, [3, 8]) == {:ok, [5]}
assert Stack.step(:multiply, [3, 2, 1]) == {:ok, [6, 1]}
assert Stack.step(:plus, []) == {:error, :not_enough_operands}
assert Stack.step(:minus, [8]) == {:error, :not_enough_operands}
```

**Point d'arrêt possible.** Vous savez maintenant expliquer les
transitions et les écrire. Selon notre rythme, nous construirons la
boucle suivante ensemble ou vous la réaliserez en autonomie. La terminer
seul n'est pas un prérequis pour entrer dans AP2.

## Relier vos briques sans cacher les erreurs

Écrivez `RPN.run/1`, qui prend le texte entier. Sa première ligne peut
découper le texte avec `String.split/1`, puis appeler une fonction
récursive avec la liste des morceaux et la pile vide.

Le contrat est le suivant :

- À la fin de l'entrée, exactement une valeur sur la pile donne
  `{:ok, valeur}`. Aucune donne `{:error, :empty_program}` ; plusieurs
  donnent `{:error, :extra_operands}`.
- Pour chaque morceau, appelez `Token.read/1`, puis `Stack.step/2`.
  Une erreur est renvoyée telle quelle ; on s'arrête immédiatement.
- Un succès continue avec le reste des morceaux et la nouvelle pile.

Vous avez vu `case` pour distinguer un succès d'une erreur. Vous pouvez
en utiliser deux imbriqués. `defp` définit une fonction privée, appelée
uniquement depuis son module : notre boucle n'a pas besoin d'être publique.
L'un des cas d'arrêt est fourni ; à vous de construire les autres clauses
et le traitement d'un morceau. Expliquez ce qui diminue à chaque appel.

Pour conserver toute l'erreur dans un `case`, une clause peut s'écrire
`{:error, _reason} = error -> error`. Le motif vérifie la forme et
`error` nomme le tuple complet : aucune information n'est perdue.

```elixir
defmodule RPN do
  def run(source), do: walk(String.split(source), [])

  defp walk([], [value]), do: {:ok, value}
  # Écrivez les deux autres cas d'arrêt, puis le cas non vide.
end
```

```elixir
assert RPN.run("8 3 -") == {:ok, 5}
assert RPN.run("1 2 3 * +") == {:ok, 7}
assert RPN.run("  12   3 -  ") == {:ok, 9}
assert RPN.run("-12 3 *") == {:ok, -36}
assert RPN.run("") == {:error, :empty_program}
assert RPN.run("1 2") == {:error, :extra_operands}
assert RPN.run("1 +") == {:error, :not_enough_operands}
assert RPN.run("12x 3 +") == {:error, {:invalid_token, "12x"}}
```

Ajoutez un cas personnel qui pourrait faire échouer une implémentation
presque correcte. Expliquez quel souci il détecte, sans regarder les
assertions précédentes. Comparez vos choix avec ceux d'un voisin.

Retrouvez l'appel `walk(rest, next_stack)` dans votre boucle ou dans
la reprise collective. Malgré les deux `case`, rien ne reste à faire
après son retour : cet appel est terminal. Les erreurs, elles,
arrêtent le parcours. La pile d'appels reste bornée, mais la liste
`stack` de notre calculatrice peut grandir : ce sont deux piles distinctes.

En AP6, une boucle de processus transmettra ainsi son prochain état
en attendant de nouveaux messages. Dans le projet de compilation
CHIP-8 d'AP9, un appel terminal pourra devenir un saut : la machine
repart avec le nouvel état sans empiler une adresse de retour.

Notre langage exige des espaces entre les morceaux et écrit ses
opérateurs après les nombres. Il calcule des entiers avec trois opérations.
En AP2, nous voudrons lire une écriture plus familière, avec des décimaux,
des parenthèses et des priorités. Nous retrouverons le reste à lire,
les résultats étiquetés, les clauses et les appels récursifs.

## Garder une trace

Dans une cellule de texte, conservez votre trace de `"1 2 3 * +"`, un
cas de test de votre choix et ce qu'il détecte. Si la boucle reste
incomplète, indiquez les cas traités et le prochain essai envisagé :
vous rendez le travail de séance, il n'y a pas de copie à refaire dans
un carnet bilan.

Avec les quatre carnets précédents, ce fichier constitue votre travail
d'AP1. **Rendu unique du cycle AP1–AP3 : dimanche 6 décembre 2026 à
23 h 59 (Paris).** Aucun travail n'est demandé entre AP1 et AP2. Les
carnets de réserve et les prolongements restent facultatifs. Avant le
rendu, repartez d'une session vide et évaluez chaque carnet de haut en
bas ; décrivez les parties incomplètes. Pas de quiz en AP2, qui suit de
deux jours ; à partir d'AP3, chaque séance s'ouvrira par un quiz de dix
minutes sur la précédente, préparé par un chapitre du poly.

Enregistrez ce carnet sous `AP1/ap1-05-pile-calculatrice.livemd` dans votre dépôt privé
`ap-homework`.

Un bout de syntaxe oublié ? Les chapitres 1 à 4 du [poly](https://ap-2026.noce.fr/poly/poly-elixir.pdf)
reprennent les formes du langage, chacune avec un exemple et son résultat ;
le cours, lui, se fait dans les carnets.

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