1.
NF11
-‐
Génération
d'analyseur
lexical
et
syntaxique
Fayçal
Laatef
&
Friscira
Elsa
Groupe
p014
Objectifs
• Créer
une
grammaire
du
langage
Logo
et
une
représentation
intermédiaire
d’un
programme
Logo.
• Générer
un
analyseur
syntaxique
de
cette
grammaire.
• Créer
un
interpréteur
graphique
du
langage
Logo.
• Crée
une
table
des
symboles
d’un
programme.
• Vérifier
quelques
conditions
sémantiques.
1
2.
Table
des
matières
OBJECTIFS
............................................................................................................................................................
1
I.
INTRODUCTION,
QU'EST
CE
QUE
LOGO?
.............................................................................................
3
II.
STRUCTURE
DE
NOTRE
PROJET
...........................................................................................................
3
III.
LEXER,
PARSER
ET
INTERPRETEUR
DU
LANGAGE
LOGO
...........................................................
4
A.
INTRODUCTION
................................................................................................................................................
4
C.
ANALYSE
SYNTAXIQUE:
LE
PARSER
(LOGO.G)
................................................................................................
5
D.
ANALYSE
SEMANTIQUE:
TREE
WALKER
(LOGOTREE.G)
...............................................................................
6
E.
EN
RESUME,
COMMENT
AJOUTER
UNE
FONCTIONNALITE?
.............................................................................
7
E.
QUELLES
FONCTIONNALITES
AVONS-‐NOUS
AJOUTEES?
..................................................................................
7
PRINCIPALES
COMMANDES
DE
LA
TORTUE
..........................................................................................................................
7
EXPRESSIONS
ARITHMETIQUES
.............................................................................................................................................
8
EXPRESSIONS
BOOLEENNES
...................................................................................................................................................
9
IDENTIFICATEURS,
AFFECTATION
.........................................................................................................................................
9
IV.
STRUCTURES
DE
CONTROLE
.............................................................................................................
12
EVALUATION
DIFFEREE
D'UNE
ARBORESCENCE
..............................................................................................................
12
LA
REPETITION
......................................................................................................................................................................
13
L'ALTERNATIVE
(SI)
.............................................................................................................................................................
14
V.
PROCEDURES
ET
FONCTIONS
.............................................................................................................
15
PROCEDURES
..........................................................................................................................................................................
15
REMARQUES:
.....................................................................................................................................................
17
VI.
ELEMENTS
NON
TRAITES
ET
DIFFICULTES
RENCONTREES
....................................................
18
..........................................................................................................................................
18
VII.
CONCLUSION
2
3.
I.
Introduction,
qu'est
ce
que
Logo?
Le
langage
Logo
est
un
langage
de
programmation
qui
recouvre
deux
concepts:
• un
mode
d’apprentissage
inspiré
des
travaux
de
Jean
Piaget
sur
le
développement
cognitif
de
l’enfant
• un
type
d’environnement
informatique,
à
partir
d'un
langage
adapté
du
LISP
Le
langage
Logo
est
connu
pour
sa
simplicité
mais
est
également
capable
de
manipuler
des
expressions
arithmétiques,
des
expressions
booléennes
ou
de
mettre
en
place
des
structures
de
contrôles
telles
que
les
conditions,
les
boucles,
les
procédures
et
les
fonctions.
Le
but
de
ce
projet
est
de
créer
un
analyseur
lexical
et
syntaxique
du
langage
Logo.
Pour
cela,
nous
avons
utilisé
AntLR
(Another
Tool
for
Language
Recognition).
AntLR
est
un
outil
de
génération
automatique
d'analyseur
permettant
de
reconnaître
les
phrases
d'un
programme
écrit
dans
un
langage
donné.
II.
Structure
de
notre
projet
3
4.
L'interface
graphique,
réalisée
par
Claude
Moulin,
nous
permet
d'observer
et
de
valider
le
travail
de
développement
des
grammaires
que
nous
réalisons.
L'objectif
du
projet
est
de
réaliser
une
grammaire
du
langage
LOGO
afin
de
pouvoir
l'analyser.
Il
y
a
donc
trois
types
d'analyses
à
réaliser:
1. l'analyse
lexicale
2. l'analyse
syntaxique
3. l'analyse
sémantique
(traitement
à
partir
de
l'arbre
AST)
Remarque:
AntLR
utilise
une
analyse
descendante
de
type
LL.
III.
Lexer,
Parser
et
interpréteur
du
langage
Logo
a.
Introduction
Pour
réaliser
les
analyses
lexicales
et
syntaxiques
du
langage
LOGO,
nous
avons
spécifié
une
grammaire
(Lexer,Parser)
qui
décrit
la
façon
de
découper
un
flux
de
caractères
en
un
flux
de
mots
du
langage
(tokens)
puis
d'analyser
le
flux
sortant.
Avec
AntLR,
nous
avons
créé
un
fichier
"nomFichier.g"
définissant
les
unités
lexicales
et
syntaxiques
de
la
grammaire.
AntLR
a
ensuite
créer
lui
même
un
fichier
annexe
contenant
la
grammaire
du
lexer.
Enfin,
il
a
généré
les
classes
correpondant
au
lexer
et
au
parser.
4
5.
b.
Analyse
lexicale:
le
lexer
(Logo.g)
L'analyseur
lexical
(ou
lexer)
est
l'outil
qui
permet
de
découper
un
flux
de
caractères
en
un
flux
de
mots
du
langage:
les
tokens.
Flux
de
caractères
=>
Flux
de
tokens
Ajout
des
tokens
au
lexer
dans
la
grammaire
Logo.g:
c.
Analyse
syntaxique:
le
parser
(Logo.g)
L'analyseur
syntaxique
(ou
parser)
vérifie
que
l'ensemble
des
mots
issus
de
l'analyse
lexicale
(tokens)
forme
une
phrase
syntaxiquement
correcte.
Le
parser
va
donc
générer
un
arbre
syntaxique
abstrait
(AST)
à
partir
des
tokens
générés
par
le
lexer.
Flux
de
tokens
=>
AST
Nous
avons
utilisée
la
règle
suivante
du
parser
comme
axiome
de
notre
grammaire:
Remarque
importante:
Ici,
nous
avons
utilisé
un
token
imaginaire:
PROGRAMME.
Cela
nous
permet
de
résoudre
les
problèmes
de
récursivité
à
gauche.
En
effet,
AntLR
réalise
une
analyse
descendante
de
type
LL
et
nous
avons
vu
en
cours,
qu'une
grammaire
récursive
à
gauche
ne
pouvait
pas
être
LL(1).
5
6.
AntLR
donne
la
possibilité
de
créer
automatiquement,
semi
automatiquement
ou
manuellement
l'arbre
AST.
Dans
le
fichier
Logo.g,
nous
avons
ajouté
une
section
option
indiquant
au
parser
de
créer
l'arbre
AST:
Les
méthodes
du
parser
associées
aux
règles
de
la
grammaire
ne
sont
alors
plus
de
type
void
mais
d'un
type
structuré
dont
le
nom
reprend
celui
de
la
méthode.
Que
fait
AntLR?
Pour
chaque
règle:
AntLR
crée
une
racine
d'arborescence
Pour
chaque
token
présent
dans
une
règle:
• AntLR
crée
un
noeud
(Tree)
• Ajoute
ce
noeud
comme
fils
du
noeud
courant
(les
noeuds
forment
donc
une
liste)
• Attache
les
noeuds
de
la
liste
à
un
noeud
racine
de
type
nil.
Ajout
des
instructions
au
parser
dans
la
grammaire
Logo.g:
Les
noeuds
AV,
TD
et
TG
dans
l'exemple
ci-‐dessus
sont
des
noeuds
racine
car
ils
sont
suffixés
avec
un
symbole
^.
Au
contraire,
pour
ne
pas
créer
de
noeud
avec
un
token,
on
peut
le
suffixer
avec
un
symbole
!.
d.
Analyse
sémantique:
tree
Walker
(LogoTree.g)
Le
tree-‐walker
parcourt
l'arbre
AST
généré
par
le
parser
et
exécute
les
actions.
Pour
les
fonctionnalités
concernant
l'UI
(qui
consistent
à
tracer
un
trait
sur
l'écran
en
se
déplaçant
par
exemple),
nous
avons
fait
appel
à
la
classe
Traceur.
Pour
les
autres
fonctionnalités
comme
par
exemple
l'affectation
de
variables,
nous
avons
fait
appel
à
des
fonctions
que
nous
avons
développées
dans
la
classe
LogoUtil.java.
6
7.
e.
En
résumé,
comment
ajouter
une
fonctionnalité?
Pour
ajouter
une
fonctionnalité,
il
faut:
Dans
la
grammaire
Logo.g:
1. Ajouter
un
token
au
lexer
2. Ajouter
une
instruction
au
parser
3. Sauvegarder
(les
classes
lexer
et
parser
sont
générées)
Dans
la
grammaire
LogoTree.g:
1. Ajouter
le
parsing
d'un
arbre
2. Faire
générer
le
parseur
d'arbre
3. Ajouter
une
méthode
à
la
classe
logogui.Traceur
e.
Quelles
fonctionnalités
avons-‐nous
ajoutées?
Principales
commandes
de
la
tortue
Nous
avons
implémenté
les
commandes
de
base
permettant
à
la
tortue
d'avancer,
d'effectuer
une
rotation,
de
reculer
...
Lexer
(Logo.g)
Parser
(Logo.g)
7
8.
Tree-‐Walker
(LogoTree.g)
Pour
les
instructions
élémentaires
suivantes
(CF
fenêtre
de
gauche),
l'arbre
suivant
(fenêtre
de
droite)
est
généré
par
le
parser:
La
représentation
AntLR
sous
forme
de
liste
est
la
suivante:
(PROGRAMME
(FPOS
[500
500])
(AV
100)
(TD
300)
)
AntLR
utilise
deux
tokens
imaginaires:
UP
et
DOWN
pour
indiquer
les
niveaux
dans
l'arbre.
Expressions
arithmétiques
Nous
avons
implémenté
certaines
opérations
arithmétiques
telles
que
l'addition,
la
soustraction,
la
multiplication,
la
division
et
l'opération
puissance.
Afin
de
créer
une
grammaire
non
ambigüe,
nous
avons
donné
la
priorité
aux
opérateurs
multiplier
et
diviser
sur
les
opérateurs
additionner
et
soustraire.
Lexer
(Logo.g)
8
9.
Parser
(Logo.g)
Tree-‐Walker
(LogoTree.g)
Expressions
booléennes
De
la
même
façon
que
pour
les
expressions
arithmétiques,
nous
avons
implémenté
les
expressions
booléennes
de
base
telles
que
égal,
différent,
supérieur
et
inférieur.
Parser
(Logo.g)
Tree-‐Walker
(LogoTree.g)
Identificateurs,
affectation
Variables
globales
La
déclaration
et
l’affectation
d’une
variable
globale
sont
effectuées
grâce
au
mot-‐clé
DONNE.
Dans
le
tree-‐walker,
nous
appelons
la
méthode
setVar(String,Double)
définie
dans
la
classe
LogoUtil
du
fichier
LogoUtil.java.
9
10.
Tree-‐Walker
(LogoTree.g)
LogoUtil.java
La
fonction
setVar
de
la
classe
LogoUtil
stocke
les
variables
dans
une
table
de
hachage
"variables"
qui
associe
le
nom
de
la
variable
(String)
à
sa
valeur
(Double).
Variables
locales
Pour
les
variables
locales,
nous
avons
du
gérer
le
problème
des
portées
imbriquées.
Nous
avons
représenté
chaque
portée
par
une
hashmap
contenant
le
dictionnaire
des
symboles
qui
sont
déclarés
dans
la
portée
concernée.
Pour
gérer
le
problème
des
portées
imbriquées,
nous
avons
donc
utilisé
une
pile
de
hashmap.
• A
chaque
fois
que
nous
rentrons
dans
un
bloc,
nous
empilons
la
hashmap
contenant
les
variables
locales
déclarées
dans
ce
bloc
• A
chaque
fois
que
nous
sortons
d'un
bloc,
nous
dépilons
cette
hashmap
(qui
se
trouve
au
sommet
de
la
pile)
Ainsi,
avec
cette
méthode,
la
portée
courante
courante
est
toujours
la
portée
au
sommet
de
la
pile.
Contexte
d'un
symbole
=
portée
dans
laquelle
il
est
(+
portées
dans
laquelle
la
portée
est
imbriquée)
éventuellement
10
11.
LogoUtil.java
Insertion
d'un
identificateur
dans
un
contexte
L’insertion
d’un
identificateur
se
fait
dans
le
dictionnaire
de
la
portée
en
sommet
de
pile.
Recherche
d'un
identificateur
dans
un
contexte
Pour
rechercher
un
identificateur
nous
utilisons
son
contexte
(qui
possède
une
pile
de
portées
donc
une
pile
de
hashmaps).
Nous
cherchons
donc
l'identificateur
dans
cette
pile
de
portées
de
la
façon
suivante:
1. nous
regardons
si
l'identificateur
se
situe
dans
la
portée
courante
(donc
au
sommet
de
la
pile
de
portées
du
contexte)
2. si
nous
ne
trouvons
pas
l'identificateur
dans
la
portée
courante,
nous
parcourons
la
pile
élément
par
élément
jusqu'à
trouver
l'identificateur.
(parcours
de
la
portée
la
plus
récente
à
la
portée
la
plus
ancienne)
3. si
nous
ne
trouvons
pas
l'identificateur
dans
la
pile
de
portée
(donc
s'il
n'est
dans
aucune
portée)
alors
nous
regardons
dans
la
hashmap
contenant
les
variables
globales.
4. si
l'identificateur
n'est
pas
dans
cette
hashmap,
il
n'est
pas
défini.
11
12.
LogoUtil.java
IV.
Structures
de
contrôle
Evaluation
différée
d'une
arborescence
Pour
les
structures
de
contrôle
qui
suivent,
nous
avons
utilisé
le
parsing
différé.
En
quoi
cela
consiste-‐t-‐il?
Le
parsing
différé
se
fait
à
l'aide
d'une
règle
que
l'on
écrit
dans
la
grammaire.
On
mémorise
l'index
sur
lequel
cette
règle
doit
commencer
le
matching.
Avant
d'appeler
la
méthode
du
parser
associée
à
la
règle,
on
empile
l'index
sur
lequel
elle
doit
commencer
le
matching
et
on
dépile
ensuite
le
noeud
qu'elle
aura
placé
pour
la
suite
du
matching
et
qui
s'avère
inutile.
12
13.
La
répétition
Repeat
Tree-‐Walker
(LogoTree.g)
Nous
avons
placé
un
point
(.)
après
l'expression
"exp"
afin
que
le
parsing
ne
se
fasse
pas
par
défaut.
Le
point
représente
l'index
de
l'arborescence.
L'arbre
AntLR
associé
est
donc
le
suivant:
REPEAT
DOWN
<atom>
<.>
UP.
Rappel:
AntLR
utilise
deux
tokens
imaginaires:
UP
et
DOWN
pour
indiquer
les
niveaux
dans
l'arbre.
La
règle
list_evaluation
doit
connaître
l'index
de
l'arborescence
car
lorsqu'elle
va
être
appelée,
elle
va
devoir
dépiler
cet
index.
Le
code
situé
dans
la
boucle
FOR
ci-‐dessus
effectue
les
actions
suivantes:
• empile
l'index
du
noeud
racine
traité
par
list_evaluation
push(mark_list)
• appelle
la
méthode
list_evaluation
list_evaluation()
=
règle
qui
permet
de
parser
un
arbre
dont
la
racine
est
le
token
LIST
et
dont
les
fils
sont
les
instructions
13
14. • dépile
le
noeud
superflu
que
list_evaluation
va
empiler
pop()
Les
méthode
push() et
pop()
sont
des
méthodes
que
nous
avons
développées
dans
LogoTree.g
afin
d'alléger
l'écriture:
While
Pour
le
while,
nous
avons
suivi
la
même
méthode
que
pour
le
repeat.
Tree-‐Walker
(LogoTree.g)
L'alternative
(si)
Dans
le
cas
de
l'alternative,
nous
utilisons
la
possibilité
d'ignorer
une
branche
de
l'arbre
au
cours
du
parcours
initial
et
d'y
revenir
par
la
suite
en
sauvegardant
son
index.
La
particularité
de
l'instruction
alternative
est
que
le
second
bloc
d'instructions
est
facultatif.
Si
l'expression
booléenne
renvoie
vraie
alors
exécuter
le
bloc
1
sinon
exécuter
le
bloc
2
(optionnel
car
"?"):
14
15.
Nous
initialisons
le
marqueur
d'index
(mark_list2)
à
0.
La
récupération
de
l'index
a
lieu
grâce
à
la
méthode
"input.mark()"que
l'on
affecte
à
mark_list2.
Si
mark_list2>0
alors
nous
pouvons
traiter
le
second
bloc
d'instructions,
sinon,
l'interpréteur
ne
l'a
pas
trouvé.
Tree-‐Walker
(LogoTree.g)
V.
Procédures
et
fonctions
Procédures
Afin
de
générer
des
branches
d'instructions
qui
seront
plus
facilement
accessibles
lors
du
parcours
de
l'arbre
AST,
nous
avons
créé
deux
tokens
imaginaires:
ARG
et
et
PARAM.
ARG
est
utilisé
lors
de
la
déclaration
de
notre
procédure.
PARAM
est
utilisé
lors
de
l'appel
de
notre
procédure.
Nous
avons
également
créé
une
classe
Procedure
qui
contient
les
deux
structures
suivantes:
• une
liste
ordreParam
utilisée
pour
enregistrer
les
paramètres
et
leur
ordre
au
moment
de
la
déclaration
de
notre
procédure
15
16. private ArrayList<String> ordreParam;
• une
hashmap
params
utilisée
pour
associer
une
valeur
à
chaque
variable
lors
de
l'appel
de
notre
procédure
private ArrayList<String> ordreParam;
Déclaration
d'une
procédure
La
fonction
"pushProcedure"
définie
dans
la
classe
LogoUtils,
va
nous
permettre
d'empiler
notre
procédure
sur
une
pile
de
procédures
afin
de
simplifier
l'ajout
des
paramètres
lors
de
la
déclaration.
16
17.
Appel
d'une
procédure
On
va
récupérer
le
nom
de
la
procédure
en
cours
et
on
va
dire
à
notre
variable
de
type
LogoUtil
(u)
de
récupérer
la
procédure
du
nom
contenu
dans
la
variable:
"nameProc"
c'est
à
dire
de
la
chercher
dans
la
pile
puis
de
l'extraire
de
cette
pile.
On
va
ensuite
modifier
cette
procédure
en
associant
une
valeur
à
chaque
variable
déclarée
lors
de
la
déclaration
de
procédure.
Remarque:
Arité
d'une
procédure
La
méthode
getArite()
dans
notre
classe
Procedure
vérifie
que
le
nombre
de
paramètres
passés
lors
de
l'appel
de
cette
procédure
(
dans
la
hashmap
params)
est
bien
égal
au
nombre
de
paramètres
déterminé
lors
de
la
déclaration
de
cette
procédure
(dans
la
liste
ordreParam).
17
18.
VI.
Eléments
non
traités
et
difficultés
rencontrées
Eléments
non
traités
Faute
de
temps,
nous
n'avons
pas
implémenté
les
fonctionnalités
suivantes:
• Les
procédures
récursives
• La
primitive
LOOP
qui
renvoie
le
nombre
de
tours
dans
une
boucle.
• La
primitive
STOP.
• La
possibilité
d'utiliser
les
opérateurs
AND
et
OR
dans
les
expressions
booléennes.
Difficultés
rencontrées
• Au
début
du
projet,
il
nous
a
fallu
du
temps
pour
nos
familiariser
la
syntaxe
et
le
fonctionnement
de
AntLR.
• Nous
avons
rencontré
quelques
difficultés
dans
la
gestion
des
tokens
imaginaires
générés
par
AntLR
<UP>
and
<DOWN>.
Cela
nous
créait
des
erreurs
de
marquage.
Il
y
avait
un
décalage
entre
l'arbre
que
l'on
créait
et
l'arbre
que
l'on
parsait.
VII.
Conclusion
Ce
projet
nous
a
permis
d'aller
plus
loin
dans
notre
compréhension
des
différents
types
d'analyse:
lexicale,
syntaxique
et
sémantique.
Il
nous
a
permis
également
de
voir
un
exemple
concret
et
d'appliquer
les
principes
théoriques
vus
en
cours.
Nous
avons
désormais
une
vision
globale
plus
claire
sur
les
différentes
phases
allant
de
l'écriture
à
l'exécution
d'un
programme.
18