avril
2009
Je me demandais à quoi pourrait ressembler un système de macro avancé pour dodo.
Il se trouve que dodo a déjà deux mécanismes qui pourraient être réutilisés: les intercepteurs d’appel (wrap) et les templates. Voici un exemple de ce que l’on pourrait faire avec s’ils étaient utilisés pour les macros.
Le but de cet exemple est de transformer une forme impérative (boucle while) en une forme à continuations.
Par exemple, je veux transformer:
loop while (x > 2.0) { .x = (x + e) * k }
en:
fun: x -> ret(double) if (x > 2.0) (x + e) * k -> x $loop1(x) else ret(x) . -> loop1 loop1(x)
Ce code est l’équivalent de la boucle while en CPS.
Ma proposition utilise wrap
pour définir la syntaxe de la boucle while et les actions associées, et template
pour transformer la syntaxe.
Commençons notre exemple. D’abord on va définir une expression régulière qui correspond à la syntaxe de la boucle while:
/loop while \(\n $cond\n \)( {\n $body\n } | :\n $body\n \.[ \n])/
Dans l’expression régulière, j’utilise un espace pour dénoter que l’on peut mettre des espaces ou tabulations, et un saut de ligne ou \n pour indiquer que l’on peut mettre des espaces, tabulations ou sauts de lignes. Soit [ \t]*
et [ \t\n\r]*
respectivement.
Les syntaxes acceptées sont
loop while (cond) {body}
et
loop while (cond): body.
Quand le compilateur rencontre cette syntaxe, les variables cond
et body
prennent une valeur et une action est exécutée. Je définis un template loop_while
qui est l’action utilisée pour transformer la syntaxe. Son invocation sera:
loop_while( use (body.inVars + cond.inVars) * body.outVars, set body.outVars, while cond, do body)
Les paramètres de loop_while
sont:
use
les variables d’entrée de la boucle. Ce sont les variables utilisées par le corps et les variables utilisées par la condition qui sont aussi modifiées par le corps de la boucle.set
les variables de sortie de la boucle. Ce sont les variables modifiées par le corps de la boucle.while
la condition.do
le corps de la boucle
Voici le code complet.
/loop while \(\n $cond\n \)( {\n $body\n } | :\n $body\n \.[ \n])/ { loop_while( use (body.inVars + cond.inVars) * body.outVars, set body.outVars, while cond, do body) } def template loop_while(use $inVars, set $outVars, while $cond, do $body) = fun: inVars -> ret(outVars.types) if (cond) body -> outVars $loop1(inVars) else ret(outVars) . -> loop1 loop1(inVars)
Voici comment fonctionne le code dans la forme à continuations.
fun: inVars -> ret(outVars.types)
Définit une fonction lambda qui prend en paramètre inVars
et retourne outVars
.
if (cond) body -> outVars $loop1(inVars)
Si la condition est vraie, exécute le corps de la boucle puis fait un appel récursif à la fonction lambda avec inVars
comme arguments.
else ret(outVars) .
Sinon, retourne outVars
.
-> loop1 loop1(inVars)
Enfin, met la fonction lambda dans une variable loop1
puis appelle la fonction avec inVars
comme arguments.