• Aucun résultat trouvé

6.2 Formalisation d’un module de référence

6.2.1 Notion de continuation et notations utilisées

Pour exprimer le sens des descriptions des pages, nous avons recours à une formalisa- tion des états du programme d’interprétation, et pour ce faire nous allons exprimer (par- tiellement) sa sémantique dénotationnelle5 à l’aide d’une représentation par passage de

continuation.

Selon Ridoux [102], une continuation peut être définie comme « [une] structure de données abstraite qui permet de formaliser le contrôle des langages de programmation en notant les calculs qui restent à faire. » Dans le cas d’une sémantique dénotationnelle, dite par passage de continuation, le sens mathématique d’un programme est une fonction qui prend une continuation (celle qui suit son exécution) et rend une continuation (celle qui correspond à son exécution). Cette approche est particulièrement intéressante dans notre travail car elle permet :

– de composer simplement des opérations pour définir le comportement du programme, et de définir de façon macroscopique les éléments requis, ce qui va dans le sens de notre démarche d’extension d’un système existant ;

– d’implémenter rapidement un compilateur qui transforme le langage de description en un programme d’interprétation de page, en spécifiant précisément l’état du pro- gramme à chaque étape de son calcul, ainsi que son flot de contrôle ;

5. Introduite par Scott et Strachey [109], cette formalisation du sens d’un programme est basée sur la re- présentation de ce dernier par une composition de fonctions mathématiques. Elle nous semble adaptée à la construction d’un compilateur comme le montrent les travaux récents sur les combinateurs d’analyseurs syn- taxiques. Voir [40] pour une introduction rapide.

– d’utiliser explicitement la notion de calcul futur bien adaptée au problème d’écriture de programmes intégrant des interactions asynchrones.

Nous allons donner deux exemples pour préciser ces notions et ce formalisme, avant de proposer une formalisation du programme de base du module d’interprétation de page que nous allons étendre dans la suite de cette section.

6.2.1.1 Premier exemple : notion de continuation

Commençons par préciser la notion de continuation : l’équation suivante montre la dé- finition d’une fonction simple :

incrementer : N → N

x 7→ x + 1 (6.8)

Une invocation de cette fonction6peut alors être :

(incrementer 3) (6.9)

Une façon de transformer cette définition pour lui permettre d’accepter une continuation κ est la suivante :

incrementer : N × (N → Cont) → Cont

x , κ 7→ (κ (x + 1)) (6.10)

Où on peut considérer que le type Cont est (État → État). Nous conserverons cette simpli- fication dans la suite. L’invocation de cette fonction prend alors une forme telle que :

(incrementer 3 λ x.(print(x))) (6.11)

Où λx.(print(x)) est une fonction qui accepte un paramètre et affiche la valeur de ce dernier sur la sortie standard. Elle permet de capturer l’état du programme à la fin du calcul.

Dans cette approche, une fonction ne retourne pas de résultat, mais elle applique la continuation sur son résultat. Les paramètres de la fonction de continuation (κ) passée à la fonction courante (incrementer) correspondent aux « résultats » produit par la fonction courante. Le contrôle du programme peut alors être réifié en devenant un ensemble de pa- ramètres passés de fonction en fonction. Des techniques classiques existent pour éviter le débordement de la pile d’appel dans tous les langages de programmation fonctionnelle mo- dernes.

6.2.1.2 Second exemple : compilation d’un analyseur syntaxique simple avec retour arrière

Pour illustrer l’intérêt de cette approche pour l’interprétation de documents, nous allons montrer comment construire un analyseur syntaxique simple permettant de reconnaître le langage

L

= ab∪ac. Nous allons nous baser sur une grammaire écrite de la façon suivante

qui est assez naturelle, mais qui présente l’inconvénient de nécessiter un nombre illimité de

6. Nous utilisons ici une notation préfixe pour l’application d’une fonction. La syntaxe est la suivante : parenthèse ouvrante, référence à la fonction, liste de paramètres séparés par des espaces, parenthèse fermante.

lectures avant de prendre une décision lors d’une analyse descendante7: S −→ AB | AC A −→ aA | a B −→ b C −→ c (6.12)

Ici, S représente l’axiome de la grammaire, les non-terminaux sont A, B et C, et les termi- naux sont a, b et c. La syntaxe du langage de description utilisé ici est alors extrêmement simple et contient quatre constructions permettant de décrire les règles de production de la grammaire :

l’alternative notée A | B indique que l’expression peut être formée de A ou de B ; la concaténation notée AB indique que l’expression est formée de A suivi de B ;

la consommation d’un terminal notée x, indique la position d’un terminal « x » dans le mot ;

la dérivation notée G −→ D indique une abstraction qui (dans le cas de langages hors contexte) indique que le non-terminal G résume l’expression D.

Ce type de grammaire est représentatif, selon nous, des difficultés présentes dans l’in- terprétation de contenus de documents complexes, comme les documents anciens ou dé- gradés, pour lesquels on doit considérer un préfixe d’analyse parfois important avant de pouvoir savoir ce qu’il représente. Un mécanisme de retour arrière est alors indispensable pour permettre à la fois l’écriture d’une description (grammaticale) intuitive, et permettre la détection de structures complexes. Il est donc nécessaire de permettre la génération auto- matique d’un analyseur à partir de la description du contenu de la page qui mette en œuvre ce comportement.

Grâce à un système de réécriture, il est possible de produire automatiquement une fonc- tion réalisant le traitement souhaité. Le système de réécriture présenté ci-après (la fonction

T

) est un exemple d’une telle transcription. Pour bien distinguer les éléments appartenant

au domaine de la description qui reste à traduire, des éléments qui appartiennent au domaine des fonctions, il est d’usage d’isoler les fragments de description par des crochets doubles ([[ et ]]).

Pour mettre en place le mécanisme de retour arrière, nous utilisons ici deux continua- tions, à l’instar d’un solveur Prolog. La continuation κ est la continuation de succès, à laquelle est passé le reste de la structure à analyser lorsque l’analyse peut progresser, et la continuation ζ est la continuation d’échec invoquée lorsque l’analyse ne peut plus progres- ser pour l’hypothèse courante. F représente la séquence à analyser8.

T

[[A | B]] ≡ λ Fκζ.(

T

[[A]] F κ (

T

[[B]] F κ ζ))

T

[[A B]] ≡ λ Fκζ.(

T

[[A]] F λ F.(

T

[[B]] Fκ ζ) ζ)

T

[[x]] ≡ λ Fκζ.  (κ cdr(F)) si car(F) = x ζ sinon

T

[[G]]

T

[[D]] si G −→ D (6.13)

Grâce à ce système de réécriture, on peut générer un analyseur syntaxique pour la langage

L

précédent. Letable 6.1détaille chacune des constructions de l’équation 6.13.

7. Ici, « descendante » fait référence à une analyse LL(k), avec k fini mais non borné [3].

8. Cette séquence est munie de deux opérations élémentaires : car et cdr qui renvoient respectivement le premier élément de la séquence, et la séquence privée du premier élément.

TABLE6.1 – Détail des constructions utilisées dans l’exemple d’analyseur syntaxique avec

retour arrière de l’équation 6.13(page97).

Règle de réécriture Description de la sémantique choisie

T

[[A | B]]

Alternative

Pour reconnaître A ou B, le système de réécriture construit, à par- tir de la description une fonction qui prend trois paramètres : une séquence à reconnaître F, une continuation de succès κ et une continuation d’échec ζ. Cette fonction va transmettre F et κ à

T

[[A]], en modifiant la continuation d’échec pour que

T

[[B]] soit

appelée si la reconnaissance de A échoue.

T

[[A B]]

Concaténation

Pour reconnaître A puis B, le système de réécriture produit une fonction qui consistera à appeler

T

[[A]] avec la séquence d’entrée

F, et à poursuivre l’analyse des éléments restants F′ avec

T

[[B]]

si l’élément abstrait A peut être reconnu. Dans les deux cas, un échec de reconnaissance produira un appel de ζ qui reste inchan- gée.

T

[[x]]

Terminal

Pour valider la reconnaissance d’un élément terminal x, le sys- tème de réécriture produit une fonction qui vérifie que le prochain élément de la séquence F est bien celui attendu, et dans ce cas appelle la continuation de succès κ avec la séquence mise à jour (privée du premier élément). Dans le cas contraire, la continuation d’échec ζ est appelée. Contrairement aux opérations de contrôle précédentes qui construisent l’arbre de recherche de solution en générant des fonctions de continuation, cette opération appelle directement les continuations et réduit l’arbre de recherche.

T

[[G]]

Dérivation

Cette règle de réécriture ne sert qu’à gérer les niveaux d’abstrac- tion de la description, et à sauvegarder les traductions déjà réali- sées.

Comment exploiter ces définitions en pratique ?Les fonctions produites par l’applica- tion du système de traduction décrit par la fonction

T

peuvent être directement implémen-

tées avec un langage de programmation fonctionnelle, mais peuvent aussi être implémentée dans d’autres environnements, comme par exemple une machine à piles. Une invocation simple de la fonction fS=

T

[[S]] pour déterminer si un mot appartient ou non au langage

L

peut alors être :

( fS "aaaaaaac"

λ f .(print « Mot reconnu. ») λ .(print « Mot inconnu. »))

6.2.2 Formalisation des propriétés requises du module d’interprétation de