MOGWAI sous le capot : sucre syntaxique et forme canonique RPN

Il y a un moment que tout concepteur de langage à pile redoute. Vous ouvrez un script écrit trois semaines plus tôt, et il ressemble à ça :

code size 'code_len' STO 0 @code_len 1 - 'i' {"_" code @i 1 sub char-> ->str + ->key} FOR

Parfaitement valide. Parfaitement exécutable. Complètement illisible.

C’est la tension classique des langages RPN : le paradigme est élégant et cohérent à l’exécution, mais naïvement écrit, il devient du code en écriture seule — même pour son propre auteur. MOGWAI répond à ce problème frontalement avec une couche de sucre syntaxique qui rend les scripts lisibles sans compromettre la pureté du modèle d’exécution sous-jacent.

Deux mondes, un seul langage

MOGWAI est, dans son essence, un langage à pile RPN. Chaque opération consomme des valeurs depuis la pile et y repousse ses résultats. Fonctions, structures de contrôle, affectations — tout obéit à la même règle : poser les arguments d’abord, puis appeler le mot qui les traite.

Mais MOGWAI dispose aussi d’un parser, et ce parser sait traduire une syntaxe de surface plus lisible vers la forme RPN canonique que le runtime exécute réellement. Ce sont deux couches distinctes, et comprendre la frontière entre elles est la clé pour comprendre le langage.

Passons en revue les principales transformations.

Définir une fonction

Dans la forme source, une définition de fonction ressemble à ceci :

to 'greet' with [name: .string] do
{
"Hello, " @name + ??
}

C’est lisible. On voit immédiatement que greet prend un paramètre chaîne appelé name, et que son corps concatène un message de salutation et l’affiche. Après parsing, le même code devient :

«[name: .string] ->safeVars "Hello, " @name + ??» 'greet' DEFUNC

Le sucre to ... with ... do a disparu. À sa place : un objet programme « », suivi du nom de la fonction et du mot DEFUNC. DEFUNC consomme les deux depuis la pile et enregistre la fonction.

L’instruction ->safeVars, insérée automatiquement par le parser juste après le record de paramètres, n’est pas de la simple plomberie. C’est le mot qui valide et lie les arguments entrants selon la signature déclarée — ici, en vérifiant que name est bien un .string. Sans elle, le record [name: .string] ne serait qu’une valeur décorative posée sur la pile sans aucun effet. C’est ->safeVars qui rend la déclaration de paramètres réellement effective : la vérification de type se produit à l’appel, pas au parsing, et tout écart est intercepté avant même que le corps de la fonction commence à s’exécuter.

Remarquez les délimiteurs « » — un héritage direct des calculatrices RPL de HP. Ce ne sont pas de simples crochets décoratifs. Un bloc « » crée son propre scope de variables locales : les variables définies à l’intérieur ne peuvent pas s’en échapper, et la fonction démarre dans un environnement propre à chaque appel. C’est fondamentalement différent d’un simple bloc { }, qui ne crée pas de nouveau scope et partage le contexte de variables de son appelant. La distinction est importante : les blocs { } sont des quotations — des valeurs que l’on peut manipuler, stocker dans un dictionnaire ou passer à une structure de contrôle. Les blocs « » sont des programmes — des contextes d’exécution isolés.

Affecter des variables

La forme source utilise l’opérateur flèche :

now -> 'begin'
code size -> 'code_len'

Après parsing :

now 'begin' STO
code size 'code_len' STO

L’opérateur -> est du sucre pur pour STO. Le parser inverse simplement l’ordre — dans la source, la valeur vient en premier, puis la flèche, puis le nom de la variable. Dans la forme canonique, la valeur et le nom sont tous deux sur la pile avant que STO soit appelé. Même sémantique, lisibilité différente.

Les structures de contrôle

C’est là que le sucre justifie le plus sa présence.

La boucle while

Forme source :

while (@code_ptr @code_len <) do
{
&dispatch @code_ptr get eval
'code_ptr' ++
}

Forme canonique :

{@code_ptr @code_len <}
{&dispatch @code_ptr get eval 'code_ptr' ++}
WHILE

La syntaxe while ... do { } encadre la condition et le corps dans des blocs explicites et lisibles. Après parsing, les deux deviennent des quotations sur la pile, et WHILE les consomme. Le runtime évalue la quotation de condition de manière répétée ; tant qu’elle laisse une valeur vraie sur la pile, il évalue la quotation du corps.

Le conditionnel if

Forme source :

if (@cell_ptr 0 >) then
{
'cell_ptr' --
}

Forme canonique :

{! @cell_ptr 0 >}
{'cell_ptr' --}
IF

Il y a quelque chose de subtil ici : le ! en début de bloc de condition. Ce marqueur indique au runtime d’auto-évaluer le bloc plutôt que de simplement le poser sur la pile comme une quotation. Sans lui, {@cell_ptr 0 >} resterait sur la pile comme une valeur, et il faudrait appeler eval explicitement ensuite. Avec !, le bloc s’auto-évalue au moment où il est rencontré, produisant le résultat booléen qu’IF attend. C’est un token minuscule avec un rôle précis : il condense le pattern poser-puis-évaluer en une seule étape.

La boucle for

Forme source :

0 @code_len 1 - for 'i' do
{
"_" code @i 1 sub char-> ->str + ->key
}

Forme canonique :

0 @code_len 1 - 'i'
{"_" code @i 1 sub char-> ->str + ->key}
FOR

Les bornes de la boucle (0 à @code_len 1 -) et le nom de la variable d’itération ('i') sont poussés sur la pile, suivis de la quotation du corps, suivie de FOR. Propre et mécanique.

Ce qui disparaît au passage

La forme canonique rend une chose évidente : les commentaires et les espaces disparaissent complètement. Le résultat du parsing est un flux de tokens plat et linéaire — pas d’indentation, pas de lignes vides, pas de # explications. Le runtime n’en a pas besoin. C’est quelque chose à garder à l’esprit quand on écrit du MOGWAI : le fichier source est pour les humains, la forme parsée est pour la machine, et le parser est le traducteur entre les deux.

Quelques exemples…

MOGWAI — Sucre syntaxique → Forme RPN canonique

Définition de fonction
to 'greet' with [name: .string] do
{
    ... corps ...
}
«[name: .string] ->safeVars
  ... corps ...
» 'greet' DEFUNC
Affectation de variable
now -> 'begin'
code size -> 'code_len'
now 'begin' STO
code size 'code_len' STO
Boucle for
0 @code_len 1 - for 'i' do
{
    ... corps ...
}
0 @code_len 1 -  'i'
  {... corps ...}
FOR
Boucle while
while (@code_ptr @code_len <) do
{
    ... corps ...
}
{@code_ptr @code_len <}
  {... corps ...}
WHILE
Conditionnel if / then
if (@cell_ptr 0 >) then
{
    'cell_ptr' --
}
{! @cell_ptr 0 >}
  {'cell_ptr' --}
IF
Switch / pattern matching
switch
{
    (@instr _91: ==) then { ... corps A ... }
    (@instr _93: ==) then { ... corps B ... }
}
{{@instr _91: ==} {... corps A ...}
 {@instr _93: ==} {... corps B ...}}
SWITCH
Commentaires et mise en forme
# Commentaire explicatif
# Indentation, lignes vides...
(supprimés — le flux RPN
est un flux linéaire de tokens)
Forme source (avec sucre syntaxique) Forme canonique RPN (après parsing)

Ce qu’il faut retenir

Chaque transformation effectuée par le parser MOGWAI suit la même règle de fond : prendre une construction lisible par un humain, en extraire les parties, les disposer sur la pile dans le bon ordre, et remplacer le mot-clé sucré par le mot primitif correspondant. to ... do devient DEFUNC. while ... do devient WHILE. if ... then devient IF. -> devient STO.

Aucune de ces transformations ne change la sémantique. Elles ne changent que la surface. Le runtime ne voit jamais le sucre — il ne voit qu’une séquence de valeurs et de mots, qui traversent la pile exactement comme si vous aviez écrit la forme canonique à la main.

C’est ce qui rend la conception fiable : le sucre est une commodité, pas un tour de magie. Si vous avez jamais besoin de comprendre ce que MOGWAI fait réellement, vous pouvez toujours tracer l’exécution en lisant la forme canonique. Les deux couches sont transparentes l’une pour l’autre.

Et si vous voulez mesurer ce que ce sucre vous épargne, essayez d’écrire un switch non trivial à la main en RPN pur. Vous apprécierez le mot-clé do très rapidement.

Stéphane.

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