• Aucun résultat trouvé

cans l’instrumentation de marcissus pour l’évaluation à facettes, une catégorie de changements consistait à passer la valeur du pro- gram counter dans les appels récursifs à execute. dn s’inspirant de cette situation, on va ici étendre les objets qui représentent les termes du langages pour compter le nombre d’appels àev6l.

. . Passer de l’état aux opérations hoca

oour cela, on écrit une fonctionst6tequi va étendre les termes

numetpluspour incrémenter le compteur lors d’un appel ànum.ev6l

ouplus.ev6l.

v6r st6te = function(b6se) { v6r count =

v6r num = {__proto__: b6se.num, ev6l() {

count++

return b6se.num.ev6l.c6ll(this) }} v6r plus = {__proto__: b6se.plus,

ev6l() { count++

return b6se.plus.ev6l.c6ll(this) }} v6r getCount = function() { return count } return {__proto__: b6se, num, plus, getCount}} with (st6te({num,plus})) {

getCount() //:

plus.new(num.new( ), num.new( )).ev6l() //:

getCount() //:

}

cans ces redéǤnitions denumetplus, on retourne Ǥnalement la valeur de ev6l de base après l’incrémentation. ka valeur du compteur est accessible via la fonction getCount, qui sera expo- sée au code à l’intérieur du blocwith.

kors du premier appel àgetCount, le compteur est bien à zéro, puisqu’aucune instruction n’a été évaluée. ke second appel retourne 3, après l’évaluation de l’expression, ce qui correspond bien aux 3 appels deev6l: un pourplus, et un pour chaquenum.

be qui est marquant ici c’est que le compteur est une variable lo- cale à la fonctionst6te. hl n’existe que temporairement, lors d’une activation de cette fonction dans unwith. nn n’a pas eu à modiǤer le code précédent directement, juste à l’étendre par délégation.

bette délégation nous permet d’ailleurs de composer les ex- tensions entre elles. oar exemple, on peut composer l’extension

doubleavecst6te:

with (double({num})) {

with (st6te({num, plus})) { getCount() //:

plus.new(num.new( ), num.new( )).ev6l() //:

getCount() //:

}}

ke résultat de l’évaluation de l’expression est 6, ce qui indique que la modiǤcation double est bien active. dn même temps, le compteur est accessible et donne le bon résultat.

hoirs Construire un interpréteur par modules

motons que l’ordre d’activation des modules n’importe pas ici. kes inverser donne le même résultat :

with (st6te({num, plus})) { with (double({num})) {

getCount() //:

plus.new(num.new( ), num.new( )).ev6l() //:

getCount() //:

}}

dn général, la commutativité dépend de la déǤnition des mo- dules. cans le cas présent, le compteur peut être vu comme un eǣet de bord qui n’interfère pas avec l’évaluation, donc les deux commutent.

dnǤn, l’opération show que l’on a déǤni précédemment elle aussi commute. nn peut l’ajouter sans avoir à modiǤer une seule des déǤnitions précédentes.

with (st6te({num, plus})) { with (double({num})) {

with (show({num, plus})) { getCount() //: v6r n = plus.new(num.new( ), num.new( )) n.ev6l() //: getCount() //: n.show() //: " + " }}}

hci encore, l’ordre d’activation des modules n’a pas d’incidence sur le résultat.

.

Discussion

nn a montré comment étendre le langage, en lui ajoutant des nouveaux termes, et des nouvelles opérations, sans modiǤer le code source de l’interpréteur de base. kes extensions peuvent se compo- ser, sans avoir connaissance explicite ou préalable des autres ex- tensions. b’est un bel exemple de détournement.

Trois ingrédients principaux

be détournement a trois ingrédients principaux : la délégation, les extensions représentées par des fonctions, et la manipulation de portée. ka délégation nous a permis la représentation diǣérentielle des extensions. cans l’extension double, on réutilise le construc- teurnewet la fonctionnalité de base deev6lpar délégation. b’est en partie grâce à la délégation que l’on peut composer les exten- sions de l’interpréteur : l’objet numcréé par double, quand il est évalué, invoque la fonction ev6l du module de base ; à son tour, ce module peut être une composition, etc. ke code de l’évaluation

. . Discussion huche

de numest écrit une seule fois, et réutilisé par les extensions par délégation. ka délégation permet d’indiquer quel code réutiliser.

bomposer les extensions est facilité par leur représentation par des fonctions. mos extensionsdouble,show etst6tesont toutes des fonctions qui prennent en argument le module à étendre. qap- pelons qu’il n’y a pas de système de module en iavarcript ; les modules dont on parle ici sont de pures conventions. dn l’occur- rence, ce sont justes des objets iavarcript (des structures associa- tives). dn faire des fonctions incite naturellement à les composer. ka constructionwithn’est d’ailleurs pas nécessaire :

v6r d = double(double(double({num})))

plus.new(d.num.new( ), d.num.new( )).ev6l() //:

hl y a une diǣérence subtile entre compo- ser les modules ainsi et les combiner via plusieurs blocswith; diǣérence qui est discutée enA.3.

nn voit qu’en réalité, on ne fait que composer des fonctions qui retournent des objets (modules) qui contiennent plusieurs fonc- tions, que l’on va ensuite appeler. conc, la délégation permet la réutilisation, et les modules en tant que fonctions permettent la composition. ke problème, c’est que ce n’est pas suǦsant pour dé- tourner l’interpréteur, car on doit modiǤer le code (ajouter le pré- Ǥxed.) en fonction de l’extension. b’est la manipulation de portée qui vient résoudre ce problème.

cans tous les exemples donnés, on évalue toujours la même ligne de code, à savoir l’arbre syntaxique qui représente l’expression 1 + 2 :

plus.new(num.new( ), num.new( )).ev6l()

lais cette même expression, suivant les extensions activées, donne diǣérents résultats. k’astuce consiste à changer, pour chaque exemple, les référencesplusetnumgrâce àwith:

with (double(..)) {

plus.new(num.new( ), num.new( )).ev6l() }

cans cette expression, numfait référence à l’objet exporté par

double, maisdoublen’exporte pas d’objetplus. oour savoir à quel objetplusfait référence, il faut connaître les environnements ac- tifs et leur portée. r’il y a une extension qui déǤnit plus (comme

show) au dessus dedouble, c’est cet objet qui sera utilisé, sinon, ce sera leplusde base.

nn pourrait faire référence explicitement aux objets retournés pardouble:

v6r d = double({num})

plus.new(d.num.new( ), d.num.new( )).ev6l()

ce cette façon, on voit de quels modules sont tirés les objets

num et plus. lais ça nous demanderait de modiǤer le code de l’expression. ka constructionwithcrée un environnement à partir de l’objet passé en argument, mais qui hérite de l’environnement

hufa Construire un interpréteur par modules

courant. ri à l’extérieur duwith, l’environnement contient les as- sociations : num objet global plus eval num environnement du with créé par double() code ...

eval... code ... Environnement glob6lnum = objet de b6se plus = objet de b6se

alors, à l’intérieur d’unwith(double), on a l’environnement :

Environnement p6rent: Environnement glob6l num = objet de double

plus = objet de b6se

ka constructionwithne nous est utile que parce qu’elle construit cet environnement. dn suivant la sémantique du langage, on n’a plus à préǤxernumpard.; les préǤxes sont implicitement donnés par l’environnement. ka création de cet environnement est la clé du détournement, car si l’on change ce que plus etnum etev6l

signiǤent, alors on contrôle le résultat.

hl est important de remarquer que ces trois mécanismes ne sont pas spéciǤques à iavarcript, ni au scénario de l’extension d’un in- terpréteur. ka délégation est présente dans d’autres langages à pro- totypes, mais pour satisfaire la réutilisation, l’héritage de classes pourrait tout aussi bien convenir. qeprésenter les modules comme des fonctions pourrait se faire par convention dans de nombreux langages (en oython, ou en rcheme par exemple). kes analogues de la création d’environnements sont moins évidents, mais on peut y substituer des mécanismes qui désambiguïsent les noms.

oar exemple, dans le langage qust zqust], on pourrait obtenir un résultat semblable en utilisant les traits :

ke code complet de cet exemple se trouve

enA.2. enum Term {Num(u ),

Plus(Term, Term), }

fn num(u ) -> Term { ... }

fn plus(Term, Term) -> Term { ... } mod b6se { tr6it Ev6l { fn ev6l(..) }

impl Ev6l for Term { fn ev6l(..) {}}} mod double { tr6it Ev6l { fn ev6l(..) }

impl Ev6l for Term { fn ev6l(..) {}}} fn m6in() {

{ use b6se::Ev6l; num( ).ev6l(); } //: { use double::Ev6l; num( ).ev6l(); } //: }

hci les termes sont représentés par un type algébrique à deux va- riantes. nn ajoute des fonctions de constructions de ces termesnum

etplus. kes deux modulesb6seetdoubledéǤnissent le trait d’éva- luationEv6let fournissent chacun une implémentation de ce trait

. . Discussion huitain

pourTerm. nn voit ensuite que dans la fonctionm6in, on construit et évalue le même termenum( ), mais suivant le module importé localement paruse, le résultat est diǣérent. ka même instruction

num( ).ev6l()donne 3 quand on importe le traitb6se::Ev6let 6 quand on importe le traitdouble::Ev6l. bes deux appels àev6l

surnumsont équivalents aux appels suivants :

b6se::Ev6l::ev6l(num( )) double::Ev6l::ev6l(num( ))

nn voit alors qu’on invoque deux fonctions ev6l diǣérentes. ke mécanisme d’import du module ne fait que fournir un sucre syntaxique pour évoquer la syntaxe de la liaison dynamique, mais il n’y a aucune liaison ici, simplement des appels de fonctions. ce façon analogue, notre utilisation dewith en iavarcript revient à camouǥer les appels aux constructeurs fournis par chaque module sous une seule et même syntaxe.

Manipuler la portée suffit pour détourner le programme cans tous les exemples que l’on a donnés, on a pu voir qu’en- rober le code d’unwithnous permettait de modiǤer son résultat. Avec un with(double)on activait l’évaluation qui double les va- leurs des nombres, avecwith(show)on permettait d’utiliser l’opé- rationshowsur les arbres syntaxiques, etc.

tn inconvénient, c’est que l’on doit construire l’arbre syntaxique à l’intérieur duwith. nn ne peut pas construire l’arbre une seule fois, et réutiliser sa valeur pour diǣérentes évaluations. Autrement dit, on voudrait écrire :

1 v6r t = plus.new(num.new( ), num.new( )) 2

3 with (double({num})) { 4 t.ev6l() //:

5 }

rauf que évaluertici nous donne 3, et non 6. k’extensiondouble

ne s’applique pas, cardoublene fait que remplacer les références

numetplus. nr, à l’intérieur duwith, on n’utilise pasplusetnum

directement : ils ont déjà été utilisés à la construction ligne 1. nn peut simuler une réutilisation du même arbre syntaxique ; il suǦt d’en faire une fonction :

be qui revient à retarder la construction de cet arbre, ou le construire de façon paresseuse.

1 v6r p = function(m = {}) { 2 with (m) {

3 return plus.new(num.new( ), num.new( ))

4 }

5 }

ka variablep représente un arbre syntaxique, un programme de notre langage arithmétique. be langage est paramétré par le

hyphen Construire un interpréteur par modules

modulem, qui sera actif lors de la construction de l’arbre syntaxique ligne 3.

nn peut maintenant utiliser cette fonction, mais avec diǣérents modules :

p ().ev6l() //:

p (double({num})).ev6l() //:

p (double(double({num}))).ev6l() //:

p (show({num,plus})).show() //: " + "

oar défaut, si aucun module n’est passé en argument, on utili- sera lesplusetnumde base déclarés globalement. dn revanche, si on passedouble, on construit un arbre syntaxique avec ce module activé. nn peut ensuite évaluer cet arbre avec ev6l, pour obte- nir 6. ka composition de modules est possible par composition de fonctions, et les modules qui oǣrent de nouvelles opérations fonc- tionnent tout aussi bien.

nn a alors une fonction,p , dont le résultat est entièrement déterminé par l’argument qu’on lui passe. bette fonction en réalité appelle lesplus etnumpassés en argument à travers with. b’est une inversion de contrôle : la fonction p déǤnit la structure du programme, mais le code exécuté est fourni par l’appelant. dt cette inversion est rendue possible par l’utilisation duwithà l’intérieur de la fonction qui vient manipuler les référencesplusetnum. Au- trement dit, la manipulation de la portée suǦt pour détourner le programme.

cans ce chapitre, on a construit l’interpréteur de toutes pièces. lais on peut utiliser la manipulation de portée sur un programme existant. cans le chapitre suivant, on montre comment la manipu- lation de portée peut être employée pour détourner l’interpréteur marcissus.

Étendre un interpréteur par