On présente plusieurs exemples d'"opérateurs" dont le comportement est lié au contexte dans lequel on les utilise – il s'agit soit d'opérateurs du langage sont de textes qu'on aura identifiés comme représentant des concepts significatifs du langage.
Une grammaire est très généralement exprimée sous la forme d'une grammaire « indépendante du contexte » ("context-free"). En pratique, la grammaire d'un langage n'a quasiment jamais cette propriété. Mais on en fait pourtant largement usage, pour la réalisation des outils qui travaillent sur une grammaire (analyseur syntaxique, compilateur, éditeur syntaxique, ...).
Des exemples d'opérateurs contextuels :
RETURN » qui est la phrase par laquelle on indique qu'on termine l'exécution du sous-programme et quelle est la valeur retournée dans le cas d'une fonction ; l'« appel récursif » qui se représente par un appel de sous-programme mais qui a une sémantique beaucoup plus riche.EXIT » qui indique qu'on sort de la boucle – éventuellement en la nommant.le cas de la fonction
Figure 1.a : grammaire des fonctions
Pour simplifier la présentation, on n'introduit pas la notion de liste. De ce fait :
- une fonction est définie avec un unique paramètre,
- le corps de la fonction est composé d'une unique instruction,
- etc.
La prise en compte des listes compliquerait l'exemple mais ne le contredirait pas.
opérateur fonction
Il est défini par :
(def fonction ()
((def return ...« phrase RETURN »...)
(def call-rec ...« appel récursif »...))
(... déclaration de la fonction...))
c'est-à-dire que
return et call-rec : ceux-ci seront alors accessibles dans la déclaration de la fonction, et uniquement là.opérateur call-rec
C'est l'appel d'une fonction, formellement paramétré par :
BLOC-FCT : un « bloc de fonction », duquel on attend la propriété NOM,EXP : une expression qui représente le paramètre effectif de l'appel.Figure 1.a : grammaire des fonctions [ << masquer ]
(def affect ()
((VAR) ":=" (EXP) ";"))
(def cond ()
("IF " (TEST) " THEN" "^M"
" " (ALORS) "^M"
"ELSE" "^M"
" " (SINON) "^M"
"END IF;" "^M"))
(def none ()
("NONE"))
(def fonction
((def return
((def VAR ()
((NOM ((bloc-fct))))))
((affect)))
(def call-rec
((def BLOC-FCT
((bloc-fct))))
((call-fct))))
("FUNCTION " (NOM ((bloc-fct))) " (" (formel ((PARAM ((bloc-fct))))) ")"
" RETURNS " (TYP ((bloc-fct))) ";" "^M"
"BEGIN" "^M"
" " (INSTR ((bloc-fct))) "^M"
"END;" "^M"))
(def call-fct ()
((NOM ((BLOC-FCT))) " (" (effect) ")"))
(def formel ()
((NOM) " : " (SNS) " " (TYP)))
(def effect ()
((EXP)))
Figure 1.b : la fonction compte
La définition d'une fonction introduit celle de « bloc de fonction » : c'est le bloc des déclarations de la fonction, qui définit :
- le nom de la fonction et le type de la valeur retournée,
- les paramètres formels (leur nom, leur type, leur sens),
- les déclarations locales.
Partant, on pourra ensuite utiliser ces déclarations "assez profondément" dans l'arbre syntaxique du corps de la fonction. La difficulté est alors de savoir nommer ces déclarations "globales" – "globales" vis-à-vis du corps de la fonction.
Pour particulariser les déclarations "globales" de la fonction, on structure la définition « textuelle » de la fonction en plaçant les paramètres de définition d'une fonction à l'intérieur d'un texte de nom bloc-fct :
(def fct-compte ()
((fonction)
(def bloc-fct
((def NOM ...)
(def TYP ...)
(def PARAM ...)
...déclarations locales...
(def INSTR ...))))
((fonction)))
On définit le texte fct-compte : la fonction compte :
fonction : il donne la visibilité des opérateurs return et call-rec ;bloc-fct, les paramètres :NOM : le nom de la fonction,TYP : le type de la fonction,PARAM : la liste des paramètres formels (ici : le paramètre formel), définis par :NOM : le nom du paramètre formel,TYP : son type,TYP : le sens (IN, OUT, INOUT, ...),INSTR : la liste des instructions du corps (ici : l'instruction du corps).Dans le cas de la fonction compte, on définit localement le texte param-arbre :
PARAM est défini par référence au texte param-arbre : il s'agit donc d'un paramètre formel.Figure 1.b : la fonction compte [ << masquer ]
(def fct-compte ()
((fonction)
(def bloc-fct
((def NOM () ("compte"))
(def TYP () ("integer"))
(def PARAM
((param-arbre)))
(def param-arbre
((def NOM () ("a"))
(def TYP () ("arbre"))
(def SNS () ("IN"))))
(def INSTR
((def TEST
((def BLOC-FCT
((bloc-fct ((fct-feuille)))))
(def EXP () ((NOM ((param-arbre ((bloc-fct)))))))))
((call-fct)))
(def ALORS
((def EXP () ("1")))
((return)))
(def SINON
((def plus-gche
((def EXP
((def BLOC-FCT
((bloc-fct ((fct-fg)))))
(def EXP () ((NOM ((param-arbre ((bloc-fct)))))))))
((call-fct))))
((call-rec)))
(def plus-dte
((def EXP
((def BLOC-FCT
((bloc-fct ((fct-fd)))))
(def EXP () ((NOM ((param-arbre ((bloc-fct)))))))))
((call-fct))))
((call-rec)))
(def EXP ()
((plus-gche) " + " (plus-dte))))
((return))))
((cond))))))
((fonction)))
Un appel de fonction consiste alors en la fourniture d'un « bloc de fonction ». Par exemple :
(def TEST ()
((def BLOC-FCT
((bloc-fct ((fct-feuille)))))
(def EXP ...))
((call-fct)))
on fournit le « bloc de fonction » de la fonction feuille.
Un appel récursif se ramène à fournir le « bloc de fonction » implicitement visible dans la déclaration de la fonction – le sien :
(def call-rec ()
((def BLOC-FCT
((bloc-fct))))
((call-fct)))
(le paramètre EXP est fourni par défaut : call-rec est lui-même paramétré par EXP).
Figure 1.c : les fonctions sur les arbres
On reconnaît dans la définition « textuelle » des fonctions sur les arbres le même schéma de construction du texte – renforcé ici par le fait que le paramètre formel est toujours un arbre entré (mode "IN") : mais ceci est en fait un "hasard".
Figure 1.c : les fonctions sur les arbres [ << masquer ]
(def fct-feuille ()
((fonction)
(def bloc-fct
((def NOM () ("feuille"))
(def TYP () ("boolean"))
(def PARAM
((param-arbre)))
(def param-arbre
((def NOM () ("a"))
(def TYP () ("arbre"))
(def SNS () ("IN"))))
(def INSTR
((none))))))
((fonction)))
(def fct-fg ()
((fonction)
(def bloc-fct
((def NOM () ("fg"))
(def TYP () ("arbre"))
(def PARAM
((param-arbre)))
(def param-arbre
((def NOM () ("a"))
(def TYP () ("arbre"))
(def SNS () ("IN"))))
(def INSTR
((none))))))
((fonction)))
(def fct-fg ()
((fonction)
(def bloc-fct
((def NOM () ("fd"))
(def TYP () ("arbre"))
(def PARAM
((param-arbre)))
(def param-arbre
((def NOM () ("a"))
(def TYP () ("arbre"))
(def SNS () ("IN"))))
(def INSTR
((none))))))
((fonction)))
On reprend ici l'exemple du bloc de réservation, présenté auparavant (Chapitre 2.4, « Exemple de structuration des traitements »). Le bloc de réservation est un "opérateur" défini par l'utilisateur qui va connoter les phrases de « sortie brutale » : la « phrase EXIT » ou la « phrase RETURN » (on ne traite pas cette dernière).
le texte while
(def while ()
((def exit1 ()
("EXIT " (NOM((bloc-while))) ";" "^M")))
("while " (EXP-COND) " do /" (NOM((bloc-while))) "/" "^M"
" " (lst-init ((INSTR*))
((INSTR
((def exit () ((exit1)))))))
"END DO;" "^M"))
pour accéder au nom de la boucle on introduit un « bloc de boucle » bloc-while : les instructions sont décompilées en définissant le texte exit par sa forme simple exit1.
le texte reserve
(def reserve ()
((def exit2 ()
("free(" (NOM((bloc-reserve))) ");" "^M"
(exit1))))
("reserve(" (NOM((bloc-reserve))) ");" "^M"
" " (lst-init ((INSTR*))
((INSTR
((def exit () ((exit2)))))))
"free(" (NOM((bloc-reserve))) ");" "^M"))
on introduit un « bloc de réservation » bloc-reserve pour nommer la ressource concernée. Les instructions sont alors décompilées en définissant le texte exit par sa forme complétée exit2.
Note : en toute rigueur il faudrait dans reserve introduire un test qui vérifie s'il y a lieu de définir un texte exit, c'est-à-dire qui puisse savoir si l'on est placé à l'intérieur d'une boucle (texte while).
le "corps" du bloc de réservation
(def INSTR ()
((def EXP-COND () ("R.c<R.P"))
(def ALORS
(lst-env
((def INSTR () ("incr(R.c);" "^M")))
((def INSTR () ((exit))))))
(def SINON
(lst-env)))
((cond))
on utilise l'"opérateur" exit, en "oubliant" qu'on est placé à l'intérieur d'un bloc de réservation, et qu'il faut donc libérer la ressource : c'est le texte exit qui se le rappellera.