ANTLR
ANTLR
ANTLR
ANTLR (prononcez « anteuleur ») veut dire « ANother Tool for Language Recognition ». Beaucoup de documentation se trouve ici http://www.antlr.org/ ou là https://github.com/antlr/antlr4/blob/master/doc/getting-started.md .
Pour VS Codium (version libre de VS Code installée sur les PC de la fac). Télécharger le plugin sur le marketplace de VS Code (Version History ; download extension) : ici. Dans VS Codium, dans la fenêtre des extensions, cliquer sur les 3 petits points, puis sur « Installer à partir de VSIX… ». Ouvrir l’extension que vous venez de télécharger. Procéder à son installation.
Vous pouvez utiliser emacs en ajoutant dans son fichier de configuration (le
fichier ~/.emacs
)
le code suivant.
(add-to-list 'auto-mode-alist '("\\.g4\\'" . antlr-mode))
Pour ceux qui n'ont pas besoin de M-x Doctor, on peut configurer Vim
en écrivant le code ci-dessous dans .vimrc
:
au BufRead,BufNewFile *.g4 set filetype=antlr4
D'autres alternatives sont décrites là
Nous allons utiliser, dans ce TP, tout d'abord la partie construction d'analyseur lexical d'ANTLR. Elle permet de découper l'entrée en sous-chaînes (que l'on appellera jetons) : mots-clés, identifiant, opérateur, ponctuation, ... ANTLR permet la définition des jetons en BNF. Nous utiliserons ici cette notation pour exprimer des expressions régulières. ANTLR utilisera ces définitions pour générer le code d'automates finis reconnaissant ces jetons.
Il faut commencer le fichier de définition de la grammaire par la déclaration suivante définissant le nom de la grammaire :
lexer grammar JetonsJava;
Le fichier ANTLR d'une grammaire a par tradition l'extension .g4
.
Le fichier portera obligatoirement le nom de la grammaire, soit ici : JetonsJava.g4
.
On peut mettre ensuite des définitions de jetons. Le jeton sera toujours en majuscules (ce n'est pas seulement une convention c'est une obligation !), suivi de deux points et de sa définition en BNF terminée par un point-virgule. Dans un premier temps, nous nous limiterons à des définitions littérales exprimées par des règles de la forme :
identifiant : expression-régulière { instructions-java } ;
Pour écrire une expression régulière, on peut spécifier :
..
» ou entre crochets comme par
ex. pour un chiffre décimal :
'0'..'9'
ou [0-9]
.concaténation | rien |
alternative | | |
parenthésage | ( et ) |
multiplicateur 0 ou 1 fois | ? |
multiplicateur 1 ou n fois | + |
multiplicateur 0 ou n fois | * |
négation | ~ |
On se propose d'écrire un analyseur capable de reconnaître des jetons dans un source Java.
Opérateurs et mots clés ne sont que des littéraux
OPERATEUR : '<'|'<='|'>'|'>='|'=='|'!=' ; MOTCLE : 'break' | 'class' | 'double' | 'else' | 'if' | 'import' | 'public' | 'static' | 'throws' ;
Un identificateur est composé de lettres de chiffres et du souligné en nombre quelconque et commence par une lettre ou un souligné.
IDENTIFIANT : ('a'..'z' | 'A'..'Z' | '_')('a'..'z' | 'A'..'Z' | '_' | '0'..'9')* ;
Les espaces au sens large :
WHITE_SPACE : (' '|'\n'|'\t'|'\r')+ ;
Afin de garantir d'analyser tout, nous ajoutons une règle correspondant au reste :
UNMATCH : . ;
Cela donne pour le fichier JetonsJava.g4
:
lexer grammar JetonsJava; OPERATEUR : '<'|'<='|'>'|'>='|'=='|'!=' ; MOTCLE : 'break' | 'class' | 'double' | 'else' | 'if' | 'import' | 'public' | 'static' | 'throws' ; IDENTIFIANT : ('a'..'z' | 'A'..'Z' | '_')('a'..'z' | 'A'..'Z' | '_' | '0'..'9')* ; WHITE_SPACE : (' '|'\n'|'\t'|'\r')+ ; UNMATCH : . ;
JetonsJava.java
qui contient la définition
en Java de la classe JetonsJavaLexer
. Il faudra au préalable
s'assurer d'avoir le jar antlr version 4 dans son CLASSPATH
.
$ export CLASSPATH=".:/usr/share/java/*:$CLASSPATH"Afin de tester la bonne installation et vérifier la version de ANTLR :
$ java org.antlr.v4.Tool ANTLR Parser Generator Version 4.4 - ...ANTLR va en plus du fichier
JetonsJava.java
générer
JetonsJava.tokens
dont on verra l'utilisation ci-après :
$ java org.antlr.v4.Tool JetonsJava.g4 $ ls JetonsJava.g4 JetonsJava.interp JetonsJava.java JetonsJava.tokens $Le fichier
JetonsJava.java
contient la définition de la classe
JetonsJavaLexer
.
On le compile; pour l'exécuter on peut utiliser la classe
TestRig
prédéfinie dans ANTLR :
$ javac JetonsJava.java $ java org.antlr.v4.runtime.misc.TestRig JetonsJava 'tokens' -tokens public class ColorLexer cour == 1 (Contrôle-D) [@0,0:5='public',<2>,1:0] [@1,6:6=' ',<4>,1:6] [@2,7:11='class',<2>,1:7] [@3,12:12=' ',<4>,1:12] [@4,13:22='ColorLexer',<3>,1:13] [@5,23:23='\n',<4>,1:23] [@6,24:27='cour',<3>,2:0] [@7,28:28=' ',<4>,2:4] [@8,29:30='==',<1>,2:5] [@9,31:31=' ',<4>,2:7] [@10,32:32='1',<5>,2:8] [@11,33:33='\n',<4>,2:9] [@12,34:33='<EOF>',<-1>,3:10]
On peut également définir son propre programme muni d'une méthode main
qui crée une instance « lexer » avec un flux d'entrée et appeler simplement la méthode
nextToken
qui rend le jeton suivant jusqu'à la fin du flux
d'entrée qui nous est signalée par un jeton de type Token.EOF
.
import java.io.*; import org.antlr.v4.runtime.*; public class MainJetonsJava { public static void main(String args[]) throws Exception { ANTLRInputStream inputStream = args.length == 0 ? new ANTLRInputStream(System.in) : new ANTLRFileStream(args[0]); JetonsJava lex = new JetonsJava(inputStream); while(true) { Token token = lex.nextToken(); if (token.getType() == Token.EOF) break; System.out.println(token); } } }Il faut maintenant avant d'exécuter le programme, le compiler. On a encore besoin du jar antlr version 4 dans son
CLASSPATH
.
$ javac JetonsJava.java MainJetonsJava.java $
CLASSPATH
JetonsJava.g4
JetonsJava.g4
MainJetonsJava.java
javac
$ java MainJetonsJava < MainJetonsJava.java [@-1,0:5='import',<2>,1:0] [@-1,6:6=' ',<4>,1:6] [@-1,7:10='java',<3>,1:7] [@-1,11:11='.',<5>,1:11] [@-1,12:13='io',<3>,1:12] [@-1,14:14='.',<5>,1:14] [@-1,15:15='*',<5>,1:15] [@-1,16:16=';',<5>,1:16] [@-1,17:17='\n',<4>,1:17] [@-1,18:23='import',<2>,2:0] [@-1,24:24=' ',<4>,2:6] [@-1,25:27='org',<3>,2:7] [@-1,28:28='.',<5>,2:10] [@-1,29:33='antlr',<3>,2:11] [@-1,34:34='.',<5>,2:16] [@-1,35:36='v4',<3>,2:17] [@-1,37:37='.',<5>,2:19] [@-1,38:44='runtime',<3>,2:20] [@-1,45:45='.',<5>,2:27] [@-1,46:46='*',<5>,2:28] [@-1,47:47=';',<5>,2:29] [@-1,48:49='\n\n',<4>,2:30] ...
À chaque jeton correspond une chaîne de caractères
entre crochets qui commence toujours par le numéro du canal
@-1,
. Suivent ensuite les indices du premier caractère du
jeton et de son dernier caractère dans le flot d'entrée, puis le texte du
jeton et le numéro du type du jeton entre crochets, et enfin le numéro
de ligne et de colonne du premier caractère du jeton.
Les numéros de types de jeton sont dans le fichier
JetonsJava.token
.
En résumé pour faire un test après avoir changé le fichier
JetonsJava.g4
, il suffit de lancer la ligne de commande qui
enchaîne les 3 étapes :
$ java org.antlr.v4.Tool JetonsJava.g4 && javac JetonsJava.java MainJetonsJava.java && java MainJetonsJava <MainJetonsJava.java ... $
On veut identifier certaines parties du texte d'un programme Java (les jetons ?) afin de pouvoir les colorer à la manière d'un éditeur de texte intelligent. Nous allons, dans un premier temps, en complétant les règles avec du code, écrire le type de certains jetons dans le flot de sortie et mettre des crochets autour des séquences reconnues. On peut faire par exemple (notez bien l'utilisation des parenthèses) :
MOTCLE : ( 'break' | 'class' | 'double' | 'else' | 'if' | 'import' | 'public' | 'static' | 'throws' ) { System.out.print("[motclé : "+getText()+" ]"); } ; ;
On peut alors obtenir :
$ java org.antlr.v4.Tool JetonsJava.g4 && javac JetonsJava.java MainJetonsJava.java && java MainJetonsJava <MainJetonsJava.java [motclé : import ] [ident : java ].[ident : io ].*; [motclé : import ] [ident : org ].[ident : antlr ].[ident : runtime ].*; [motclé : public ] [motclé : class ] [ident : MainJetonsJava ] { [motclé : public ] [motclé : static ] [ident : void ] [ident : main ]([ident : String ] [ident : args ][]) [motclé : throws ] [ident : Exception ] { [ident : JetonsJava ] [ident : lex ] = [ident : new ] [ident : JetonsJava ]([ident : new ] [ident : ANTLRInputStream ]([ident : System ].[ident : in ])); [ident : while ]([ident : true ]) { [ident : Token ] [ident : token ] = [ident : lex ].[ident : nextToken ](); [motclé : if ] ([ident : token ].[ident : getType ]() == [ident : Token ].[ident : EOF_TOKEN ].[ident : getType ]()) [motclé : break ]; [ident : System ].[ident : err ].[ident : println ]([ident : token ]); } } } $
[motclé :
» et «]
»
et de «[ident :
» et «]
».
..
» pour exprimer des intervalles comme par ex. :
'0'..'9'
.Indication : Pour les nombres vous pouvez utiliser des fragments. Un fragment ainsi défini pourra être repris en partie droite d'une autre règle. Son nom comme celui d'un jeton doit être en majuscules. Par ex. :
fragment EXPOSANT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
'\n'
et '\r'
pour fin de ligne et retour
chariot ;~
pour la négation, par exemple
~('\n')*
pour n'importe quel caractère jusqu'à la fin de ligne.//
. Pensez aux fichiers venant de DOS, Windows ou Mac.
/*
et */
à votre
analyseur.MainJetonsJava.java
.