Évaluer une formule mathématique saisie par l’utilisateur avec GIZMO et MOGWAI

L’application Formula Calculator est un exemple concret de ce qu’on peut construire avec GIZMO en peu de lignes : l’utilisateur saisit une formule mathématique avec une variable X, définit un intervalle et un pas, et obtient un tableau de valeurs calculées à la volée.

Interface d'une calculatrice de formules avec des champs pour entrer une formule, un début, une fin et un pas, ainsi qu'un bouton pour calculer.
Interface TUI de formula.mog

Le code complet est disponible dans les exemples du repo GIZMO.

Au-delà du résultat visuel, cette petite app illustre trois patterns MOGWAI particulièrement utiles que je vais détailler ici.


1. Transformer une string en code exécutable avec ->code et eval

La formule est saisie par l’utilisateur sous forme de texte — par exemple 2 X * 7 + X sin +. C’est une string ordinaire. Pour la calculer, il faut l’exécuter comme du code MOGWAI.

MOGWAI fournit ->code pour compiler une string en un objet code, et eval pour l’exécuter :

wFormula ->code -> 'wFormula'

À partir de ce moment, wFormula n’est plus une string mais un bloc de code prêt à être exécuté. Dans la boucle de calcul, pour chaque valeur de X, on affecte la variable et on exécute le code compilé :

wStart wEnd for 'X' step wStep do
{
wFormula eval (.number) check -> 'v'
&values (! X v) +
}

La formule est compilée une seule fois avant la boucle. C’est bien plus efficace que de parser la string à chaque itération — sur de grands intervalles, la différence est significative.


2. Valider que les saisies sont bien des nombres

Les champs Début, Fin et Pas sont saisis sous forme de texte. Avant de lancer le calcul, il faut s’assurer que ces valeurs sont des nombres valides. MOGWAI propose ->num pour convertir une string en nombre — mais si la string n’est pas un nombre valide, ->num génère une erreur.

On s’en sert justement pour détecter les entrées invalides. Toute la fonction calculate est enveloppée dans un guard { } else { } :

guard
{
wFormula ->code -> 'wFormula'
wStart ->num -> 'wStart'
wEnd ->num -> 'wEnd'
wStep ->num -> 'wStep'

# ... boucle de calcul
}
else
{
switch
{
(wStart ->type .number !=) then { "start is not a valid number !" }
(wEnd ->type .number !=) then { "end is not a valid number !" }
(wStep ->type .number !=) then { "step is not a valid number !" }
(true) then { "Error {! error.last}" eval }
}
}

Si l’une des conversions échoue, l’exécution saute directement dans le bloc else. Le switch identifie ensuite quelle entrée est en cause et produit un message d’erreur lisible. Le dernier cas (true) attrape les erreurs inattendues — par exemple une formule syntaxiquement invalide ou qui ne pousse pas de nombre sur la pile.

Ce pattern guard / switch est une façon élégante de gérer la validation sans multiplier les if imbriqués.

Pour valider le résultat de la formule elle-même, on utilise (.number) check :

wFormula eval (.number) check -> 'v'

(.number) check vérifie que la valeur au sommet de la pile est bien de type .number. Si la formule pousse autre chose — une string, rien, plusieurs valeurs — l’erreur est interceptée par le guard et un message clair est affiché.


3. Yielder le thread TUI avec after 0 do { }

Le calcul peut être long selon l’intervalle et le pas choisis. Avant de lancer le calcul, on vide le tableau et on met à jour la statusbar pour indiquer à l’utilisateur que le calcul est en cours :

[window.update name: 'valuesTableview' rows: ()]
[window.update name: 'statusbar' title: "Calculating..."]

Problème : MOGWAI et Terminal.Gui partagent le même thread. Le calcul est bloquant — si on l’exécute directement dans le onClick:, le moteur TUI n’a pas l’occasion de se redessiner. L’utilisateur ne verra jamais le message « Calculating… » ni le tableau vidé.

La solution : after 0 do { }. Cette primitive MOGWAI diffère l’exécution du bloc de code au prochain cycle du moteur, en laissant Terminal.Gui rafraîchir l’écran entre les deux.

onClick: 
{
[window.update name: 'valuesTableview' rows: ()]
[window.update name: 'statusbar' title: "Calculating..."]

after 0 do
{
now -> 'begin'

[!
wFormula: {! [name: 'formulaEdit'] ui.gprop text: get}
wStart: {! [name: 'startEdit'] ui.gprop text: get}
wEnd: {! [name: 'endEdit'] ui.gprop text: get}
wStep: {! [name: 'stepEdit'] ui.gprop text: get}
]

calculate -> 'r'

now begin - ->duration ms: get -> 'd'

if (r ->type .list ==) then
{
[! window.update name: 'valuesTableview' rows: r]

[! window.update
name: 'statusbar'
title: "Ready - {! r size} values - calculation time {! d} ms"
]
}
else
{
[window.update name: 'valuesTableview' rows: ()]
[! window.update name: 'statusbar' title: r]
}
}
}

Le délai de 0 milliseconde suffit : on ne cherche pas à attendre, juste à céder le contrôle au thread TUI le temps d’un cycle. Le résultat est visible immédiatement — l’interface se met à jour avant que le calcul commence.


Résultat

Ces trois patterns combinés donnent une app réactive et robuste en quelques dizaines de lignes de MOGWAI. La formule est compilée une fois, les entrées sont validées proprement, et l’interface reste fluide pendant le calcul.

Le code complet de Formula Calculator est disponible dans les exemples du repo GIZMO.

Et si tu veux en savoir plus sur GIZMO ou MOGWAI :

Laisser un commentaire

En savoir plus sur CODING 4 PHONE

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture

En savoir plus sur CODING 4 PHONE

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture