C'est parce qu'un simple StringReplace, comme une expression régulière, ne marche "qu'en avant", c'est-à-dire qu'il ne revient pas sur ce qu'il vient de remplacer. La récursion dans PCRE ne concerne que des éléments de pattern, pas l'emplacement en cours dans le sujet.
Si on fait ça :
il se passe, en séquence (souligné = déjà été traité, gras = en cours de traitement) :
ZaaabaabaaabbW
ZaaabaaabbW
ZaaabaaabbW
ZaaaabbW
ZaaaabbW
ZaabW
ZaabW
Donc si l'entrée contient un nombre non borné de sous-chaînes à remplacer qui produiront d'autres sous-chaînes sujettes à remplacement, comme ci-dessus, il faudrait autant (donc un nombre non borné !) de StringReplace pour être certain d'effectuer tous les remplacements dans le cas général. Du coup c'est soit la récursion sur l'ensemble de la chaîne en cours de traitement, soit l'astuce à la Mikell qui fonctionne dans notre cas très particulier.
Que ce soit avec récursion ou dé-récursion (suite de remplacements explicites, en nombre forcément limité) il y a une limite au-delà de laquelle ça ne fonctionne plus (explosion de la pile ou dépassement du nombre maximum préu de remplacements) et ça a été la source d'innombrables bugs et "exploits" malveillants dans d'innombrables logiciels.
Seule une boucle non bornée (do .. until ou while 1 .. wend) apporte l'assurance de résultat correct dans tous les cas, ... sauf si l'entrée est un flux lui-même non borné, auquel cas on ouvre la voie à une paralysie partielle (DoS = denial of service) si on ne met pas un stéthoscope pour arrêter les frais.
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.