Mémento d'utilisation de JFlex
Exécution
Il prend en entrée une spécification JFlex et produit en sortie une classe Yylex.java contenant la méthode d'analyse lexicale yylex() ou next_token().
Si l'on définit une méthode main() dans la spécification JFlex, on pourrait exécuter les commandes suivantes (le conditionnel indique que nous procéderons presqu'exclusivement avec Maven) :
Voici quelques options JFlex utiles :
Option
en ligne de commande |
Option
dans pom.xml Maven |
Explications |
---|---|---|
-h --help | affiche toutes les options | |
-v --verbose | <verbose>true</verbose> | plus verbeux (défaut) |
-q --quiet | moins verbeux | |
--nobak | <backup>true</backup> | pas de sauvegarde pour le fichier Yylex.java généré |
--dump | <dump>true</dump> | affichage textuel des automates générés |
--dot | <dot>true</dot> | écriture des automates générés dans des fichiers .dot pour Graphviz |
Dans le module, ce n'est pas l'archive JAVA que nous utiliserons directement avec des commandes JAVA, mais les greffons Maven de JFlex. Cela explique la présence de la deuxième colonne dans le travaux ci-avant.
Format d'une spécification
Reconnaissance de fragments
Fragments reconnus et Actions
Pour un fragment reconnu par une expression régulière :
- l'action associée à l'expression est exécutée,
- la valeur du fragment est accessible par la méthode String yytext(),
- la méthode void ECHO() imprime la valeur de yytext(),
- si l'action est vide, l'unité lexicale est « absorbée ».
Pour un fragment non reconnu, une exception JAVA est levée.
Reconnaissance gloutonne sans recouvrement
Un fragment débute avec la reconnaissance d'une expression régulière et se prolonge le plus loin possible jusqu'à la reconnaissance d'un autre fragment ou jusqu'à la fin de l'entrée.
La reconnaissance est dite « gloutonne » (greedy) par opposition à d'autres outils permettant une reconnaissance dites « récalcitrante » (reluctant).
Une action n'est réalisée que sur un fragment qui ne peut pas être prolongé.
Ambiguïté, ordre des règles
Si plusieurs règles peuvent s'appliquer :
- l'expression reconnaissant le plus long fragment est choisie,
- en cas d'égalité de longueur, on choisit la règle en premier dans la spécification.
Règle dite « balai »
L'entrée peut contenir des données erronées, ou sans utilité applicative, ou encore incorrectement traitées par la spécification. Il est donc utile d'ajouter des règles pour traiter ce qui n'est pas reconnu par le reste de la spécification.
Une réalisation simple consiste à ajouter en fin de spécification une règle dite « balai ou « ramasse-miettes » qui reconnaît tous les caractères. Elle s'applique sur les fragments non reconnus par les règles précédentes. L'action associée permet de faire du traitement d'erreur, ou de débogage, ou encore un traitement par défaut des caractères inutiles.
Traditionnellement la règle balai s'écrit sous la forme .|\n. Avec JFlex, on a une écriture plus systématique avec [^].
Action vide
L'action notée | indique qu'il faut faire l'action de la règle suivante. On peut aussi le voir comme l'écriture d'une expression régulière sur plusieurs lignes séparées par l'opérateur « OU ».
Expressions régulières JFlex
Échappement : ""
et \
Les caractères suivants peuvent avoir une interprétation réservée dans une spécification JFlex : <>{}()[]|!~*+?"^$/.\,-.
Pour supprimer l'interprétation par JFlex des méta-caractères, on dispose de :
" " |
Chaîne de caractères mono-ligne
Dans une chaîne, seul " et \ sont des caractères réservés |
\ | Échappement du caractère suivant |
Opérateurs fondamentaux, priorités et parenthèses : |
,*
, ()
Les expressions régulières sont définies à partir des opérateurs (ordre croissant de priorité) :
r1 | r2 | Alternative |
r1 r2 | Concaténation |
r * | Fermeture de Kleene |
( r ) | Groupement pour application des opérateurs |
Caractères et Classe de caractères : .
, [ ]
, [^ ]
, [ - ]
Un caractère C dans une expression régulière peut être obtenu par :
C | si C est imprimable et n'est pas un métacaractère JFlex |
\C | si C est imprimable et n'est pas en conflit avec la suite |
\xnm | ASCII avec nm 2 chiffres hexadécimaux ([0-9a-fA-F]) |
\ijk | ASCII avec ijk 3 chiffres octaux ([0-7]) |
\unmop | Unicode avec 4 chiffres hexadécimaux |
\Unmopqr | Unicode avec 6 chiffres hexadécimaux |
\b | ANSI-C Backspace |
\f | ANSI-C Form Feed |
\n | ANSI-C Newline |
\r | ANSI-C Carriage Return |
\t | ANSI-C Horizontal Tabulation |
[ab...] | un caractère parmi a, b,... (== a|b|....) |
[i-p...] | un caractère dans l'intervalle ASCII de i à p ou... |
[^ab...] | un caractère quelconque sauf a,b,... (Complémentaire) |
. |
un caractère quelconque sauf une fin de ligne
== [^\r\n\u2028\u2029\u000B\u000C\u0085] |
[^] | un caractère quelconque == (. | \R) |
\R |
« caractère » de fin de ligne
== \r\n | [\r\n\u2028\u2029\u000B\u000C\u0085] n'est ni un caractère ni une classe, n'est pas utilisable à l’intérieur d'une classe |
Répétitions : *
, +
, ?
, {}
Pour une expression r, on a les « répétitions » de r :
r * | répétition n fois avec n≥0 |
r + | répétition n fois avec n>0 |
r ? | répétition n fois avec n=0 ou n=1 |
r {N} | répétition exactement N fois |
r {N,M} | répétition n fois avec N≤n≤M |
Spécification de contexte : ^
, $
, /
Pour une expression r, on a les reconnaissances :
^ r | reconnaît r uniquement en début de ligne |
r $ | reconnaît r uniquement en fin de ligne |
r / suite | reconnaît r uniquement avant "suite" |
Dans les différents cas, le contexte « \n » ou « suite n'est pas consommé.
Si quelque chose précède ^, ou suit $, les caractères ^ et $ reprennent leur sens littéral.
^r$ est interdit si r peut être vide.
L'usage simultané de / et $ est interdit.
Autres expressions
Les éléments suivants sont plus spécifiques à l'outil JFlex et l'on évitera de les utiliser :
[:jletter:] [:jletterdigit:]
[:letter:] [:digit:] [:uppercase:] [:lowercase:] |
Classes prédéfinies à la POSIX |
\p{...} \P{...} | Classes prédéfinies selon les catégories Unicode |
\d \D \s \S \w \W | Classes POSIX [:digit:], [:space:], [:word:] et complémentaires |
! r ~ r |
Opérateur Négation (Danger explosion combinatoire !)
Opérateur UpTo (Danger explosion combinatoire !) |
[ || ] [ && ] [ -- ] [ ~~ ] |
Opérations ensemblistes sur les classes de caractères :
Union, Intersection, Différence, et Différence symétrique |
Définitions régulières
NOM = RegExp | Définition dans la section « Options et déclarations » |
{NOM} | Utilisation |
Super-états (Bonus)
Les super-états sont déclarées sous la forme %state nom1 nom2... /* Définit des super-états inclusifs */ %xstate nom4 nom5... /* Définit des super-états exclusifs */
Au départ, l'automate est dans le super-état YYINITIAL ou 0.
La méthode yybegin(etat) change le super-état de l'automate.
Une expression régulière préfixée <etat>expr ne s'applique que si l'automate est dans le super-état etat.
Les expressions non préfixées s'appliquent pour l'état YYINITIAL et pour les super-états inclusifs (%state), mais pas pour les super-états exclusifs (%xstate).
Instrumentation du code Java
La classe Yylex
L'outil JFlex produit une classe Yylex.java avec un constructeur Yylex(java.io.Reader in).
L'analyse lexicale sera réalisée par la méthode Yytoken yylex() (ou suivant la configuration : int yylex(), int next_token(), java_cup.runtime.Symbol next_token()).
La méthode yylex() :
- lit les caractères en entrée jusqu'à reconnaître un fragment,
- exécute l'action correspondante,
- continue avec la lecture du prochain fragment, sauf si l'action contient une instruction return.
Ainsi la méthode yylex() ne termine que sur la fin de fichier (EOF), ou sur un return explicite dans une action.
API
Les variables et méthodes suivantes sont utilisables dans les actions :
String yytext() | le dernier fragment reconnu |
int yylength() | yytext().length() |
char yycharat(int pos) | yytext().charAt(pos) |
int yybegin(int state) | change le super-état |
int yystate() | retourne le super-état courant |
static final int YYINITIAL | le super-état initial |
int yyreset(java.io.Reader in) | réinitialise le scanner sur la nouvelle entrée. |
void yypushback(int n) | remet les n derniers caractères lus dans le flot d'entrée (très sale mais parfois utile !) |
long yychar; int yycolumn, yyline | positions courantes dans l'entrée |
String WHERE() | chaîne yyline/yycolumn(yychar) |
void ECHO() | imprime yytext() |
void ECHO(String categorie) | imprime [categorie:yytext()] |
void WARN(String msg) | imprime message d'erreur |
public static void main(String[] args) | main() avec lecture sur l'entrée standard ou sur le fichier args[0] |
java_cup.runtime.Symbol TOKEN(int code) | génère une valeur de retour pour CUP |
java_cup.runtime.Symbol TOKEN(int code, Object value) | idem avec valeur sémantique du token |
Codes utilisateur
Un bloc %{ ... %} permet d'ajouter des variables ou des méthodes à la classe Yylex pour utilisation dans les actions.
Un bloc %init{ ... %init} permet d'ajouter du code exécuté initialement dans le constructeur de Yylex.
Un bloc %eof{ ... %eof} permet d'ajouter du code exécuté à la fin de l'entrée.
CSC4251_4252, Télécom SudParis, Pascal Hennequin, J. Paul Gibson, Denis Conan Last modified: December 2024