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 :
-Wall -Wno-unused-function -Werror -Wextra -pedantic -std=gnu99 -O3
.
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:
a* (aab + bb*a + bb)*
" deviendra ainsi
SYMB(a) STAR LPAR SYMB(a) SYMB(a) SYMB(b) PLUS SYMB(b) SYMB(b) STAR SYMB(a) PLUS SYMB(b) SYMB(b) RPAR STAR
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')) ) ) )
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...)
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
rePLUS
pour l'addition ("+
"),
reSTAR
pour l'étoile ("*
"),
reZERO
pour l'expression vide ("0
"),
reONE
pour le mot vide ("1
"),
reDOT
pour la concaténation (".
").
reSYMB
pour un symbole.
Les autres tokens définis sont :
END
pour la fin du fichier,
NL
pour un saut de ligne,
LPAR
et RPAR
pour les parenthèses, ouvrantes et fermantes.
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 :
END
(qui est généré à la fin du fichier), auquel cas l'action affiche simplement un saut de ligne et quitte le programme,
commande
(au singulier), suivie d'autres commandes
(au pluriel). Dans ce cas, aucune action n'est donnée. (Il y aura par contre des actions associées aux règles de la grammaire commande
.)
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
NL
("new line") seul, auquel cas on affiche simplement un nouveau prompt,
expression
(nouveau symbole non-terminal de la grammaire) suivie d'un token NL
. Dans ce cas, l'action est un peu plus complexe :
print_C_regex
,
print_C_regex
: "$1
" est remplacé par la valeur obtenue par le symbole non-terminal expr
de la règle. Ce symbole est le premier de la règle et s'appelle donc "1
". Le deuxième s'appellerait "$2
", etc.
Il faut donc que les symboles non-terminaux puissent avoir une valeur ! Le type des valeurs associées au symbole non-terminal expr
est donné par la ligne
%type <regex> expr
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 :
$1
")
rePLUS
$3
")
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*
.
Il y a deux manières de lire a+b+c
:
add(symbol('a'),add(symbol('b'),symbol('c')))
add(add(symbol('a'),symbol('b')),symbol('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
a+b*.c
a.b*+c
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.
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 :
QUESTION
pour le caractère "?
" dans le fichier tp3-Roudoudou.l
,
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.
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...
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
ab+c
"
a+bc
"
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 :
expr
continue d'exister et correspond aux regex arbitraires
term
correspond aux regex qui ne commencent pas explicitement par une somme:
ab + cd*
n'est pas reconnu par la grammaire term
a(b+c).d
est reconnu par la grammaire term
, tout comme a(bc*)*
ou (ab + cd*)
, où la somme est cachée par les parenthèses.
atom
correspond aux regex atomiques : 0
, 1
, les symboles, les expressions parenthésées, les étoiles, ...
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
Ajoutez les opérations du TP2 que vous avez écrites :
empty
pour tester si une regex est vide,
infinite
pour tester si une regex a un langage infini,
reverse
pour calculer le "renversé" d'une regex,
prefix
pour calculer la regex des préfixes d'une regex,
suffix
pour calculer la regex des suffixes d'une regex,
subword
pour calculer la regex des sous-mots d'une regex,
Faites la partie bonus de la question 5.
Ajoutez une commande pour afficher un message d'aide.
...