Consignes

Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir une unique archive .tar contenant les deux fichiers tp3-Roudoudou.l et tp3-Roudoudou.y (où Roudoudou est remplacé par votre nom).

Certaines questions appellent à une réponse que vous pouvez mettre en commentaire dans vos fichiers.

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Liens utiles

1. Préliminaires

Définir une valeur de type regex (pour le type défini dans le TP précédent) est verbeux :

   regex test =
      cat(
        star(symbol('a')),
        star(
          plus(
            plus(
              cat(cat(symbol('a'), symbol('a')), symbol('b')),
              cat(symbol('b'), cat(star(symbol('b')), symbol('a')))
                ),
              cat(symbol('b'), symbol('b'))
              )
            )
         );

Tester vos fonction du TP précédents sur des regex non triviales était donc un peu lourd.

L'objectif de ce TP est d'introduire la notion de parseur : c'est un programme qui lit du texte (fichier, chaine de caractères, stdin, ...) et le transforme en valeurs utilisable par votre programme. Il sera ainsi possible de définir la regex précédente simplement avec

    a* (aab + bb*a  + bb)*

Nous allons utiliser Lex et Yacc pour cette tache:

Nous allons en fait utiliser Flex et Bison pour remplacer Lex et Yacc. Les paquets correspondants pour Ubuntu / Debian sont flex et bison...

La plupart des langages possèdent des outils similaires...

Récupérez l'archive du TP ainsi que votre fichier tp2-Roudoudou.c du TP précédent. (Il resservira...)

Modifiez le Makefile et changez le nom des fichiers tp3-Roudoudou.l tp3-Roudoudou.y (sauf si votre nom est Roudoudou) et testez le programme :

$ make
gcc -Wall -Wno-unused-function -Wextra -pedantic -std=gnu99 -O3 -lgc -c utils.c
gcc -Wall -Wno-unused-function -Wextra -pedantic -std=gnu99 -O3 -lgc -c tp2-Roudoudou.c
yacc -d tp3-Roudoudou.y
conflits: 4 décalage/réduction
gcc -Wall -Wno-unused-function -Wextra -pedantic -std=gnu99 -O3 -lgc -c y.tab.c
lex tp3-Roudoudou.l
gcc -Wall -Wno-unused-function -Wextra -pedantic -std=gnu99 -O3 -lgc -c lex.yy.c
gcc -Wall -Wno-unused-function -Wextra -pedantic -std=gnu99 -O3 -lgc main.c utils.o tp2-Roudoudou.o y.tab.o lex.yy.o -o regex:w
$ ./regex
>> a+b.c
add(symbol('a'),cat(symbol('b'),symbol('c')))
>>

(Pour sortir, il suffit d'appuyer sur Control-d...)

2. Grammaire

Tokens

Les tokens (càd les mots) sont définis dans le fichier tp3-roudoudou.l. Comme les nom PLUS, STAR, etc. sont déjà utilisé par le type regex, j'ai utilisé les noms

Les autres tokens définis sont :

Règles de grammaire

Le fichier Yacc tp3-Roudoudou.y contient la grammaire utilisée. La règle initiale est spécifiée avec

%start commandes

"%start" est un mot clé Yacc, et commandes est le symbole non-terminal de la grammaire par lequel on commence.

La fonction (appelée yyparse) essaiera donc de repérer des chaines dans le langage des commandes.

Les grammaires sont données dans une syntaxe à la BNF, mais avec des actions possibles, données entre accolades. Pour les commandes, on commence avec la grammaire

commandes:
        END                 { printf("\n"); exit(0); }
    |   commande commandes

Autrement dit, une commande est soit :

Le symbole non-terminal commande est défini par la grammaire

commande:
        NL                  { printf(">> ");  }
     |  expr NL             { print_C_regex($1);
                              printf("\n");
                              printf(">> "); }

Une commande est générée par

Les symboles non-terminaux commandes et commande n'avaient pas de valeur. Le symbole non-terminal expr en a. La valeur associée à une règle est donnée en définissant la "variable" "$$". Ainsi, la règle

expr:
     ...
     |   expr rePLUS expr    { $$ = plus($1, $3); }
     ...

signifie qu'une expression peut être générée par :

L'expression résultat sera obtenu en calculant le résultat de la fonction plus appliquée aux deux expressions $1 et $3.

La grammaire fournie (dans le fichier tp3-Roudoudou.y) ne reconnait pas les regex contenant des étoiles:

>> a+b.c*
syntax error

Ajoutez cette possibilité en ajoutant une règle dans la grammaire des expressions pour accepter le token reSTAR.

N'oubliez pas de vérifier que cela fonctionne, en testant par exemple la regex a+b.c*.

3. Associativité, priorités ...

Il y a deux manières de lire a+b+c:

La grammaire donnée est donc ambigue. Yacc le détecte car il affiche un message conflits: 4 décalage/réduction lors de la compilation.

Il est possible de préciser laquelle des deux possibilités on souhaite utiliser en déclarant le token rePLUS (correspondant au symbole "+") avec

%left rePLUS

ou

%right rePLUS

dans le fichier tp3-Roudoudou.y, par exemple après les déclarations des tokens

%token reZero ...
%token ...

Testez ce qui se passe avec la ligne

%right rePLUS reDOT

puis avec la ligne

%left rePLUS reDOT

Comment sont interprétées les expressions

Pour gérer les priorités des opérations +, . et *, il est possible de donner plusieurs lignes %left ou %right. La priorité d'une opération dépendra de son ordre d'apparition. Par exemple,

%left rePLUS
%left reDOT

Mettez les contraintes d'associativités sur plusieurs lignes (et ajoutez en une pour l'étoile) pour gérer les priorité. N'oubliez pas de tester pour vérifier que vous n'avez pas mis les priorités à l'envers.

4. Synomymes

Ajoutez un token pour le symbole "?" et une règle pour pouvoir interpréter le point d'interrogation comme dans les regex POSIX.

Pour ajouter un nouveau token QUESTION, il faut :

  1. renvoyez QUESTION pour le caractère "?" dans le fichier tp3-Roudoudou.l,
  2. déclarez le token QUESTION dans le fichier tp3-Roudoudou.y avec le mot clé "%token".

Bonus, (à faire en fin de TP) ajoutez des tokens et des règles pour pouvoir interpréter la syntaxe POSIX "R{13,27}" pour "au moins 13 répétitions, au plus 27 répétitions". Attention, il faudra ajouter des tokens pour lire des nombres entiers et écrire des fonctions auxiliaires.

5. Nouvelles commandes

Remplacez l'appel à la fonction print_C_regex par un appel à la fonction print_regex afin d'avoir un affichage plus "user friendly".

Ajoutez ensuite une règle aux expressions pour appeler la dérivé:

$ ./regex
>> (a.b.c)* / "ab"
c(abc)*

Il faudra pour ceci ajouter un token pour le symbole "/".

Le token STR est déjà défini et renvoie une valeur de type char*. Par contre, la chaine donnée contient les deux guillemets !

Pour le moment, la seule commande possible est d'afficher une expression.

Ajoutez un token IN et une règle dans la grammaire des commande pour permettre de tester l'appartenance d'une chaine au langage d'une regex.

$ ./regex
>> "abc" IN a.b*.c
TRUE
>> "abc" IN c.b*.a
FALSE

Si vous n'avez pas programmé la fonction match, ou si votre fonction derivative du TP2 ne fonctionne pas, vous pouvez ajouter une commande pour tester la fonction contains_epsilon à la place...

6. Concaténation implicite

Ajoutez une règle pour permettre la concaténation implicite: on pourra ainsi écrire "abc" au lieu de "a.b.c".

Que pensez-vous des expressions

Comme la concaténation implicite ne correspond a aucun token, on ne peut pas déclarer de priorité ! Il va donc falloir désambiguer la grammaire à la main.

Modifiez la grammaire en ajoutant des catégories :

Votre grammaire pour les expressions se décomposera donc en 3:


expr:
    ...
  | ...
  | ...

term:
    ...
  | ...
  | ...

atom:
    ...
  | ...
  | ...

Vérifiez que votre grammaire fonctionne correctement:

$ ./regex
>> a+bc
a + bc
>> ab+c
ab + c
>> abc*
abc*
>> a.b*c+d
ab*c + d
>> (ab + c)*d+e
(ab + c)*d + e

7. Pour finir (Bonus)

Ajoutez les opérations du TP2 que vous avez écrites :

Faites la partie bonus de la question 5.

Ajoutez une commande pour afficher un message d'aide.

...