Analyse lexicale
La première étape est l’analyse lexicale qui va permettre de découper un flot de caractères en mots. Ces mots sont appelés unités lexicales. On obtient alors une information
plus structurée où les mots clés du langage, les identifiants, les entiers et les booléens ont été identifiés. Cette phase va aussi nous permettre de supprimer les commentaires et
les blancs (espaces et retours à la ligne).
Pour le programme ci-dessus, l’analyse lexicale va produire le flot d’unités lexicales suivantes. On peut voir, par exemple, que le mot clé CLASS
a été identifié, que la constante entière INT_CONST 35
aussi.
CLASS
IDENT ‘Print42‘
LBRACE
PUBLIC
STATIC
VOID
MAIN
LPAREN
STRING
LBRACKET
RBRACKET
IDENT ‘a‘
RPAREN
LBRACE
SYSO
LPAREN
INT_CONST ‘35‘
PLUS
INT_CONST ‘2‘
TIMES
INT_CONST ‘3‘
PLUS
INT_CONST ‘1‘
RPAREN
SEMICOLON
RBRACE
RBRACE
EOF
Dans la vidéo suivante, nous allons donner une vue d’ensemble du transpileur, et nous présenterons l’analyseur lexical de MiniJava.
Analyse syntaxique
La deuxième étape, l’analyse syntaxique, prend en entrée le flot d’unités lexicales et va construire un arbre syntaxique abstrait permettant de représenter la structure du
programme sous la forme d’un arbre.
Pour le programme ci-dessus, on obtient l’arbre suivant. On peut y voir, par exemple, l’expression arithmétique avec de manière explicite la priorité
des opérateurs (plus c’est bas dans l’arbre et plus c’est prioritaire).
program
├name Print42
├main_args a
└main
└ISyso
└EBinOp OpAdd
├EBinOp OpAdd
│ ├EConst (ConstInt 35)
│ └EBinOp OpMul
│ ├EConst (ConstInt 2)
│ └EConst (ConstInt 3)
└EConst (ConstInt 1)
L’analyse syntaxique a besoin de connaître la structure d’un programme MiniJava.
Cette structure est donnée sous la forme d’une grammaire.
La grammaire de MiniJava, dans sa forme EBNF
est donnée ci-dessous. Une version sûrement plus lisible pour nous en diagramme syntaxique est donnée
ici.
Program = MainClass { ClassDeclaration } 'eof' ;
MainClass = 'class' Identifier '{' 'public' 'static' 'void' 'main' '(' 'String' '[' ']' Identifier ')' '{' Statement '}' '}' ;
ClassDeclaration = 'class' Identifier [ 'extends' Identifier ] '{' { VarDeclaration } { MethodDeclaration } '}' ;
VarDeclaration = Type Identifier ';' ;
MethodDeclaration = 'public' Type Identifier '(' [ Type Identifier { ',' Type Identifier } ] ')' '{' { VarDeclaration } { Statement } 'return' Expression ';' '}' ;
Type = 'int' '[' ']'
| 'boolean'
| 'int'
| Identifier ;
Statement = '{' { Statement } '}'
| 'if' '(' Expression ')' Statement 'else' Statement
| 'while' '(' Expression ')' Statement
| 'System.out.println' '(' Expression ')' ';'
| Identifier '=' Expression ';'
| Identifier '[' Expression ']' '=' Expression ';' ;
Expression = Expression ( '&&' | '<' | '+' | '-' | '*' ) Expression
| Expression '[' Expression ']'
| Expression '.' 'length'
| Expression '.' Identifier '(' [Expression {',' Expression}] ')'
| Integer
| 'true'
| 'false'
| Identifier
| 'this'
| 'new' 'int' '[' Expression ']'
| 'new' Identifier '(' ')'
| '!' Expression
| '(' Expression ')' ;
Letter = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G'
| 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N'
| 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U'
| 'V' | 'W' | 'X' | 'Y' | 'Z' | 'a' | 'b'
| 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i'
| 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p'
| 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w'
| 'x' | 'y' | 'z' ;
Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ;
Integer = Digit { Digit } ;
Character = Letter | Digit | '_' ;
Identifier = Letter { Letter | Digit | '_' } ;
La vidéo suivante va présenter l’analyseur syntaxique de MiniJava.
Typage
La troisième étape, l’analyse de types, va prendre en entrée l’arbre abstrait et va vérifier si le typage est correct. Par exemple, on va vérifier qu’on utilise les méthodes
avec le bon nombre de paramètres, que les opérateurs +
et *
sont utilisés avec des opérandes entières, qu’une classe est compatible avec une autre via la relation d’héritage, …
La vidéo suivante va présenter une vue d’ensemble du typage dans MiniJava.
Génération de code
La dernière étape va consister à générer le code en C. On va de nouveau parcourir l’arbre syntaxique abstrait pour générer ce code.
Pour notre example, on obtient le fichier C ci-dessous.
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%d\n", ((35 + (2 * 3)) + 1));
return 0;
}
La vidéo suivante va décrire succinctement la génération de code C dans MiniJava.
Sémantique de MiniJava
La sémantique de MiniJava est donnée par sa sémantique en tant que programme Java . Les principales restrictions sont
- Les classes n’héritent pas de la classe
Object
.
- Le mot clé
super
n’existe pas.
- Il y a simplement un constructeur par défaut.
- Les seuls types autorisés sont,
int
.
boolean
.
int[]
.
- Les classes définies par l’utilisateur.
- La surcharge d’opérateurs n’est pas autorisée.
- L’instruction
System.out.printl()
ne peut imprimer que des entiers.
- Toutes les méthodes doivent retourner une valeur.
- Il n’y a pas d’interface, d’exception, de généricité, de lambda.
Nous expliquons dans la vidéo suivante les principales différences entre MiniJava et Java.
Nous avons décrit rapidement l’initialisation par défaut en Java (et MiniJava) dans la vidéo. Nous apportons quelques précisions ci-dessous.
Vous pouvez regarder ici pour tous les détails.
Soit le programme Java Init.java
suivant.
1class Init {
2 public static void main(String[] args) {
3 int i;
4 boolean b;
5 int[] a;
6 System.out.println(i + " " + b + " " + a);
7 }
8}
Si on le compile, on obtient les messages d’erreur suivants.
$ javac Init.java
Init.java:6: error: variable i might not have been initialized
System.out.println(i + " " + b + " " + a);
^
Init.java:6: error: variable b might not have been initialized
System.out.println(i + " " + b + " " + a);
^
Init.java:6: error: variable a might not have been initialized
System.out.println(i + " " + b + " " + a);
^
3 errors
En effet, les trois variables i
, b
et a
sont situées sur la pile, et leurs valeurs
vont dépendre des valeurs qui sont situées sur la pile lors de l’appel du main
. Java n’initialisant
pas les variables locales avec une valeur par défaut, le compilateur refuse le programme en nous indiquant
que l’on cherche à utiliser des variables non initialisées.
Par contre, le programme suivant est correct car les attributs ont des valeurs par défaut.
1class Init {
2 public static void main(String[] args) {
3 new Default().print();
4 }
5}
6
7class Default {
8 private int i;
9 private boolean b;
10 private int[] a;
11
12 public void print() {
13 System.out.println(i + " " + b + " " + a);
14 }
15}
Et on obtient la sortie suivante.
$ javac Init.java
$ java Init
0 false null
Rappels sur la liaison dynamique en Java
Nous allons faire quelques rappels sur la liaison dynamique en Java dans la vidéo ci-dessous, car la gestion
de la liaison dynamique sera un des points difficiles à gérer dans notre transpileur.
Questions
Reprenons la grammaire de MiniJava.
Nous voudrions ajouter la possibilité d’avoir
- L’opérateur de comparaison
==
.
- Des constructeurs.
- Des constructeurs et méthodes
private
.
Quelles sont les modifications à apporter à cette grammaire pour incorporer ces nouveaux éléments ?
Dans la question précédente, quels terminaux avez-vous dû ajouter à la grammaire (un terminal dans la grammaire deviendra une unité lexicale) ?
Soit le programme Java suivant.
1class A {
2 public int m1(int n) {
3 System.out.println("int A:m1(int n)");
4 return 0;
5 }
6}
7
8class B extends A {
9 public boolean m1(int n) {
10 System.out.println("boolean B:m1(int n)");
11 return false;
12 }
13}
Ce code compile-t-il ?
Ce code ne compile pas. En effet, à la ligne 9, la méthode m1
est une redéfinition de la
méthode m1
de la ligne 2 : elle à le même nom et les mêmes paramètres. Par contre, pour être
une redéfinition correcte, il aurait fallu que le type de retour boolean
soit compatible avec
le type de retour int
, mais ce n’est pas le cas.
Soit le programme Java suivant.
1class A {
2 public int m1(int n) {
3 System.out.println("int A:m1(int n)");
4 return 0;
5 }
6 public boolean m1(int n) {
7 System.out.println("boolean A:m1(int n)");
8 return false;
9 }
10}
Ce code compile-t-il ?
Ce code ne compile pas. En effet, le type de retour ne permet pas de différentier deux méthodes. Donc
même si les méthodes aux lignes 2 et 6 ont des types de retour différents, comme elles se nomment pareil et ont les
mêmes paramètres, on a pas une surcharge et il y a donc une erreur.
Soit le programme Java suivant.
1class A {
2 public int m1(A a) {
3 System.out.println("int A:m1(A a)");
4 return 0;
5 }
6 public boolean m1(B b) {
7 System.out.println("boolean A:m1(B b)");
8 return false;
9 }
10}
11
12class B extends A {
13}
Ce code compile-t-il ?
Ce code compile. Cette fois-ci, la méthode à la ligne 6 est bien une surcharge de la méthode
à la ligne 2 car le paramètre est d’un type différent.
Ressources