Récursivité gauche

En informatique, et notamment en théorie des langages formels, en compilation et analyse syntaxique descendante, la récursivité gauche est un concept de grammaires formelles qui décrit un certain type de réapparition d'une variable dans une dérivation lors d'un processus d'analyse syntaxique.

Dans la terminologie des grammaires formelles et notamment des grammaires algébriques, une variable est récursive gauche s'il existe une règle de la grammaire dont le membre droit débute par cette variable (ce cas est dit récursivité directe) ou pour laquelle un mot dérivé débute par cette variable (cas de la récursivité indirecte).

L'élimination de la récursivité gauche est une étape préliminaire pour pouvoir utiliser l'analyse syntaxique descendante.

Définition

Une grammaire est récursive gauche s'il existe une variable A {\displaystyle A} qui dérive en un mot du langage étendu[1] qui débute par cette variable, formellement si

A + A α {\displaystyle A\Rightarrow ^{+}A\alpha } ,

+ {\displaystyle \Rightarrow ^{+}} indique l'opération consistant à appliquer une ou plusieurs règle de grammaire, et où α {\displaystyle \alpha } est une suite de symboles terminaux et non terminaux.

Récursivité gauche directe

La récursivité gauche est directe si la définition est réalisée en une étape, c'est-à-dire si la grammaire possède une règle de la forme

A A α {\displaystyle A\to A\alpha }

α {\displaystyle \alpha } est une suite de symboles terminaux et non terminaux. Par exemple, la règle

E E + T {\displaystyle E\to E+T}

d'une grammaire usuelle pour les expressions arithmétiques est récursive gauche directe. Un analyseur descendant pourrait l'implémenter par une procédure du type

function Expression()
{
    Expression();  match('+');  Terme();
}

ce qui provoque une boucle infinie d'appels récursifs à l’exécution.

Récursivité gauche indirecte

La récursivité gauche est indirecte si la définition est réalisée en plusieurs étapes. Formellement, elle demande l’existence d'une suite de règles selon le schéma suivant :

A 0 β 0 A 1 α 0 {\displaystyle A_{0}\to \beta _{0}A_{1}\alpha _{0}}
A 1 β 1 A 2 α 1 {\displaystyle A_{1}\to \beta _{1}A_{2}\alpha _{1}}
{\displaystyle \qquad \cdots }
A n β n A 0 α n {\displaystyle A_{n}\to \beta _{n}A_{0}\alpha _{n}}

β 0 , β 1 , , β n {\displaystyle \beta _{0},\beta _{1},\ldots ,\beta _{n}} sont des mots qui chacun peuvent dériver en le mot vide et α 0 , α 1 , , α n {\displaystyle \alpha _{0},\alpha _{1},\ldots ,\alpha _{n}} sont des mots quelconques. La dérivation

A 0 β 0 A 1 α 0 + A 1 α 0 β 1 A 2 α 1 α 0 + + A 0 α n α 1 α 0 {\displaystyle A_{0}\Rightarrow \beta _{0}A_{1}\alpha _{0}\Rightarrow ^{+}A_{1}\alpha _{0}\Rightarrow \beta _{1}A_{2}\alpha _{1}\alpha _{0}\Rightarrow ^{+}\cdots \Rightarrow ^{+}A_{0}\alpha _{n}\dots \alpha _{1}\alpha _{0}}

produit A 0 {\displaystyle A_{0}} comme premier symbole du mot dernier étendu.


Élimination de la récursivité gauche

Élimination de la récursivité directe

L'élimination de la récursivité directe procède par le remplacement de règles récursives gauche par d'autres, et l'introduction de nouveaux non-terminaux. Pour une variable récursive gauche A {\displaystyle A} , on supprime d'abord la règle A A {\displaystyle A\to A} si elle existe, puis on considère les règles restantes :

A A α 1 A α n β 1 β m {\displaystyle A\rightarrow A\alpha _{1}\mid \ldots \mid A\alpha _{n}\mid \beta _{1}\mid \ldots \mid \beta _{m}}

où:

  • chaque α {\displaystyle \alpha } est formé de symboles terminaux ou non terminaux, et
  • chaque β {\displaystyle \beta } est un mot formé de symboles terminaux ou non terminaux et qui ne commence pas par A {\displaystyle A} .

On remplace ces règles par deux ensembles de règles, un groupe de règles commençant par la variable A {\displaystyle A} :

A β 1 A β m A {\displaystyle A\rightarrow \beta _{1}A^{\prime }\mid \ldots \mid \beta _{m}A^{\prime }}

et un autre pour une nouvelle variable A {\displaystyle A'} (appelée le « reste » ou la « queue ») :

A α 1 A α n A ε {\displaystyle A^{\prime }\rightarrow \alpha _{1}A^{\prime }\mid \ldots \mid \alpha _{n}A^{\prime }\mid \varepsilon }

On répète ce processus jusqu'à épuisement des variables récursives gauches directes.

Cette méthode à l'inconvénient d'introduire des epsilon-règle ; une variante évite ce défaut, au prix de plus de règles. Les règles récursives gauches sont remplacées par[2] :

A β 1 A β m A β 1 β m {\displaystyle A\rightarrow \beta _{1}A'\mid \ldots \mid \beta _{m}A'\mid \beta _{1}\mid \ldots \mid \beta _{m}}
A α 1 A α n A α 1 α n {\displaystyle A'\rightarrow \alpha _{1}A'\mid \ldots \mid \alpha _{n}A'\mid \alpha _{1}\mid \ldots \mid \alpha _{n}}

Exemples

Par exemple, dans une grammaire simplifiée des expressions arithmétiques, les règles :

E E + E I N {\displaystyle E\rightarrow E+E\mid I\mid N}

peuvent être remplacées, pour supprimer la récursion gauche, par :

E I E N E {\displaystyle E\rightarrow I\,E'\mid N\,E'}
E + E E ε {\displaystyle E'\rightarrow {}+E\,E'\mid \varepsilon } .

L'image miroir du langage de Lukasiewicz a pour grammaire :

S S S a b {\displaystyle S\rightarrow SSa\mid b} .

on introduit une nouvelle variable T {\displaystyle T} et on remplace les règles par :

S b T {\displaystyle S\rightarrow bT}
T S a T ε {\displaystyle T\rightarrow SaT\mid \varepsilon } .

Puis, on substitue b T {\displaystyle bT} à S {\displaystyle S} dans la deuxième règle, et on obtient :

S b T {\displaystyle S\rightarrow bT}
T b T a T ε {\displaystyle T\rightarrow bTaT\mid \varepsilon } .

On peut aussi écrire, avec la deuxième méthode :

S b T b {\displaystyle S\rightarrow bT\mid b}
T S a T S a {\displaystyle T\rightarrow SaT\mid Sa} ,

puis remplacer la variable S {\displaystyle S} dans les deuxièmes règles :

S b T b {\displaystyle S\rightarrow bT\mid b}
T b T a T b a T b T b {\displaystyle T\rightarrow bTaT\mid baT\mid bT\mid b} .

Élimination de toute récursivité gauche

Une façon — brutale — d'éliminer toute récursivité gauche est de mettre la grammaire sous forme normale de Greibach. Plus simplement, on peut d'abord éliminer les ε-règles, c'est-à-dire les règles de la forme A ε {\displaystyle A\to \varepsilon } et les règles triviales A A {\displaystyle A\to A} . Ensuite, on numérote les variables, puis on opère sur les règles

A i A j α {\displaystyle A_{i}\to A_{j}\alpha }

pour avoir toujours i < j {\displaystyle i<j} . Si pour une règle, cette condition n'est pas vérifiée, on remplace A j {\displaystyle A_{j}} par ses membres droits de règle. Plus formellement, l'algorithme est le suivant[3] :

Numéroter les variables en 
  
    
      
        
          A
          
            1
          
        
        ,
        
        ,
        
          A
          
            n
          
        
      
    
    {\displaystyle A_{1},\ldots ,A_{n}}
  

pour  
  
    
      
        i
        =
        1
        ,
        
        ,
        n
      
    
    {\displaystyle i=1,\ldots ,n}
  

   pour 
  
    
      
        j
        =
        1
        ,
        
        ,
        i
        
        1
      
    
    {\displaystyle j=1,\ldots ,i-1}
  

      remplacer chaque production 
  
    
      
        
          A
          
            i
          
        
        
        
          A
          
            j
          
        
        γ
      
    
    {\displaystyle A_{i}\to A_{j}\gamma }
  
 par les productions
         
  
    
      
        
          A
          
            i
          
        
        
        
          δ
          
            1
          
        
        γ
        
        
        
        
          δ
          
            k
          
        
        γ
      
    
    {\displaystyle A_{i}\to \delta _{1}\gamma \mid \cdots \mid \delta _{k}\gamma }
  
, où
         
  
    
      
        
          A
          
            j
          
        
        
        
          δ
          
            1
          
        
        
        
        
        
          δ
          
            k
          
        
      
    
    {\displaystyle A_{j}\to \delta _{1}\mid \cdots \mid \delta _{k}}
  
 sont toutes les productions de 
  
    
      
        
          A
          
            j
          
        
      
    
    {\displaystyle A_{j}}
  

   éliminer la récursivité gauche directe pour les productions de 
  
    
      
        
          A
          
            i
          
        
      
    
    {\displaystyle A_{i}}
  

Voici un exemple, tiré du livre de Hopcroft et Ullman[4] : On considère la grammaire

A 1 A 2 A 3 , A 3 A 1 A 2 {\displaystyle A_{1}\to A_{2}A_{3},\quad A_{3}\to A_{1}A_{2}}
A 2 A 3 A 1 , A 3 a , A 2 b {\displaystyle A_{2}\to A_{3}A_{1},\quad A_{3}\to a,\quad A_{2}\to b}

Seule la règle A 3 A 1 A 2 {\displaystyle A_{3}\to A_{1}A_{2}} doit être modifiée, on remplace A 1 {\displaystyle A_{1}} par son unique membre de règle A 2 A 3 {\displaystyle A_{2}A_{3}} . La nouvelle grammaire est alors

A 1 A 2 A 3 , A 3 A 2 A 3 A 2 {\displaystyle A_{1}\to A_{2}A_{3},\quad A_{3}\to A_{2}A_{3}A_{2}}
A 2 A 3 A 1 , A 3 a , A 2 b {\displaystyle A_{2}\to A_{3}A_{1},\quad A_{3}\to a,\quad A_{2}\to b} .

Comme le membre droit de la règle A 3 A 2 A 3 A 2 {\displaystyle A_{3}\to A_{2}A_{3}A_{2}} commence par une variable de numéro inférieur, on substitue à la première occurrence de A 2 {\displaystyle A_{2}} les membres droits de règle A 3 A 1 {\displaystyle A_{3}A_{1}} et b {\displaystyle b} . Ainsi, la règle A 3 A 2 A 3 A 2 {\displaystyle A_{3}\to A_{2}A_{3}A_{2}} est remplacée par les règles A 3 A 3 A 1 A 3 A 2 {\displaystyle A_{3}\to A_{3}A_{1}A_{3}A_{2}} et A 3 b A 3 A 2 {\displaystyle A_{3}\to bA_{3}A_{2}} La nouvelle grammaire est

A 1 A 2 A 3 , A 3 A 3 A 1 A 3 A 2 , A 3 b A 3 A 2 {\displaystyle A_{1}\to A_{2}A_{3},\quad A_{3}\to A_{3}A_{1}A_{3}A_{2},\quad A_{3}\to bA_{3}A_{2}}
A 2 A 3 A 1 , A 3 a , A 2 b {\displaystyle A_{2}\to A_{3}A_{1},\quad A_{3}\to a,\quad A_{2}\to b} .

On obtient ainsi une grammaire où ne subsiste qu'une récursivité gauche directe pour la variable A 3 {\displaystyle A_{3}} que l'on élimine par la procédure précédente.

Un exemple traditionnel

La grammaire récursive gauche suivante est une grammaire traditionnelle pour décrire les expressions arithmétiques dans leur forme la plus rudimentaire :

E → E + T | T
T → T * F | F
F → (E) | id

Elle se transforme, en appliquant les règles ci-dessus, en :

E → T E’
E’→ +T E’ | ε
T → F T’
T’→ *F T’ | ε
F → (E) | id

On obtient une grammaire non récursive gauche et par conséquent on peut appliquer une analyse descendante sur celle-ci.

Note bibliographique

Les manuels de théorie des langages formels traitent indirectement de l'élimination de la récursivité gauche dans le cadre de la mise sous forme normale de Greibach. Les manuels de programmation, comme le « Dragon book », traitent l'élimination dans le cadre de l'analyse descendante; leur algorithme[3] 4.19 décrit la méthode esquissée ci-dessus plus en détail.

En linguistique informatique aussi ce problème est d'intérêt, comme le montre l'article suivant : Robert C. Moore, « Removing Left Recursion from Context-Free Grammars », Applied Natural Language Processing,‎ , p. 249-255 (lire en ligne)

Notes et références

  1. Un mot du langage étendu (en anglais « sentential form ») est un mot qui dérive de l’'axiome de la grammaire, mais qui est susceptible de contenir encore des variables.
  2. Ceci est la méthode préconisée par exemple dans : Olivier Carton, Langages formels, calculabilité et complexité, [détail de l’édition] (lire en ligne).
  3. a et b Alfred Aho, Monica Lam, Ravi Sethi et Jeffrey Ullman (trad. de l'anglais), Compilateurs : principes, techniques et outils : Avec plus de 200 exercices, Paris, Pearson, , 2e éd., 928 p. (ISBN 978-2-7440-7037-2 et 2744070378).
  4. John E. Hopcroft et Jeffrey D. Ullman, Formal languages and their relation to automata, Addison-Wesley, , 242 p. (ISBN 0-201-02983-9, SUDOC 004772571), , page 55.

Articles connexes

  • icône décorative Portail de la programmation informatique
  • icône décorative Portail de la logique
  • icône décorative Portail de l'informatique théorique