L'expérience montre que les
constituent un outil de programmation
simple d'emploi, puissant et flexible.
Cela vient de ce que le langage des
est celui des grammaires
sans contexte avec attributs,
que ces grammaires sont traduites en un programme qui peut fonctionner comme
un analyseur ou comme un générateur,
et que tout le langage Prolog peut être utilisé pour décrire les attributs.
Nous avons voulu transposer cet outil à
Prolog et plus généralement
étudier ce que pourrait être une grammaire logique fondée sur les
formules héréditaires de Harrop*.
Il faut noter que la traduction de en Prolog est un exemple de
métaprogrammation* en Prolog où la tradition utilise la
représentation non-close* des grammaires et des programmes objet
(voir la section «Métaprogrammation en Prolog et
Prolog»*).
La première version publiée
de ce traducteur [Clocksin et Mellish 81],
qui en a influencé beaucoup d'autres,
contenait une erreur de manipulation de métavariable qui causait,
rarement, la génération d'un programme faux.
Notre travail de transposition des
à
Prolog a donc d'abord porté sur
le métaprogramme de traduction de
à Prolog (en
Prolog),
puis sur la définition d'une variante
de
pour
Prolog,
et enfin sur la traduction de cette variante en
Prolog.
Le résultat est un formalisme appelé *
(pour Higher-order Hereditary Harrop Grammar -- grammaire de Harrop
héréditaire d'ordre supérieur)
qui intègre les
formules de Harrop au niveau grammatical et au niveau du calcul des
attributs [Le Huitouze et al. 93a].
Au niveau grammatical,
l'implication correspond à la faculté d'ajouter à la grammaire
de nouvelles règles pendant la durée
du dépliement d'un non-terminal.
Au niveau du calcul des attributs,
l'implication correspond à la faculté d'ajouter au programme
de nouvelles règles de calcul pendant la durée
du dépliement d'un non-terminal,
et la quantification universelle correspond à la faculté de construire
une structure sémantique (un attribut) qui est une fonction de la
variable universellement quantifiée.
Un nouveau connecteur permet d'établir le lien entre une occurrence
d'un non-terminal et le mot qu'il engendre en cette occurrence.
L'augmentation locale de la grammaire permet de décrire aisément des
déclarations locales de symboles, ou des phénomènes
d'extraposition en langue naturelle.
Par exemple, la règle suivante décrit l'effet à distance
du pronom relatif sur la structure de la phrase relative qu'il introduit.
relative -->
$ ["dont"]
( groupe_nom --> $ []
==> phrase
) .
Dans la syntaxe des ,
--> est le signe de production,
est le signe de concaténation,
le signe $ introduit des terminaux du langage engendré
et ==> est le signe d'introduction dynamique de nouvelles règles.
Cette règle se lit donc de la manière suivante :
le pronom relatif
«dont» marque le début d'une phrase dans laquelle un groupe
nominal est vide.
La règle présentée n'est qu'un schéma.
Une règle plus complète a des attributs pour contrôler l'accord
en mode du groupe nominal vide (complément introduit par un
«de»)
et construire une représentation sémantique pour la relative
(une fonction dont le pronom est le paramètre formel).
La production de structures sémantiques fonctionnelles est le corollaire
de la représentation par abstraction* utilisée en
métaprogrammation en Prolog.
C'est aussi le codage naturel des grammaires de Montague pour la langue
naturelle [Montague 74, Warren 83b, Miller et Nadathur 86a, Coupet-Grimal et Ridoux 95].
Par exemple,
la règle
suivante est une variante de la règle précédente qui
décrit en plus une partie
des structures sémantiques associées aux non-terminaux.
relative REL -->
$ ["dont"]
all compl
( groupe_nom compl --> $ []
==> phrase (REL compl) ) .
On y voit que la structure sémantique REL est une fonction d'un type qui lui permet d'être appliquée à la structure sémantique d'un groupe nominal.
En ,
on ne peut vérifier qu'une séquence de terminaux
obéit à une contrainte donnée qu'en construisant la contrainte
au fur et à mesure de la génération des terminaux.
Cela nuit au partage des règles de grammaires puisque des segments
obéissant à la même syntaxe,
mais vérifiant des conditions sémantiques différentes doivent être
décrits par des non-terminaux différents.
On a besoin d'une construction qui permet de capturer une séquence
engendrée par un non-terminal,
pour vérifier sa sémantique à part.
Nous avons appelé ce connecteur delta.
Il relie un non-terminal et un prédicat,
et engendre ce que le non-terminal engendre et qui vérifie le prédicat.
On peut penser que ce connecteur manquait déjà en
.
Par exemple,
la règle
suivante décrit des mots engendrés par le non-terminal
nt1 et qui sont de longueur L.
nt0 L --> delta entréesortie
(dlength entrée sortie L) nt1 .
La traduction de en
Prolog par
Prolog est très régulière
et concentre la manipulation des variables objet dans la définition
d'un petit nombre de combinateurs (un par connecteur).
Cela rend improbable l'erreur commise
dans la première version des
.
Par exemple,
le traitement de la concaténation consiste en les
déclarations et définitions suivantes.
La déclaration de type
type ''
((list A)->(list A)->o) -> ((list A)->(list A)->o) -> ((list A)->(list A)->o) .
donne le type du connecteur de concaténation.
Pour fixer les idées,
nous avons adopté pour la représentation des mots la technique des
listes en différence*,
qui est aussi la plus souvent employée dans les .
Les mots sont représentés par des paires de listes qui correspondent,
l'une à leur début,
l'autre à ce qui suit leur fin.
Un non-terminal est un prédicat sur les mots
(donc de type
),
et le connecteur de concaténation joint deux non-terminaux pour en construire
un troisième.
Une déclaration d'opérateur
spécifie que la concaténation a une notation infixe associative à droite.
La déclaration de macro
#define CONC gauchedroite
e
s
( sigma Lien
(gauche e Lien, droite Lien s) )
donne la sémantique de la concaténation sous la forme d'un combinateur
qui construit la jointure des prédicats gauche et droite
par la variable Lien.
Celle-ci est une variable objet représentée
par une -variable (Lien
(...)).
C'est la seule mention de variable objet.
Noter aussi l'usage de
l´ordre supérieur* :
gauche
et droite
quantifient des prédicats.
Cette déclaration n'est pas strictement nécessaire,
mais elle permet de donner un nom à un combinateur qui pourra
servir plusieurs fois.
La clause
(Item1 Item2) Entrée Sortie :- CONC Item1 Item2 Entrée Sortie .
donne la sémantique de la concaténation en utilisant le combinateur
CONC dans une règle d'interprétation.
Il n'est plus question de variable objet.
Cette clause n'est nécessaire que parce que toute la traduction ne peut pas être
faite pendant la compilation.
En effet,
il est possible d'avoir des non-terminaux inconnus lors de la traduction de
la grammaire,
et ceux-ci ne seront connus que pendant l'exécution.
C'est le cas lorsque l'on code en
l'étoile de Kleene.
opt NT --> ( $ [] : NT ) .
etoile NT --> opt ( NT etoile NT ) .
Le non-terminal NT n'est pas connu lors de la traduction de la grammaire.
Enfin, la clause
type trad_item ((list A)->(list A)->o) -> (o->o->o) -> o .
trad_item (Gauche0 Droite0) (CONC Gauche Droite) :-
trad_item Gauche0 Gauche ,
trad_item Droite0 Droite .
donne la sémantique de la concaténation en utilisant le combinateur
CONC dans une règle de traduction vers un programme Prolog.
Là encore,
il n'est plus question de variable objet.
Cette clause serait la seule utile si tous les non-terminaux étaient connus
à la compilation.