Les compilateurs permettent d'analyser et manipuler des données textuelles. Leurs applications sont très nombreuses. `Hoa\Compiler` propose de manipuler plusieurs compilateurs selon les besoins.

## Table des matières

## Introduction

> Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire viennent aisément.

Un langage est une façon d'exprimer ou de formuler une solution à un problème. Et des problèmes, il en existe beaucoup. Nous lisons et écrivons dans plusieurs langages au quotidien, et certains de ces langages sont compris par des machines. Cette opération est possible grâce aux compilateurs.

La théorie des langages étudie entre autres l'analyse automatique de ces langages à travers des outils comme des automates ou des grammaires. Il est nécessaire d'avoir un cours détaillé pour bien comprendre tous ces concepts. Toutefois, nous allons essayer de vulgariser un minimum pour permettre une utilisation correcte de `Hoa\Compiler`.

### Langage et grammaire

Un langage est un ensemble de mots. Chaque mot est une séquence de symboles appartenant à un alphabet. Un symbole représente la plus petite unité lexicale d'un langage, il est atomique et nous l'appellons lexème (ou token en anglais). Les séquences de lexèmes représentant les mots sont construites avec des règles. À partir d'un mot et d'une règle racine, nous allons essayer de dériver ses sous-règles. Si une dérivation existe, alors le mot est considéré comme valide, sinon il est considéré comme invalide. Nous parlons aussi de reconnaissance de mots. Par exemple, si nous considérons les règles suivantes :

        exp ::= exp + exp
              | nombre
     nombre ::= chiffre nombre
              | chiffre
    chiffre ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Le mot que nous voulons reconnaître est `7 + 35`. La règle racine est `exp`. Si nous la dérivons (de gauche à droite et de haut en bas, ou left-to-right et top-to-bottom en anglais), nous pouvons avoir `exp + exp` ou `nombre` (la disjonction, i.e. le « ou », est représentée par le symbole « `|` ») :

    exp + exp | nombre
    → exp + exp
    → ( exp + exp | nombre ) + exp
    → nombre + exp
    → ( chiffre nombre | chiffre ) + exp

Nous continuons à dériver jusqu'à éliminer toutes les règles et n'avoir que des lexèmes :

    …
    → ( chiffre nombre | chiffre ) + exp
    → chiffre + exp
    → ( 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ) + exp
    → 7 + exp
    → 7 + ( exp + exp | nombre )
    → 7 + nombre
    → 7 + ( chiffre nombre | chiffre )
    → 7 + chiffre nombre
    → 7 + ( 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 ) nombre
    → 7 + 3 nombre
    → 7 + 3 ( chiffre nombre | chiffre )
    → 7 + 3 chiffre
    → 7 + 3 ( 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 )
    → 7 + 3 5

Une dériviation existe bel et bien pour reconnaître le mot `7 + 35`, c'est donc un mot valide pour ces règles.

Un ensemble de règles est appelé une grammaire. Et donc, une grammaire représente un langage !

Toutefois, il existe plusieurs catégories de grammaires. C'est en 1956 qu'a été formulée la hiérarchie de Chomsky, classant les grammaires en quatre niveaux :

1. grammaires générales, ou unrestricted grammars, reconnaissant les langages dits de Turing, aucune restriction n'est imposée aux règles ;
2. grammaires contextuelles, ou context-sensitive grammars, reconnaissant les langages contextuels ;
3. grammaires algébriques, ou context-free grammars, reconnaissant les langages algébriques, basés sur les automates à pile ;
4. grammaires régulières, ou regular grammars, reconnaissant les langages réguliers.

Chaque niveau reconnait le niveau suivant. `Hoa\Compiler` ne traite que les langages définis par les grammaires de niveau 3 et 4. Pour donner rapidement une idée, les grammaires régulières peuvent s'apparenter aux expressions régulières (comme les PCRE), bien connues des développeurs. Mais les grammaires régulières ne permettent pas par exemple de reconnaître des couples de symboles (comme des parenthèses, des accolades ou des guillemets), alors que les grammaires algébriques le permettent (grâce à la notion de piles de lexèmes).

### Reconnaissance de mots

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#parsers'), 800, 180); var grid = paper.grid(0, 0, 800, 180, 5, 2); var word = grid.push(paper.rect(0, 0, 140, 80, 3, 'mot'), 0, 0); var sequence = grid.push(paper.rect(0, 0, 140, 80, 3, 'séquence'), 2, 0); var trace = grid.push(paper.rect(0, 0, 140, 80, 3, 'résultat'), 4, 0); grid.push(paper.rect(0, 0, 140, 50, 3, 'abcdef'), 0, 1); grid.push(paper.rect(0, 0, 380, 50, 3, '[[a ⟼ …], [bc ⟼ …], [d ⟼ …], [ef ⟼ …]]'), 2, 1); grid.push(paper.rect(0, 0, 140, 50, 3, 'valide/invalide'), 4, 1);

paper.link.between(word, sequence, 'analyseur lexical'); paper.link.between(sequence, trace, 'analyseur syntaxique'); });

En général, le processus de compilation débute par deux analyses : lexicale et syntaxique. Une analyse lexicale consiste à découper un mot en une séquence de lexèmes. Cette séquence sera ensuite utilisée par l'analyseur syntaxique afin de vérifier que le mot appartient au langage.

Selon la grammaire, la reconnaissance ne se fera pas de la même manière, mais le principe reste identique : prendre les lexèmes les uns après les autres dans la séquence et vérifier qu'ils permettent d'avancer dans la dérivation des règles de notre grammaire.

Les analyses syntaxiques sont aussi classées en catégories : LL, LR, LALR etc. `Hoa\Compiler` ne propose que des analyseurs syntaxiques LL, pour Left-to-right Leftmost derivation, i.e. de la plus haute règle vers la plus profonde, et les règles sont dérivées de la gauche vers la droite. Là encore, il existe des sous-catégories, dont deux que traite `Hoa\Compiler` : LL(1) et LL(*). D'une manière générale, nous parlons d'analyseurs syntaxiques LL(k) : si un lexème ne permet pas de dériver une règle comme il faut, alors l'analyseur peut revenir jusqu'à k étapes en arrière ; nous parlons aussi de backtrack. Autrement dit, les règles peuvent être ambiguës : à chaque fois que nous dérivons une règle de la grammaire, nous avons plusieurs choix possibles et l'analyseur peut se tromper, c'est pourquoi il doit parfois revenir en arrière. La variable k permet de définir le niveau d'ambiguïté. Si une grammaire peut être analysée par un analyseur syntaxique LL(1), elle est dite non-ambiguë : à chaque lexème utilisé pour dériver nos règles, il n'y a qu'un seul choix possible. Et si nous avons un analyseur syntaxique LL(*), cela signifie que la variable k est indéfinie. L'exemple suivant illustre une grammaire non-ambiguë :

    rule ::= a b c | d e f

Et cet exemple illustre une grammaire ambiguë :

    rule1 ::= a rule2
    rule2 ::= b rule3 | b rule4
    rule3 ::= c d
    rule4 ::= e f

Voyons quand nous essayons de trouver une dérivation pour le mot `abef` à partir de la règle racine `rule1` :

    rule1
    → a rule2                      a  bef ✔
      → a (b rule3 | b rule4)      a  bef
        → a b rule3               ab  ef  ✔
          → a b c d              abe  f   ✖
        ← a b rule3               ab  ef  ✖
      ← a (b rule3 | b rule4)      a  bef
        → a b rule4               ab  ef  ✔
          → a b e f             abef      ✔

La règle `rule2` est ambiguë, ce qui peut entraîner une mauvaise dérivation et donc un retour en arrière, un backtracking.

## Compilateur de compilateurs LL(k)

Écrire des compilateurs est une tâche laborieuse. Ce n'est pas forcément toujours difficile mais souvent répétitif et long. C'est pourquoi il existe des compilateurs de compilateurs, ou autrement dit, des générateurs de compilateurs. La plupart du temps, ces compilateurs de compilateurs utilisent un langage intermédiaire pour écrire une grammaire. La bibliothèque `Hoa\Compiler` propose la classe `Hoa\Compiler\Llk\Llk` qui permet l'écriture de compilateurs de compilateurs à travers un langage dédié.

### Langage PP

Le langage PP, pour PHP Parser, permet d'exprimer des grammaires algébriques. Il s'écrit dans des fichiers portant l'extension `.pp` (voir le fichier `hoa://Library/Compiler/.Mime`).

Une grammaire est constituée de lexèmes et de règles. La déclaration d'un lexème se fait de la manière suivante : `%token namespace_in:name value -> namespace_out`, où `name` représente le nom du lexème, `value` représente sa valeur, au format PCRE (attention à ne pas reconnaître de valeur vide, auquel cas une exception sera levée), et `namespace_in` et `namespace_out` représentent les noms des espaces de noms et sont optionels (vaut `default` par défaut). Par exemple `number` qui représente un nombre composé de chiffres de `0` à `9` :

    %token number \d+

Les espaces de noms représentent des sous-ensembles disjoints de lexèmes, utilisés pour faciliter les analyses. Une déclaration `%skip` est similaire à `%token` excepté qu'elle représente un lexème à sauter, c'est à dire à ne pas considérer. Un exemple courant de lexèmes `%skip` est les espaces :

    %skip space \s

Pour expliquer les règles, nous allons utiliser comme exemple la grammaire `Json.pp`, grammaire légèrement simplifiée du langage JSON (voir la RFC4627). La grammaire complète se situe dans le fichier `hoa://Library/Json/Grammar.pp`. Ainsi :

    %skip   space          \s
    // Scalars.
    %token  true           true
    %token  false          false
    %token  null           null
    // Strings.
    %token  quote_         "        -> string
    %token  string:string  [^"]+
    %token  string:_quote  "        -> default
    // Objects.
    %token  brace_         {
    %token _brace          }
    // Arrays.
    %token  bracket_       \[
    %token _bracket        \]
    // Rest.
    %token  colon          :
    %token  comma          ,
    %token  number         \d+

    value:
        &lt;true> | &lt;false> | &lt;null> | string() | object() | array() | number()

    string:
        ::quote_:: &lt;string> ::_quote::

    number:
        &lt;number>

    #object:
        ::brace_:: pair() ( ::comma:: pair() )* ::_brace::

    #pair:
        string() ::colon:: value()

    #array:
        ::bracket_:: value() ( ::comma:: value() )* ::_bracket::

Nous remarquons que nous avons deux espaces de noms pour les lexèmes : `default` et `string` (cela permet de ne pas confondre les lexèmes `quote_` et `string:_quote` qui ont la même représentation). Nous remarquons ensuite la règle `value` qui est une disjonction de plusieurs lexèmes et règles. Les constructions du langage PP sont les suivantes :

- `rule()` pour appeler une règle ;
- `&lt;token>` et `::token::` pour déclarer un lexème (les espaces de noms n'apparaissent pas ici) ;
- `|` pour une disjonction (un choix) ;
- `(…)` pour grouper ;
- `e?` pour dire que `e` est optionel ;
- `e+` pour dire que `e` peut apparaître 1 ou plusieurs fois ;
- `e*` pour dire que `e` peut apparaître 0 ou plusieurs fois ;
- `e{x,y}` pour dire que `e` peut apparaître entre `x` et `y` fois ;
- `#node` pour créer un nœud `node` dans l'arbre ;
- `token[i]` pour unifier des lexèmes entre eux.

Peu de constructions mais amplement suffisantes.

Enfin, la grammaire du langage PP est écrite… dans le langage PP ! Vous pourrez la trouver dans le fichier `hoa://Library/Compiler/Llk/Llk.pp`.

### Processus de compilation

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#overview'), 800, 360); var flow = paper.grid(0, 0, 800, 360, 1, 4); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur lexical')); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur syntaxique')); flow.push(paper.rect(0, 0, 200, 70, 3, 'trace')); flow.push(paper.rect(0, 0, 200, 70, 3, 'AST'));

var annot = paper.grid(180, 0, 80, 360, 1, 4); annot.push(paper.rect(0, 0, 45, 45, 22.5, '1')); annot.push(paper.rect(0, 0, 45, 45, 22.5, '2')); annot.push(paper.rect(0, 0, 45, 45, 22.5, '3')); annot.push(paper.rect(0, 0, 45, 45, 22.5, '4')); });

Le processus de compilation qu'utilise `Hoa\Compiler\Llk\Llk` est classique. Il commence par analyser lexicalement la donnée textuelle, le mot, i.e. à transformer notre donnée en une séquence de lexèmes. L'ordre de déclaration des lexèmes est primordial car l'analyseur lexical va les prendre les uns après les autres. Ensuite, c'est l'analyseur syntaxique qui entre en jeu afin de reconnaître notre donnée.

Si l'analyse syntaxique est un succès, nous obtenons une trace. Cette trace peut être transformée en AST, pour Abstract Syntax Tree. Cet arbre représente notre donnée textuelle après analyse. Il a l'avantage de pouvoir être visité (nous détaillerons plus loin), ce qui permet par exemple d'ajouter de nouvelles contraintes qui ne peuvent pas être exprimées dans la grammaire, comme une vérification de type.

Manipulons un peu `Hoa\Compiler\Llk\Llk`. Cette classe est un assistant pour lire une grammaire au format PP facilement. Elle prend en seul argument un flux en lecture vers la grammaire et retourne un compilateur `Hoa\Compiler\Llk\Parser` prêt à l'emploi. Sur ce compilateur, nous allons appeler la méthode `Hoa\Compiler\Llk\Parser::parse` pour analyser une donnée JSON. Si la donnée est correcte, nous aurons en retour un AST, sinon une exception sera levée. Enfin, nous allons utiliser le visiteur `Hoa\Compiler\Visitor\Dump` pour afficher notre AST :

    // 1. Load grammar.
    $compiler = Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp'));

    // 2. Parse a data.
    $ast      = $compiler->parse('{"foo": true, "bar": [null, 42]}');

    // 3. Dump the AST.
    $dump     = new Hoa\Compiler\Visitor\Dump();
    echo $dump->visit($ast);

    /**
     * Will output:
     *     >  #object
     *     >  >  #pair
     *     >  >  >  token(string:string, foo)
     *     >  >  >  token(true, true)
     *     >  >  #pair
     *     >  >  >  token(string:string, bar)
     *     >  >  >  #array
     *     >  >  >  >  token(null, null)
     *     >  >  >  >  token(number, 42)
     */

Quand nous écrivons et testons une grammaire, nous allons répéter ces trois tâches très régulièrement. C'est pourquoi, le script `hoa` propose la commande `compiler:pp`. Cette commande propose d'analyser une donnée par rapport à une grammaire et d'appliquer un visiteur si besoin sur l'AST résultant. Notons que la lecture de la donnée peut se faire à travers un pipe :

    $ echo '[1, [1, [2, 3], 5], 8]' | hoa compiler:pp Json.pp 0 --visitor dump
    >  #array
    >  >  token(number, 1)
    >  >  #array
    >  >  >  token(number, 1)
    >  >  >  #array
    >  >  >  >  token(number, 2)
    >  >  >  >  token(number, 3)
    >  >  >  token(number, 5)
    >  >  token(number, 8)

C'est un moyen pratique pour tester rapidement des données par rapport à notre grammaire. Il ne faut pas hésiter à regarder l'aide de la commande `compiler:pp` pour plus de détails !

Les analyses s'effectuent sur la règle racine mais nous pouvons préciser une autre règle à l'aide du deuxième argument de la méthode `Hoa\Compiler\Llk\Parser::parse` :

    $compiler->parse('{"foo": true, "bar": [null, 42]}', 'object');

Pour utiliser la règle racine, il suffit de passer `null`.

Et enfin, pour ne pas générer l'AST mais uniquement savoir si la donnée est valide ou pas, nous pouvons utiliser le dernier argument de notre méthode en lui passant `false` :

    $valid = $compiler->parse('{"foo": true, "bar": [null, 42]}', null, false);
    var_dump($valid);

    /**
     * Will output:
     *     bool(true)
     */

#### Erreurs

Les erreurs de compilation sont remontées à travers des exceptions, ainsi :

    $ echo '{"foo" true}' | hoa compiler:pp Json.pp 0 --visitor dump
    Uncaught exception (Hoa\Compiler\Exception\UnexpectedToken):
    Hoa\Compiler\Llk\Parser::parse(): (0) Unexpected token "true" (true) at line 1
    and column 8:
    {"foo" true}
           ↑
    in hoa://Library/Compiler/Llk/Parser.php at line 1

Plusieurs exceptions peuvent remonter selon le contexte :

- durant l'analyse lexicale, `Hoa\Compiler\Exception\UnrecognizedToken` quand un lexème n'est pas reconnu, i.e. quand la donnée textuelle ne peut plus être découpée en une séquence de lexèmes, et `Hoa\Compiler\Exception\Lexer` quand d'autres erreurs plus générales arrivent, par exemple si un lexème reconnaît une valeur vide ;
- durant l'analyse syntaxique, `Hoa\Compiler\Exception\UnexpectedToken` quand un lexème n'est pas attendu, i.e. qu'il ne permet plus de dériver les règles de la grammaire.

L'exception parente est `Hoa\Compiler\Exception\Exception`.

#### Nœuds

Le processus de compilation aboutit très souvent à la production d'un AST. Il est important de contrôler sa forme, sa taille, les données qu'il contient etc. C'est pourquoi il est nécessaire de comprendre la notation `#node` car elle permet de créer des nœuds dans l'AST. Une précision tout d'abord, les lexèmes déclarés avec la syntaxe `&lt;token>` apparaîtront dans l'arbre, alors que les autres lexèmes, déclarés avec la syntaxe `::token::`, n'y apparaîtront pas. En effet, dans notre dernier exemple, les lexèmes `quote_`, `brace_`, `colon`, `comma` etc. n'apparaissent pas. Ensuite, il est important de noter que les déclarations de nœuds se surchargent les unes par rapport aux autres au sein d'une même règle. Enfin, un nom de règle peut être précédé par `#`, comme pour la règle `#array`, qui permet de définir un nœud par défaut, mais il peut être surchargé. Par exemple, si nous remplaçons la règle `array` par :

    #array:
        ::bracket_:: value() ( ::comma:: value() #bigarray )* ::_bracket::

Si le tableau ne contient qu'une seule valeur, le nœud s'appelera `#array`, sinon il s'appelera `#bigarray` ; voyons plutôt :

    $ echo '[42]' | hoa compiler:pp Json.pp 0 --visitor dump
    >  #array
    >  >  token(number, 42)
    $ echo '[4, 2]' | hoa compiler:pp Json.pp 0 --visitor dump
    >  #bigarray
    >  >  token(number, 4)
    >  >  token(number, 2)

Bien sûr, il peut arriver qu'un nœud soit créé ou pas selon le dérivation empruntée dans la règle. Le mécanisme est normalement assez intuitif.

#### Espace de noms

Détaillons un peu le fonctionnement de l'analyseur lexical vis à vis des espaces de noms.

Les lexèmes sont placés dans des espaces de noms, c'est à dire que seuls les lexèmes de l'espace de noms courant seront utilisés par l'analyseur lexical. L'espace de noms par défaut est `default`. Pour déclarer l'espace de noms d'un lexème, il faut utiliser l'opérateur `:`. Quand un lexème est consommé, il peut changer l'espace courant pour la suite de l'analyse lexicale, grâce à l'opérateur `->`. Ainsi, nous allons déclarer cinq lexèmes : `foo` et `bar` dans l'espace `default`, `baz` dans `ns1` et `qux` et `foo` dans `ns2`. Le fait de déclarer deux fois `foo` n'est pas gênant car ils sont dans des espaces de noms différent :

    %token      foo   fo+     -> ns1
    %token      bar   ba?r+   -> ns2
    %token  ns1:baz   ba?z+   -> default
    %token  ns2:qux   qux+
    %token  ns2:foo   FOO     -> ns1

Écrire `default:bar` est strictement équivalent à `bar`. Le lexème `foo` emmène dans `ns1`, `bar` dans `ns2`, `ns1:baz` dans `default`, `ns2:qux` reste dans `ns2` et `ns2:foo` emmène dans `ns1`. Observons la séquence de lexèmes produite par l'analyseur lexical avec la donnée `fooooobzzbarrrquxFOObaz` :

    $pp = '%token      foo   fo+     -> ns1'     . "\n" .
          '%token      bar   ba?r+   -> ns2'     . "\n" .
          '%token  ns1:baz   ba?z+   -> default' . "\n" .
          '%token  ns2:qux   qux+'               . "\n" .
          '%token  ns2:foo   FOO     -> ns1';

    // 1. Parse PP.
    Hoa\Compiler\Llk\Llk::parsePP($pp, $tokens, $rawRules);

    // 2. Run the lexical analyzer.
    $lexer    = new Hoa\Compiler\Llk\Lexer();
    $sequence = $lexer->lexMe('fooooobzzbarrrquxFOObaz', $tokens);

    // 3. Pretty-print the result.
    $format   = '%' . (strlen((string) count($sequence)) + 1) . 's  ' .
                '%-13s %-20s  %-20s  %6s' . "\n";
    $header   = sprintf($format, '#', 'namespace', 'token name', 'token value', 'offset');

    echo $header, str_repeat('-', strlen($header)), "\n";

    foreach ($sequence as $i => $token) {
        printf(
            $format,
            $i,
            $token['namespace'],
            $token['token'],
            $token['value'],
            $token['offset']
        );
    }

    /**
     * Will output:
     *      #  namespace     token name            token value           offset
     *     ---------------------------------------------------------------------
     *      0  default       foo                   fooooo                     0
     *      1  ns1           baz                   bzz                        6
     *      2  default       bar                   barrr                      9
     *      3  ns2           qux                   qux                       14
     *      4  ns2           foo                   FOO                       17
     *      5  ns1           baz                   baz                       20
     *      6  default       EOF                   EOF                       23
     */

Nous lisons les lexèmes, leur espace et leur valeur dans le tableau. La donnée a pu être découpée, et nous sommes passés d'un espace à un autre. Si cette fois nous essayons avec la donnée `foqux`, nous aurons une erreur : `fo` correspond au lexème `foo` dans l'espace `default`, nous changeons alors d'espace pour `ns1`, et là, aucun lexème dans cet espace ne peut reconnaître au moins `q`. Une erreur sera levée.

Jusqu'à maintenant, nous avons vu comment passer d'un espace à l'autre avec l'opérateur `->`. Aucun historique sur les espaces traversés n'est conservé. Toutefois, dans certains cas rares, il peut arriver qu'un espace de lexèmes soit accessible via plusieurs autres et que nous aimerions qu'un lexème déclenche le retour vers l'espace de noms précédent. Autrement dit, nous aimerions un historique des espaces traversés et pouvoir y naviguer (en arrière uniquement). C'est le rôle du mot-clé `__shift__ * n`, à utiliser après l'opérateur `->` et à la place du nom d'un espace. `__shift__` est équivalent à dire : revient à l'espace précédent. `__shift__` est équivalent à `__shift__ * 1`, et `__shift__ * n` à : revient `n` fois à l'espace précédent.

Lorsque le mot-clé `__shift__` apparaît dans la grammaire, les espaces sont gérés comme une pile, d'où le vocabulaire shift. Il faut faire attention à bien dépiler les espaces pour ne pas avoir une pile trop conséquente.

Prenons en exemple la grammaire suivante :

    %token       foo1  a    -> ns1
    %token       foo2  x    -> ns2
    %token       end   e

    %token   ns1:bar   b
    %token   ns1:baz   c    -> ns3
    %token   ns1:tada  t    -> __shift__

    %token   ns2:bar   y
    %token   ns2:baz   z    -> ns3
    %token   ns2:tada  T    -> __shift__

    %token   ns3:qux   =   -> __shift__

    #test:
        ( &lt;foo1> | &lt;foo2> ) &lt;bar> &lt;baz> &lt;qux> &lt;tada> &lt;end>

Cette grammaire reconnaît deux données : `abc=te` et `xyz=Te`. Si le premier lexème `foo1` est rencontré, l'analyseur syntaxique changera d'espace pour `ns1`. Quand il rencontrera le lexème `ns1:baz`, il passera dans `ns3`. Ensuite, quand il rencontrera `ns3:qux`, il reviendra à l'espace précédent, soit `ns1`. Et ainsi de suite. Maintenant, s'il ne rencontre pas `foo1` mais `foo2`, il ira dans l'espace `ns2`, puis dans `ns3` puis à nouveau `ns2`. Les mots-clés `__shift__` pour `ns1:tada` et `ns2:tada` n'ont pas d'autres buts que de dépiler les espaces, mais aucune ambiguïté n'existe : ces espaces ne sont accessibles que par un seul espace, à savoir `default`.

Maintenant, enregistrons cette grammaire dans un fichier `NamespaceStack.pp` et utilisons l'option `-s/--token-sequence` de la commande `hoa compiler:pp` :

    $ echo -n 'abc=te' | hoa compiler:pp NamespaceStack.pp 0 --token-sequence
     #  namespace     token name            token value                     offset
    -------------------------------------------------------------------------------
     0  default       foo1                  a                                    0
     1  ns1           bar                   b                                    1
     2  ns1           baz                   c                                    2
     3  ns3           qux                   =                                    3
     4  ns1           tada                  t                                    4
     5  default       end                   e                                    5
     6  default       EOF                   EOF                                  6

    $ echo -n 'xyz=Te' | hoa compiler:pp NamespaceStack.pp 0 --token-sequence
     #  namespace     token name            token value                     offset
    -------------------------------------------------------------------------------
     0  default       foo2                  x                                    0
     1  ns2           bar                   y                                    1
     2  ns2           baz                   z                                    2
     3  ns3           qux                   =                                    3
     4  ns2           tada                  T                                    4
     5  default       end                   e                                    5
     6  default       EOF                   EOF                                  6

Nous voyons que l'analyse lexicale a réussi à jongler avec les espaces de noms, comme attendu. Nous avions deux façons d'accéder à l'espace `ns3` : soit depuis `ns1`, soit depuis `ns2`. L'analyseur a réussi à créer un historique des espaces et à y naviguer.

#### Unification

Une caractéristique qu'apporte le langage PP par rapport à d'autres langages de grammaires connus est la capacité d'exprimer une unification de lexèmes. Imaginons la grammaire suivante :

    %token  quote   '|"
    %token  string  \w+

    rule:
        ::quote:: &lt;string> ::quote::

Les guillemets qui entourent la chaîne de caractère peuvent être de deux sortes : simple, avec le symbole « `'` », ou double, avec le symbole « `"` ». Ainsi, les données `"foo"` et `'foo'` sont valides, mais également `"foo'` et `'foo"` !

L'unification des lexèmes permet d'ajouter une contrainte supplémentaire sur la valeur des lexèmes à l'exécution. La syntaxe est la suivante : `token[i]`. La valeur de `i` indique quels lexèmes vont devoir porter la même valeur. Et enfin, l'unification est locale à une instance d'une règle, c'est à dire qu'il n'y a pas d'unification entre les lexèmes de plusieurs règles et que cela s'applique sur la règle appelée uniquement. Ainsi, l'exemple devient :

    rule:
        ::quote[0]:: &lt;string> ::quote[0]::

Ce qui invalide les données `"foo'` et `'foo"`.

L'unification trouve de nombreuses applications, comme par exemple unifier les noms des balises ouvrantes et fermantes du langage XML.

### Abstract Syntax Tree

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#overview_ast'), 800, 360); var flow = paper.grid(0, 0, 800, 360, 1, 4); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur lexical').attr({opacity: .3})); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur syntaxique').attr({opacity: .3})); flow.push(paper.rect(0, 0, 200, 70, 3, 'trace').attr({opacity: .3})); flow.push(paper.rect(0, 0, 200, 70, 3, 'AST'));

var annot = paper.grid(180, 0, 80, 360, 1, 4); annot.push(paper.rect(0, 0, 45, 45, 22.5, '1').attr({opacity: .3})); annot.push(paper.rect(0, 0, 45, 45, 22.5, '2').attr({opacity: .3})); annot.push(paper.rect(0, 0, 45, 45, 22.5, '3').attr({opacity: .3})); annot.push(paper.rect(0, 0, 45, 45, 22.5, '4')); });

Un arbre syntaxique abstrait représente une donnée textuelle sous forme structurelle. Chaque nœud de cet arbre est représenté par la classe `Hoa\Compiler\Llk\TreeNode`. Parmis les méthodes utiles, nous trouvons :

- `getId` pour obtenir l'identifiant du nœud ;
- `getValueToken` pour obtenir le nom du lexème ;
- `getValueValue` pour obtenir la valeur du lexème ;
- `isToken` si le nœud représente un lexème ;
- `getChild($i)` pour obtenir le `$i`-ème enfant d'un nœud ;
- `getChildren` pour obtenir tous les nœuds ;
- `getChildrenNumber` pour connaître le nombre d'enfants ;
- `getParent` pour obtenir le nœud parent ;
- `getData` pour obtenir une référence vers un tableau de données ;
- `accept` pour appliquer un visiteur.

Les visiteurs sont le moyen le plus pratique pour parcourir un AST. En guise d'exemple, nous allons écrire le visiteur `PrettyPrinter` qui va réécrire une donnée JSON avec notre propre convention (espacements, retours à la ligne etc.). Un visiteur doit implémenter l'interface `Hoa\Visitor\Visit` et n'aura qu'une seule méthode à écrire : `visit` qui prend trois arguments : l'élément et deux arguments accessoires (en référence et en copie). Voyons plutôt :

    class PrettyPrinter implements Hoa\Visitor\Visit
    {
        public function visit (
            Hoa\Visitor\Element $element,
            &amp;$handle = null,
            $eldnah = null
        ) {
            static $i      = 0;
            static $indent = '    ';

            // One behaviour per node in the AST.
            switch ($element->getId()) {
                // Object: { … }.
                case '#object':
                    echo '{', "\n";
                    ++$i;

                    foreach ($element->getChildren() as $e => $child) {
                        if (0 &lt; $e) {
                            echo ',', "\n";
                        }

                        echo str_repeat($indent, $i);
                        $child->accept($this, $handle, $eldnah);
                    }

                    echo "\n", str_repeat($indent, --$i), '}';

                    break;

                // Array: [ … ].
                case '#array':
                    echo '[', "\n";
                    ++$i;

                    foreach ($element->getChildren() as $e => $child) {
                        if (0 &lt; $e) {
                            echo ',', "\n";
                        }

                        echo str_repeat($indent, $i);
                        $child->accept($this, $handle, $eldnah);
                    }

                    echo "\n", str_repeat($indent, --$i), ']';

                    break;

                // Pair: "…": ….
                case '#pair':
                    echo
                        $element->getChild(0)->accept($this, $handle, $eldnah),
                        ': ',
                        $element->getChild(1)->accept($this, $handle, $eldnah);

                    break;

                // Many tokens.
                case 'token':
                    switch ($element->getValueToken()) {
                        // String: "…".
                        case 'string':
                            echo '"', $element->getValueValue(), '"';

                            break;

                        // Booleans.
                        case 'true':
                        case 'false':

                        // Null.
                        case 'null':

                        // Number.
                        case 'number':
                            echo $element->getValueValue();

                            break;
                    }

                    break;
            }
        }
    }

Nous allons voir tout de suite un exemple d'utilisation :

    $compiler    = Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp'));
    $ast         = $compiler->parse('{"foo": true, "bar": [null, 42]}');
    $prettyPrint = new PrettyPrinter();
    echo $prettyPrint->visit($ast);

    /**
     * Will output:
     *     {
     *         "foo": true,
     *         "bar": [
     *             null,
     *             42
     *         ]
     *     }
     */

La méthode `getData` est très pratique pour stocker des données susceptibles d'être réutilisées, par exemple d'un visiteur à l'autre. Cette méthode retourne une référence sur un tableau ; ainsi :

    $data = $element->getData();

    if (!isset($data['previousComputing'])) {
        throw new Exception('Need a previous computing.', 0);
    }

    $previous = $data['previousComputing'];

Il est courant d'utiliser un visiteur par contrainte : vérifiation des symboles, vérification de types etc. Certains peuvent laisser des données nécessaires pour le suivant. La méthode `getData` n'impose aucune structuration des données, elle propose uniquement un accès à un tableau. Ce sera à vous de faire le reste.

Utiliser la classe `Hoa\Compiler\Llk\TreeNode` est vraiment trivial et nous l'utiliserons la plupart du temps avec un visiteur.

### Traces

Hoa.Document.onReady(function ( ) {

var paper = Hoa.Graph(Hoa.$('#overview_trace'), 800, 360); var flow = paper.grid(0, 0, 800, 360, 1, 4); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur lexical').attr({opacity: .3})); flow.push(paper.rect(0, 0, 200, 70, 3, 'analyseur syntaxique').attr({opacity: .3})); flow.push(paper.rect(0, 0, 200, 70, 3, 'trace')); flow.push(paper.rect(0, 0, 200, 70, 3, 'AST').attr({opacity: .3}));

var annot = paper.grid(180, 0, 80, 360, 1, 4); annot.push(paper.rect(0, 0, 45, 45, 22.5, '1').attr({opacity: .3})); annot.push(paper.rect(0, 0, 45, 45, 22.5, '2').attr({opacity: .3})); annot.push(paper.rect(0, 0, 45, 45, 22.5, '3')); annot.push(paper.rect(0, 0, 45, 45, 22.5, '4').attr({opacity: .3})); });

Le compilateur LL(k) que propose Hoa est clairement découpé en plusieurs couches. Chaque couche est exploitable pour permettre une modularité maximum. Quand la grammaire est traduite en « règles machines » et que les analyseurs lexical et syntaxique ont validé une donnée, il en résulte une trace. Cette trace est un tableau composé de trois classes seulement :

- `Hoa\Compiler\Llk\Rule\Entry` quand l'analyseur syntaxique est rentré dans une règle ;
- `Hoa\Compiler\Llk\Rule\Ekzit` quand l'analyseur syntaxique est sorti d'une règle ;
- `Hoa\Compiler\Llk\Rule\Token` quand l'analyseur syntaxique a rencontré un lexème.

Nous pouvons l'obtenir grâce à la méthode `Hoa\Compiler\Llk\Parser::getTrace`. Pour bien comprendre cette trace, nous allons commencer par un exemple :

    $compiler = Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp'));
    $ast      = $compiler->parse('{"foo": true, "bar": [null, 42]}');
    $i        = 0;

    foreach ($compiler->getTrace() as $element) {
        if ($element instanceof Hoa\Compiler\Llk\Rule\Entry) {
            echo str_repeat('>   ', ++$i), 'enter ', $element->getRule(), "\n";
        } elseif ($element instanceof Hoa\Compiler\Llk\Rule\Token) {
            echo
                str_repeat('    ', $i + 1), 'token ', $element->getTokenName(),
                ', consumed ', $element->getValue(), "\n";
        } else {
            echo str_repeat('&lt;   ', $i--), 'ekzit ', $element->getRule(), "\n";
        }
    }

    /**
     * Will output:
     *     >   enter value
     *     >   >   enter object
     *                 token brace_, consumed {
     *     >   >   >   enter pair
     *     >   >   >   >   enter string
     *                         token quote_, consumed "
     *                         token string, consumed foo
     *                         token _quote, consumed "
     *     &lt;   &lt;   &lt;   &lt;   ekzit string
     *                     token colon, consumed :
     *     >   >   >   >   enter value
     *                         token true, consumed true
     *     &lt;   &lt;   &lt;   &lt;   ekzit value
     *     &lt;   &lt;   &lt;   ekzit pair
     *     >   >   >   enter 13
     *     >   >   >   >   enter 12
     *                         token comma, consumed ,
     *     >   >   >   >   >   enter pair
     *     >   >   >   >   >   >   enter string
     *                                 token quote_, consumed "
     *                                 token string, consumed bar
     *                                 token _quote, consumed "
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit string
     *                             token colon, consumed :
     *     >   >   >   >   >   >   enter value
     *     >   >   >   >   >   >   >   enter array
     *                                     token bracket_, consumed [
     *     >   >   >   >   >   >   >   >   enter value
     *                                         token null, consumed null
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit value
     *     >   >   >   >   >   >   >   >   enter 21
     *     >   >   >   >   >   >   >   >   >   enter 20
     *                                             token comma, consumed ,
     *     >   >   >   >   >   >   >   >   >   >   enter value
     *                                                 token number, consumed 42
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit value
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit 20
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit 21
     *                                     token _bracket, consumed ]
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit array
     *     &lt;   &lt;   &lt;   &lt;   &lt;   &lt;   ekzit value
     *     &lt;   &lt;   &lt;   &lt;   &lt;   ekzit pair
     *     &lt;   &lt;   &lt;   &lt;   ekzit 12
     *     &lt;   &lt;   &lt;   ekzit 13
     *                 token _brace, consumed }
     *     &lt;   &lt;   ekzit object
     *     &lt;   ekzit value
     */

Cet exemple nous révèle plusieurs choses. Tout d'abord, les informations que nous donne la trace peuvent être utiles : si nous sommes sur une règle, nous avons son nom (avec la méthode `getRule`), et si nous sommes sur un lexème, nous avons son nom (avec la méthode `getTokenName`), sa représentation (sous la forme d'une PCRE, avec la méthode `getRepresentation`), sa valeur (avec la méthode `getValue`), si c'est un nœud à conserver dans l'AST (avec la méthode `isKept`) et son index d'unification s'il existe (avec la méthode `getUnificationIndex`). Bien sûr, tout ceci est modifiable, ce qui peut influencer la construction de l'AST qui est le processus (optionnel) suivant.

Ensuite, nous remarquons que parfois nous entrons dans une règle qui existe dans la grammaire, comme `object`, `pair`, `value` etc., et parfois nous entrons dans une règle numérique, comme `13`, `12`, `21`, `20` etc. Quand la grammaire est interprétée pour être transformée en « règles machines », chacune de ses règles est linéarisée selon les opérateurs du langage PP :

- `Hoa\Compiler\Llk\Rule\Choice` pour une disjonction ;
- `Hoa\Compiler\Llk\Rule\Concatenation` pour une concaténation ;
- `Hoa\Compiler\Llk\Rule\Repetition` pour une répétition ;
- `Hoa\Compiler\Llk\Rule\Token` pour un lexème (déjà vu précédemment).

Toutes les règles sous ce format sont accessibles à travers la méthode `Hoa\Compiler\Llk\Parser::getRules` sous la forme d'un tableau. Nous allons afficher toutes les règles accessibles depuis la règle racine pour mieux comprendre :

    $compiler = Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp'));

    // 1. Get all rules.
    $rules    = $compiler->getRules();

    // 2. Start with the root rule.
    $stack    = [$compiler->getRootRule() => true];

    while (false !== current($stack)) {
        $rule = key($stack);
        next($stack);
        echo
            "\n", '"', $rule, '" is a ',
            strtolower(substr(get_class($rules[$rule]), 22));

        $subrules = $rules[$rule]->getContent();

        // 3a. Token.
        if (null === $subrules) {
            continue;
        }

        echo ' of rules: ';

        // 3b. Other rules.
        foreach ((array) $rules[$rule]->getContent() as $subrule) {
            if (!array_key_exists($subrule, $stack)) {
                // 4. Unshift new rules to print.
                $stack[$subrule] = true;
            }

            echo $subrule, ' ';
        }
    }

    /**
     * Will output:
     *     "value" is a choice of rules: 1 2 3 string object array number
     *     "1" is a token
     *     "2" is a token
     *     "3" is a token
     *     "string" is a concatenation of rules: 5 6 7
     *     "object" is a concatenation of rules: 10 pair 13 14
     *     "array" is a concatenation of rules: 18 value 21 22
     *     "number" is a token
     *     "5" is a token
     *     "6" is a token
     *     "7" is a token
     *     "10" is a token
     *     "pair" is a concatenation of rules: string 16 value
     *     "13" is a repetition of rules: 12
     *     "14" is a token
     *     "18" is a token
     *     "21" is a repetition of rules: 20
     *     "22" is a token
     *     "16" is a token
     *     "12" is a concatenation of rules: 11 pair
     *     "20" is a concatenation of rules: 19 value
     *     "11" is a token
     *     "19" is a token
     */

Si nous lisons la règle `object`, nous savons que c'est la concaténation des règles `10`, `pair`, `13` et `14`. `10` est un lexème, `pair` est la concaténation des règles `string`, `16` et `value`, et ainsi de suite. La grammaire initiale est transformée pour être sous sa forme la plus réduite possible. Ceci permet de raisonner beaucoup plus facilement et rapidement sur les règles. En effet, les traitements sur la grammaire ne sont pas réservés aux AST. À l'étape précédente, avec la trace, nous pouvons déjà effectuer des traitements.

### Génération

Une grammaire peut être utile pour deux objectifs : valider une donnée (si nous la voyons comme un automate) ou générer des données. Jusqu'à présent, nous avons vu comment valider une donnée à travers plusieurs processus : préparation de notre compilateur, exécution des analyseurs lexical et syntaxique résultant sur une trace, transformation de la trace vers un AST pour enfin visiter cet arbre. Mais nous pouvons nous arrêter à la première étape, la préparation de notre compilateur, pour exploiter les règles afin de générer une donnée qui sera valide par rapport à notre grammaire.

`Hoa\Compiler\Llk\Sampler` propose trois algorithmes de générations de données :

- génération aléatoire et uniforme ;
- génération exhaustive bornée ;
- génération basée sur la couverture.

Pourquoi proposer trois algorithmes ? Parce qu'il est illusoire de penser qu'un seul algorithme peut suffir aux nombreux contextes d'utilisations. Chacun répond à des besoins différents, nous l'expliquerons plus loin.

Générer une donnée à partir d'une grammaire se fait en deux étapes :

1. générer des valeurs pour les lexèmes afin d'avoir des données brutes ;
2. générer un chemin dans les règles de la grammaire.

Un chemin est équivalent à une dérivation, le vocabulaire est différent selon notre objectif : validation ou génération.

#### Génération isotropique de lexèmes

Pour générer les valeurs des lexèmes, peu importe l'algorithme utilisé, il doit être rapide. Nous allons utiliser un parcours dit isotrope. Nous partons d'une règle et nous avançons uniquement à partir de choix aléatoires et uniformes localement (uniquement pour ce choix). Par exemple si nous avons une disjonction entre trois sous-règles, nous allons tirer aléatoirement et uniformément entre 1 et 3. Si nous avons une concaténation, nous allons juste concaténer chaque partie. Et enfin, une répétition n'est rien d'autre qu'une disjonction de concaténation : en effet, `e{1,3}` est strictement équivalent à `e | ee | eee`. Illustrons plutôt :

    ([ae]+|[x-z]!){1,3}              repeat [ae]+|[x-z]! 2 times
    → ([ae]+|[x-z]!)([ae]+|[x-z]!)  choose between [ae]+ and [x-z]!
    → ([ae]+)([ae]+|[x-z]!)         repeat ae 2 times
    → [ae][ae]([ae]+|[x-z]!)        choose between a and e
    → e[ae]([ae]+|[x-z]!)           again
    → ea([ae]+|[x-z]!)              choose between [ae]+ and [x-z]!
    → ea([x-z]!)                    choose between x, y and z
    → eay!

Cette génération est naïve mais ce n'est pas important. Ce qui est vraiment important est la génération des chemins dans les règles, ou autrement dit, la génération des séquences de lexèmes.

#### Génération aléatoire et uniforme

Le premier algorithme est celui de la génération aléatoire et uniforme. Cet algorithme est utile si nous voulons générer des séquences de lexèmes de taille n fixe et avec une uniformité non pas locale (comme avec un parcours isotrope) mais sur l'ensemble de toutes les séquences possibles. Succinctement, l'algorithme travaille en deux étapes : pré-calcul (une seule fois par taille) puis génération. Le pré-calcul est une étape automatique et calcule le nombre de séquences et sous-séquences possibles de taille n. Cette étape aide au calcul de fonctions de répartions pour guider la génération des séquences de lexèmes finales.

Nous allons générer 10 données aléatoires de taille 7, c'est à dire composées de 7 lexèmes. Pour cela, nous allons utiliser la classe `Hoa\Compiler\Llk\Sampler\Uniform` qui prend en premier argument notre grammaire, en deuxième le générateur de valeurs de lexèmes (ici `Hoa\Regex\Visitor\Isototropic`) et enfin la taille :

    $sampler = new Hoa\Compiler\Llk\Sampler\Uniform(
        // Grammar.
        Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp')),
        // Token sampler.
        new Hoa\Regex\Visitor\Isotropic(new Hoa\Math\Sampler\Random()),
        // Length.
        7
    );

    for ($i = 0; $i &lt; 10; ++$i) {
        echo $i, ' => ', $sampler->uniform(), "\n";
    }

    /**
     * Will output:
     *     0 => [ false , null , null ]
     *     1 => [ " l " , null ]
     *     2 => [ [ true ] , true ]
     *     3 => [ [ [ 4435 ] ] ]
     *     4 => [ [ [ 9366 ] ] ]
     *     5 => [ true , false , null ]
     *     6 => { " |h&lt;# " : false }
     *     7 => [ [ [ false ] ] ]
     *     8 => [ false , true , 7 ]
     *     9 => [ false , 5 , 79 ]
     */

Nous pouvons redéfinir la taille avec la méthode `Hoa\Compiler\Llk\Sampler\Uniform::setLength`. Nous aurons peut-être remarqué que certains opérateurs de répétition n'ont pas de bornes supérieures, comme `+` ou `*` ; dans ce cas, nous la définissons à n.

#### Génération exhaustive bornée

Le deuxième algorithme est celui de la génération exhaustive bornée. Cet algorithme génère toutes les séquences (c'est le côté exhaustif) de lexèmes de taille 1 jusqu'à n (c'est le caractère borné). Un aspect très intéressant de cet algorithme est que l'exhaustivité est une forme de preuve ! L'algorithme fonctionne comme un itérateur et est très simple à utiliser :

    $sampler = new Hoa\Compiler\Llk\Sampler\BoundedExhaustive(
        // Grammar.
        Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp')),
        // Token sampler.
        new Hoa\Regex\Visitor\Isotropic(new Hoa\Math\Sampler\Random()),
        // Length.
        7
    );

    foreach ($sampler as $i => $data) {
        echo $i, ' => ', $data, "\n";
    }

    /**
     * Will output:
     *     0 => true
     *     1 => false
     *     2 => null
     *     3 => " 8u2 "
     *     4 => { " ,M@aj{ " : true }
     *     5 => { " x`|V " : false }
     *     6 => { " NttB " : null }
     *     7 => { " eJWwA " : 0 }
     *     8 => [ true ]
     *     9 => [ true , true ]
     *     10 => [ true , true , true ]
     *     11 => [ true , true , false ]
     *     12 => [ true , true , null ]
     *     13 => [ true , true , 32 ]
     *     14 => [ true , false ]
     *     15 => [ true , false , true ]
     *     16 => [ true , false , false ]
     *     17 => [ true , false , null ]
     *     18 => [ true , false , 729 ]
     *     19 => [ true , null ]
     *     20 => [ true , null , true ]
     *     21 => [ true , null , false ]
     *     …
     *     157 => [ 780 , 01559 , 32 ]
     *     158 => 344
     */

A l'instar de l'algorithme précédent, nous pouvons redéfinir la borne maximum avec la méthode `Hoa\Compiler\Llk\Sampler\BoundedExhaustive::setLength`. Et les opérateurs de répétition sans borne supérieure utilisent n.

#### Génération basée sur la couverture

Le dernier algorithme est celui de la génération basée sur la couverture. Cet algorithme réduit l'explosion combinatoire rencontrée avec l'algorithme précédent mais l'objectif est tout autre : il va générer des séquences de lexèmes qui vont « activer » toutes les branches des règles de la grammaire. Une règle est dite couverte si et seulement si ses sous-règles sont toutes couvertes, et un lexème est dit couvert s'il a été utilisé dans une séquence. Pour assurer une diversité dans les séquences produites, les points de choix entre des sous-règles non couvertes sont résolus par tirages aléatoires. L'algorithme fonctionne également comme un itérateur :

    $sampler = new Hoa\Compiler\Llk\Sampler\Coverage(
        // Grammar.
        Hoa\Compiler\Llk\Llk::load(new Hoa\File\Read('Json.pp')),
        // Token sampler.
        new Hoa\Regex\Visitor\Isotropic(new Hoa\Math\Sampler\Random())
    );

    foreach ($sampler as $i => $data) {
        echo $i, ' => ', $data, "\n";
    }

    /**
     * Will output:
     *     0 => true
     *     1 => { " )o?bz " : null , " %3W) " : [ false , 130 , " 6 " ] }
     *     2 => [ { " ny  " : true } ]
     *     3 => { " Ne;[3 " : [ true , true ] , " th: " : true , " C[8} " : true }
     */

Pour éviter l'explosion combinatoire et assurer la termination de l'algorithme, nous utilisons l'heuristique suivante sur les opérateurs de répétition : `*` répétera `0`, `1` et `2` fois, `+` répétera `1` et `2` fois, et enfin `{x,y}` répétera `x`, `x + 1`, `y - 1` et `y` fois. Cette heuristique trouve ses origines dans le test aux limites.

Nous remarquons dans notre exemple que 4 données sont générées et suffisent à couvrir l'ensemble de notre grammaire !

#### Comparaison entre les algorithmes

Voici quelques indices pour savoir quand utiliser tel ou tel autre algorithme.

Génération aléatoire et uniforme :
  - rapide pour des petites données, grande diversité dans les données et taille fixe ;
  - la phase de pré-calcul est exponentielle et donc longue bien que, ensuite, la génération soit très rapide.
Génération exhaustive bornée :
  - rapide pour des petites données et l'exhaustivité est efficace ;
  - nombre exponentiel de données.
Génération basée sur la couverture :
  - rapide pour des données de taille moyenne ou grande, et diversité des données ;
  - ne considère pas de taille.

## Compilateur de compilateurs LL(1)

La documentation pour le compilateur LL(1), à travers la classe `Hoa\Compiler\Ll1`, n'est pas encore écrite. L'objectif est différent de `Hoa\Compiler\Llk` : il n'existe pas de langage intermédiaire, les automates sont codés en dur avec des tableaux et ce type de compilateurs est orienté haute performance. C'est pourquoi son comportement est monolithique.

## Conclusion

`Hoa\Compiler` propose deux compilateurs de compilateurs : `Hoa\Compiler\Llk` et `Hoa\Compiler\Ll1`, afin de valider des données. Le langage PP permet d'écrire des grammaires algébriques de manière simple et naturelle. Le compilateur de compilateurs LL(k) est découpé en processus distincts ce qui le rend très hackable. Enfin, différents algorithmes permettent de générer des données à partir d'une grammaire selon le contexte d'utilisation.