TP3 -- L3 -- Langage et Compilation

Analyse lexicale et syntaxique avec ANTLR

Sommaire

  1. Analyse lexicale avec ANTLR
  2. Isolation de jetons dans un code source Java
  3. Coloration d'un code source Java

Analyse lexicale avec 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 .

Choisir un éditeur correct

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

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 :

et les opérateurs :
concaténation rien
alternative |
parenthésage ( et )
multiplicateur 0 ou 1 fois ?
multiplicateur 1 ou n fois +
multiplicateur 0 ou n fois *
négation ~

Isolation de jetons dans un code source Java

On se propose d'écrire un analyseur capable de reconnaître des jetons dans un source Java.

Définition de l'analyseur lexical

Nous allons nous limiter, dans un premier temps, à reconnaître quelques opérateurs, quelques mots clés, des identificateurs, des espaces, fins de ligne et tabulation.

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
    : . 
    ;

Génération de l'analyseur lexical

On peut maintenant générer le code Java de l'analyseur lexical avec ANTLR et créer le fichier 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

 $ 
En résumé vous devez :

Exécution

On peut maintenant exécuter le programme en prenant comme test le source du programme principal lui-même :
 $ 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

...

 $ 

Coloration d'un code source 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 ]);
        }
    }
}
 $ 
Modifiez votre programme qui recopie l'entrée en encadrant les mots clés et les identificateurs reconnus de «[motclé :» et «]» et de «[ident :» et «]».
Ne pas oublier les instructions qui recopient sur la sortie les caractères non reconnus.

Les nombres

Nous avons besoin maintenant d'expressions régulières plus complexes. On peut utiliser :
Complétez votre programme pour reconnaître les nombres.

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')+ ;

Les commentaires

Pour reconnaître les lignes commentées par 2 barres obliques, il peut être utile d'utiliser :
Traitez les commentaires introduits par //. Pensez aux fichiers venant de DOS, Windows ou Mac.
Ajoutez la reconnaissance des commentaires multilignes entre /* et */ à votre analyseur.
Avez-vous profité de l'avidité de l'analyseur ? Donnez un exemple.
Générer un fichier HTML qui présente le texte du programme Java dont les mot clés, les identifiants, les commentaires, les nombres et les opérateurs soient rendus en couleur, graisse ou fonte différentes. Faire un programme qui soit général pour tout code Java syntaxiquement correct et non pour le seul exemple MainJetonsJava.java.