Aller au contenu
← 1001 Widgets
N° 031 · ●●●

Calculatrice scientifique

Parenthèses, puissances, sinus, racines : une vraie calculatrice qui comprend une expression entière, sans jamais utiliser eval. Le cousin de Tanuki Arcade. Le bon exercice pour écrire son premier analyseur.

JS
Ce qu'on apprend
  • Découper un texte en jetons
  • Respecter les priorités avec une descente récursive
  • Remplacer eval par une grammaire sûre
Pourquoi ce langage

Pourquoi une calculatrice scientifique ? La calculatrice simple calcule au fil des touches. Ici, on tape 2^10 + sqrt(16) * (3 - 1) et il faut respecter les priorités : puissance, puis multiplication, puis addition. C'est le travail d'un analyseur.

La méthode s'appelle la descente récursive. On découpe d'abord le texte en jetons : nombres, opérateurs, noms. Puis une fonction par niveau de priorité appelle la suivante. Les parenthèses relancent tout depuis le haut.

Pourquoi pas eval ? eval exécuterait n'importe quel code tapé dans le champ. Un analyseur ne comprend que ce qu'on lui a appris, et peut expliquer une erreur.

Comment ça marche
  1. LES JETONS

    On transforme le texte en une liste : nombres, noms et opérateurs.

    function jetons(s) {
      const t = [];
      let i = 0, m;
      while (i < s.length) {
        if (/\s/.test(s[i])) { i++; continue; }
        if ((m = /^\d+(\.\d+)?/.exec(s.slice(i)))) { t.push({ n: Number(m[0]) }); i += m[0].length; continue; }
        if ((m = /^[a-z]+/i.exec(s.slice(i)))) { t.push({ id: m[0] }); i += m[0].length; continue; }
        if ('+-*/^()'.includes(s[i])) { t.push({ op: s[i++] }); continue; }
        throw new Error('Caractère inattendu : ' + s[i]);
      }
      return t;
    }
  2. LA GRAMMAIRE

    Un niveau de priorité par fonction : expr appelle terme, terme appelle facteur.

    function evaluer(texte) {
      const t = jetons(texte);
      let p = 0;
      const voir = o => t[p] && t[p].op === o;
    
      function expr() {          // + et -
        let v = terme();
        while (voir('+') || voir('-')) v = t[p++].op === '+' ? v + terme() : v - terme();
        return v;
      }
      function terme() {         // * et /
        let v = facteur();
        while (voir('*') || voir('/')) v = t[p++].op === '*' ? v * facteur() : v / facteur();
        return v;
      }
      function facteur() {       // ^ se lit de droite à gauche : 2^3^2 = 2^9
        const base = unaire();
        if (voir('^')) { p++; return base ** facteur(); }
        return base;
      }
      function unaire() {        // le moins devant un nombre
        if (voir('-')) { p++; return -unaire(); }
        return primaire();
      }
  3. LES ÉLÉMENTS DE BASE

    Un nombre, une constante, une fonction ou une expression entre parenthèses.

      function primaire() {
        const k = t[p++];
        if (!k) throw new Error('Expression incomplète');
        if ('n' in k) return k.n;
        if (k.id === 'pi') return Math.PI;
        if (k.id) {
          const f = { sin: Math.sin, cos: Math.cos, sqrt: Math.sqrt, ln: Math.log }[k.id];
          if (!f || !voir('(')) throw new Error('Fonction inconnue : ' + k.id);
          p++; const v = expr(); p++;   // saute ( et )
          return f(v);
        }
        if (k.op === '(') { const v = expr(); p++; return v; }
        throw new Error('Inattendu : ' + k.op);
      }
  4. TOUT LIRE

    S'il reste des jetons après l'expression, c'est une erreur de saisie.

      const r = expr();
      if (p < t.length) throw new Error('Expression mal formée');
      return r;
    }
Pièges

PIÈGE 1 : Utiliser eval

Piège

Mauvais : eval(champ.value). N'importe quel code tapé dans le champ s'exécute, et les messages d'erreur sont incompréhensibles.

Fix

Correct : un analyseur qui ne connaît que les nombres, les opérateurs et une liste fermée de fonctions.

PIÈGE 2 : La puissance de gauche à droite

Piège

Mauvais : traiter ^ comme * dans une boucle. 2^3^2 donne 64 au lieu de 512.

Fix

Correct : facteur s'appelle lui-même à droite du ^, ce qui rend l'opérateur associatif à droite.

PIÈGE 3 : Le moins unaire avant la puissance

Piège

Mauvais : appliquer le moins après la puissance sans y réfléchir, puis être surpris par -2^2.

Fix

Correct : choisir une règle, la documenter, et la tester. Ici, le moins s'applique au nombre : -2^2 vaut 4, comme sur beaucoup de calculatrices de poche.

PERSONNALISER
  • La multiplication implicite : 2pi ou 3(4+1), en ajoutant un * entre deux jetons qui se suivent.
  • Des variables : x = 5, puis 2x + 1.
  • Un historique des calculs, à rappeler avec la flèche du haut.
OPTIMISATION
  • Pour tracer des courbes, analysez l'expression une fois en arbre, puis évaluez l'arbre pour chaque x.
  • Arrondissez l'affichage à 12 chiffres significatifs : 0,1 + 0,2 s'affichera 0,3.
VERS REACT

L'analyseur vit dans un fichier pur, sans React, facile à tester.

L'expression est un useState, le résultat se calcule au rendu dans un try et catch.

Les touches du clavier virtuel insèrent à la position du curseur grâce à un useRef sur le champ.

Commentaires
0/1000

Chargement des commentaires…