La syntaxe - Département Montefiore

Transcription

La syntaxe - Département Montefiore
INFO0004 – Structure des
langages de programmation
Analyse lexicale et syntaxique
Justus H. Piater
Résumé
Résumé – Analyse
fichier tex te
Program m ation
Analyse lex icale
Analyse
flux de jetons
Com pilation
Analyse syntax ique
Liaison
Interprétation
Ex écution
arbre de syntax e abstraite
Analyse sém antique
arbre annoté
Résumé
3 / 58
Syntaxe et sémantique
Un langage de programmation doit être précis, sans
ambiguïté.
La syntaxe :
La sémantique :
Résumé
spécifie la structure d’un
programme valide (p.ex., par une
grammaire)
spécifie la signification d’un
programme valide.
4 / 58
Un programme quelconque
trosq [] = []
trosq (x:xs) = trosq elts_lt_x ++ [x] ++ trosq elts_greq_x
where
elts_lt_x = [y | y <− xs, y < x]
elts_greq_x = [y | y <− xs, y >= x]
Ce programme est-il valide ? (syntaxe)
Si oui, que fait-il ? (sémantique)
Résumé
5 / 58
La syntaxe et les langages formels
Les langages réguliers
Trois règles de base :
1. enchaînement
2. alternative
3. fermeture de Kleene
Exemple : les nombres entiers
Tout ce qui s’exprime par ces règles appartient à un
langage régulier correspondant à une expression
régulière et est analysé par un scanneur.
Important
Langage formel (≠ langage de programmation)
La syntaxe et les langages formels
7 / 58
Les expressions régulières
Exemple d’une syntaxe :
x
xyz
(…)
M|N
MN
M*
M+
M?
[…]
[a–z]
.
un caractère (représente lui-même)
une chaîne (représente elle-même)
précédence
M ou N
M suivi par N
zéro, une ou plusieurs instances de M
une ou plusieurs instances de M
zéro ou une instance de M
n’importe quel caractère qui apparaît entre les crochets
n’importe quel caractère entre a et z
n’importe quel caractère
La syntaxe et les langages formels
8 / 58
Quelques exemples
•
•
•
•
•
un nombre entier
un mot clé d’un langage de programmation
un identifiant d’un langage de programmation
une expression avec des parenthèses équilibrées ?
le code source d’un programme ?
La syntaxe et les langages formels
9 / 58
Les langages hors-contexte
Une quatrième règle de base [7] :
4. récursion
Exemple : les expressions aux parenthèses équilibrées
Tout ce qui s’exprime par ces quatre règles appartient
à un langage hors-contexte (context free)
correspondant à une grammaire hors-contexte et
est analysé par un analyseur (parser).
La syntaxe et les langages formels
10 / 58
Les grammaires EBNF
“Extended Backus-Naur Form”
[2]
[3]
[4]
expression → number | ( expression )
expression → expression operator
expression
operator → + | − | * | /
• Il y a une ou plusieurs productions pour chaque
symbole non-terminal.
• number est un symbole terminal.
• Les opérateurs +, −, * et / sont des symboles
littéraux.
• expression est un symbole de départ.
La syntaxe et les langages formels
11 / 58
Dériver une chaîne
8*4+3
La syntaxe et les langages formels
12 / 58
Un arbre syntaxique (parse tree)
expression
expression
expression
operator
expression
number
*
number
operator
expression
+
number
La syntaxe et les langages formels
13 / 58
Une autre dérivation possible
expression
expression
operator
number
*
La syntaxe et les langages formels
expression
expression
operator
expression
number
+
number
14 / 58
Une grammaire améliorée
[5]
[6]
[7]
[8]
[9]
expression
term
factor
add_op
mult_op
→
→
→
→
→
term | expression add_op term factor | term mult_op factor
number | ( expression )
+|−
*|/
Cette grammaire représente
• l’associativité
• la précédence
8*4+3
8−4−3
La syntaxe et les langages formels
15 / 58
Comment trouver une
bonne grammaire ?
Il y a un nombre infini de grammaires qui représentent
un langage donné.
On préfère des grammaires
• qui représentent les règles d’évaluation
(associativité, précédence, …)
• qui réfléchissent la structure d’un programme
d’une manière utile au compilateur (symboles nonterminaux tels que loop, statement, function)
• dont l’analyse est simple et efficace
La syntaxe et les langages formels
16 / 58
La syntaxe: Analyse
lexicale et syntaxique
La structure lexicale :
• la structure des éléments individuels (jetons,
tokens)
(mots clés, identifiants, opérateurs, parenthèses, …)
• spécifiée par des expressions régulières ou par des
grammaires hors-contexte
La structure syntaxique :
• La composition de ces éléments
• spécifiée par une grammaire hors-contexte
Une formalisation astucieuse de la syntaxe facilite
l’analyse automatique d’un programme.
La syntaxe et les langages formels
Jay et la syntaxe concrète
17 / 58
Jay – un langage simple
// compute result – the nth Fibonacci number
void main() {
int n, fib0, fib1, temp, result;
n = 8;
fib0 = 0;
fib1 = 1;
while (n > 0) {
temp = fib0;
fib0 = fib1;
fib1 = fib0 + temp;
n = n – 1;
}
result = fib0;
}
Jay et la syntaxe concrète
19 / 58
La structure lexicale de Jay
comment
keyword
identifier
literal
separator
or_op
and_op
rel_op
add_op
mul_op
not_op
Jay et la syntaxe concrète
//.*\n
boolean | else | if | int | main | void | while
[a–zA–Z_][a–zA–Z_0–9]* (avec des exceptions)
true | false | [0–9]+
(|)|{|}|;|,
||
&&
< | <= | > | >= | == | !=
+|−
*|/
!
20 / 58
La syntaxe concrète de Jay
[10]
[11]
[12]
[13]
[14]
[15]
[16]
[17]
[18]
[19]
[20]
program → void main () { declarations statements }
declarations → { declaration }*
declaration → type identifiers ;
type → int | boolean
identifiers → identifier { , identifier }*
statements → { statement }*
statement → ; | block | assignment | if_stmt | while_stmt
block → { statements }
assignment → identifier = expression ;
if_stmt → if ( expression ) statement { else statement }opt
while_stmt → while ( expression )
statement
Jay et la syntaxe concrète
21 / 58
La syntaxe concrète de Jay (suite)
[21]
[22]
[23]
[24]
[25]
[26]
[27]
expression → conjunction { or_op
conjunction }*
conjunction → relation { and_op relation }*
relation → addition { rel_op addition }*
addition → term { add_op term }*
term → negation { mul_op negation
}*
negation → { neg_op }opt factor
factor → identifier | literal | (
expression )
Jay et la syntaxe concrète
22 / 58
Un arbre de syntaxe concrète
program
void
main
(
)
{
declarations
statements
declaration
statement
type
identifiers
;
int
identifier
identifier
x
x
}
assignment
=
expression
;
literal
1
Jay et la syntaxe concrète
23 / 58
Que l’arbre représente-t-il ?
• Le texte du programme apparaît de gauche à droite.
• Une traversée post-ordre représente (plus ou moins)
l’ordre de calcul.
Que ne représente-t-il pas ?
• Tous les identifiants ont-ils été déclarés ?
• Sont-ils du bon type ?
• Leur a-t-on assigné une valeur avant qu’ils ne soient
utilisés ?
• Si tout est correct, que signifie le programme ?
La sémantique !
Jay et la syntaxe concrète
24 / 58
La grammaire de Jay est ambiguë !
if (true) if (true) ; else ;
if_stmt
if
( true )
if
if_stmt
statement
( true )
; else ;
if
( true )
statement else ;
if
;
( true )
Jay et la syntaxe concrète
25 / 58
L’else suspendu (dangling else)
Solutions :
• exiger un mot clé pour fermer : if…fi (Ada)
• spécification en dehors de la grammaire (Pascal, C,
C++)
• grammaire élaborée (Java) :
[28] ifThenStmt → if ( expr ) statement
[29]
ifElseStmt → if ( expr ) stmtNoShortIf else statement
Jay et la syntaxe concrète
26 / 58
Reconnaître de la syntaxe
Analyse en deux étapes
1. La structure lexicale est spécifiée par un ensemble
d’expressions régulières.
Le scanneur transforme le flux de caractères d’un
programme en un flux de jetons.
2. La syntaxe est spécifiée par une grammaire.
L’analyseur transforme le flux de jetons en
une représentation abstraite de la structure du
programme.
Chaque type de jetons correspond à un symbole
terminal de la grammaire.
Reconnaître de la syntaxe
28 / 58
Scanner avec un automate fini
Les langages réguliers et les automates finis sont
équivalents.
• Un automate fini s’implémente très facilement.
• Les cas spéciaux sont aisément pris en compte.
• L’analyseur syntaxique demande des jetons au
scanneur.
• L’automate retourne toujours la plus longue chaîne
possible.
• Erreurs lexicales :
• Comment se produisent-elles ?
• Comment réagir ?
Reconnaître de la syntaxe
29 / 58
Un automate fini partiel pour Jay
space
start
/
/
\n
Reconnaître de la syntaxe
a-zA-Z_
a-zA-Z_0-9
0-9
0-9
+
<
=
non-\n
30 / 58
Implémentation d’un scanneur lexical
Token TokenStream::nextToken() { // Return token type and value
skipWhiteSpace(); // first check for whitespace and bypass it
// …
// Check for an Operator; recover 2–character operators
// as well as 1–character ones
if (isOperator(nextChar)) {
Token t("Operator", nextChar);
switch (nextChar) { // look for <=, >=, !=, ==
case '<': case '>': case '!': case '=':
nextChar = readChar();
if (nextChar == '=') {
t.value += nextChar;
nextChar = readChar();
}
return t;
// …
default:
// all other operators
nextChar = readChar();
return t;
Reconnaître de la syntaxe
}
}
31 / 58
Implémentation d’un
scanneur lexical (suite)
// Check for an Identifier, Keyword, or Literal
if (isLetter(nextChar)) {
Token t("Identifier");
while ((isLetter(nextChar) || isDigit(nextChar))) {
t.value += nextChar;
nextChar = readChar(); // get complete Identifier
}
if (isKeyword(t.value))
t.type = "Keyword";
// … (test for literal)
return t;
}
// In case of a lexical error: …
}
Reconnaître de la syntaxe
32 / 58
Implémentation d’un
scanneur lexical (suite)
bool TokenStream::isOperator(char c) const {
return (c == '=' || c == '+' || c == '–' || c == '*' || c == '/' ||
c == '<' || c == '>' || c == '!' || c == '&' || c == '|' );
}
Reconnaître de la syntaxe
33 / 58
Analyse syntaxique
Comment analyser un programme en termes de la
grammaire ?
• Plusieurs types de grammaires, plusieurs types
d’analyseurs (→ cours en compilation)
• La méthode la plus simple : L’analyse par descente
récursive (recursive-descent parsing)
Reconnaître de la syntaxe
34 / 58
L’analyse par descente récursive
On traverse l’arbre de syntaxe concrète d’en haut :
• Chaque symbole non-terminal est représenté par une
méthode.
• On reçoit du scanneur un jeton à la fois.
• Selon le prochain jeton, on décide comment
développer le nœud actuel.
• En même temps, on construit la représentation
désirée.
Note
Cela ne fonctionne pas avec n’importe quel type de
grammaire !
Reconnaître de la syntaxe
35 / 58
Un exemple en Jay
void main() {
int f, i, n;
n = 7;
f = 1;
i = 1;
while (i < n) {
i = i + 1;
f = f * i;
}
}
Reconnaître de la syntaxe
36 / 58
Comment construire un analyseur à descente récursive
Pour chaque
1.
non-terminal et chaque règle
Pour chaque membre
de
:
:
a.
2.
3.
Si est non-terminal, appeler la méthode
correspondante.
b. Si est terminal, et si la catégorie lexicale du
jeton actuel est égale à , appeler nextToken() ;
sinon, il constitue une erreur syntaxique.
Si contient une série de symboles (indiquée par
une ‘*’), insérer une boucle while correspondante.
S’il y a plusieurs règles
, insérer des if…else
pour distinguer les possibilités selon le jeton
actuel.
Reconnaître de la syntaxe
37 / 58
Les différents types de
grammaires et d’analyseurs
LL( ), LR( ), …
LL :
LR :
left-to-right, leftmost derivation (top-down)
left-to-right, rightmost derivation (bottom-up)
nombre de symboles consultés d’avance
(lookahead)
Le type d’une grammaire est donné par le type
d’analyseur correspondant.
• Notre analyseur par descente récursive est du type
LL(1).
• Nous sommes donc limités à des grammaires LL(1).
Reconnaître de la syntaxe
38 / 58
LL et la récursion gauche
[30]
[31]
[32]
id_list → id id_list_tail
id_list_tail → , id id_list_tail
→ ;
La grammaire suivante accepte le même langage :
[33]
id_list → id_list_prefix ;
[34] id_list_prefix → id_list_prefix , id
[35]
→ id
Elle n’est pas LL à cause d’une récursion gauche (left
recursion).
Elle se transforme mécaniquement en récursion droite.
Reconnaître de la syntaxe
39 / 58
LL et les préfixes communs
[36]
[37]
stmt → id := expr
→ id ( arg_list )
Ceci se résout mécaniquement, par factorisation
gauche (left factoring) :
[38]
stmt → id stmt_tail
[39]
stmt_tail → := expr
[40]
→ ( arg_list )
Mais s’il y a une autre production qui commence par le
jeton ( ?
Hélas, il y a des grammaires non-LL.
Reconnaître de la syntaxe
40 / 58
Jay et la syntaxe abstraite
Pourquoi analyser la syntaxe d’un programme ?
Basé sur la syntaxe, on définit la logique de calcul, la
sémantique du programme.
Objectif : Lors de l’analyse syntaxique, créer une
structure de données représentant cette logique.
Jay et la syntaxe abstraite
42 / 58
Un arbre de syntaxe concrète
assignment
identifier
=
expression
;
conjunction
x
relation
addition
term − term − term
a
b
c
Voir aussi la syntaxe abstraite [46].
Jay et la syntaxe abstraite
43 / 58
La syntaxe concrète…
• pour spécifier le texte d’un programme
• pour analyser le texte d’un programme
• mais non appropriée pour l’analyse sémantique :
• trop de détail
• calcul mal représenté
Jay et la syntaxe abstraite
44 / 58
… et la syntaxe abstraite
• représente la structure du calcul (indépendamment
du langage de programmation)
• représentation intermédiaire pour
• analyser la sémantique
• optimisation
• séparer le front end du back end
Jay et la syntaxe abstraite
45 / 58
Un arbre de syntaxe abstraite
Assignment
Var
Expr
Variable
Binary
x
Op
ArithOp
−
Expr
Expr
Binary
Op
Expr
Variable
c
Expr
ArithOp
Variable
Variable
−
a
b
Voir aussi la syntaxe concrète [43].
Jay et la syntaxe abstraite
46 / 58
La représentation de
la syntaxe abstraite
Expression = Variable | Value | Binary | Unary
Binary
= Operator ; Expression ; Expression
• des listes de composants (alternatifs ‘|’ ou
combinés ‘;’)
• construction en forme d’arbre
Les composants correspondent (plus ou moins) aux
composants de la syntaxe concrète. (Plutôt l’inverse !)
Important
Ceci n’est pas une grammaire (dérivations) mais
une structure de données (composants).
Jay et la syntaxe abstraite
47 / 58
La syntaxe abstraite de Jay
Program
Declarations
Declaration
Type
Statement
Block
Assignment
Conditional
Loop
Expression
Variable
Value
Jay et la syntaxe abstraite
= Declarations ; Block
= Declaration*
= Variable ; Type
= int | boolean | undef
= Skip | Block | Assignment | Conditional |
Loop
= Statement*
= Variable ; Expression
= Expression ; Statement ; Statement
= Expression ; Statement
= Variable | Value | Binary | Unary
= string
= int | boolean
48 / 58
La syntaxe abstraite de Jay (suite)
Binary
Unary
Operator
BoolOp
RelOp
ArithOp
UnaryOp
= Operator ; Expression ; Expression
= UnaryOp ; Expression
= BoolOp | RelOp | ArithOp | UnaryOp
= && | ||
= < | <= | > | >= | == | !=
=+ | − | * | /
=!
Jay et la syntaxe abstraite
49 / 58
Implémenter la syntaxe abstraite
class Binary : public Expression {
Operator *op;
Expression *term1, *term2;
};
L’héritage représente naturellement la composition des
éléments spécifiés par la syntaxe abstraite.
Note
• On ajoute des méthodes sémantiques, et voilà –
un interpréteur !
• On ajoute des méthodes pour la génération de
code, et voilà – un compilateur !
C’est cela l’objectif de la syntaxe abstraite – le lien
entre la syntaxe et la sémantique.
Jay et la syntaxe abstraite
50 / 58
Construire l’arbre de syntaxe
abstraite lors de l’analyse
Pour chaque
:
1.
2.
Créer une instance de la classe syntaxique
abstraite
correspondante.
Pour chaque membre
a.
b.
3.
non-terminal et pour chaque règle
de
:
Si est non-terminal, appeler la méthode
correspondante, et affecter sa valeur de retour
au composant correspondant de .
Si est terminal, et si la catégorie lexicale du
jeton actuel est égale à , appeler nextToken() ;
sinon, il constitue une erreur syntaxique.
Si contient une série de symboles (indiquée par
une ‘*’), insérer une boucle while correspondante.
Jay et la syntaxe abstraite
51 / 58
Construire l’arbre de syntaxe abstraite lors de l’analyse (suite)
4.
5.
S’il y a plusieurs règles
, insérer des if…else
pour distinguer les possibilités selon le jeton
actuel.
Retourner .
Jay et la syntaxe abstraite
52 / 58
Exemple : Assignment
Assignment* ConcreteSyntax::assignment() {
// Assignment ––> Identifier = Expression ;
Assignment* a = new Assignment();
if (token.type == "Identifier") {
a–>target = new Variable();
a–>target–>id = token.value;
token = input.nextToken();
match("=");
a–>source = expression();
match(";");
}
else syntaxError("Identifier");
return a;
}
Jay et la syntaxe abstraite
53 / 58
Exemple : Conjunction
Expression* ConcreteSyntax::conjunction() {
// Conjunction ––> Relation { && Relation }*
Expression* e = relation();
while (token.value == "&&") {
Binary* b = new Binary();
b–>term1 = e;
b–>op = new Operator(token.value);
token = input.nextToken();
b–>term2 = relation();
e = b;
}
return e;
}
Jay et la syntaxe abstraite
54 / 58
Exemple : Construire
l’arbre de syntaxe abstraite
Lors de l’analyse syntaxique :
void main() {
int f, i, n;
n = 7;
f = 1;
i = 1;
while (i < n) {
i = i + 1;
f = f * i;
}
}
Jay et la syntaxe abstraite
55 / 58
Résumé
Résumé
• Spécifier la syntaxe :
• expressions régulières
• grammaires EBNF
• Analyser la syntaxe :
• automates finis
• analyseurs par descente récursive
• La syntaxe abstraite
Résumé
57 / 58
Votre tâche
Inventer et définir votre propre langage de
programmation :
• structure logique / sémantique (syntaxe abstraite)
• structure lexicale (expressions régulières) et syntaxe
concrète (grammaire EBNF)
Soyez inventifs :
• éléments de Pascal, C, C++, Lisp, langages script, …
• boucles et conditionnels innovants ; opérateurs non
standards, ternaires, …
• deux types distincts au moins
Flottants, chaînes, entrée/sortie simples possibles ; voir
le site Web pour le support pour BSim.
Pas de fonctions, structures de données complexes, …
Résumé
58 / 58

Documents pareils