Créer son propre langage de programmation de A à Z
<< Syntaxe du langage Simple | Analyseur lexical | Analyseur syntaxique >>
On va passer maintenant au côté pratique. Nous pouvons commencer le développement du compilateur. Pour rappel la première chose que fait un compilateur est l'analyse lexicale. C'est donc là que nous allons commencer. Nous allons utiliser Flex qui générera un analyseur lexical pour le langage Simple.
Je vous donne le code de Flex un peu à la dure. Mais je juge l'avoir suffisamment bien commenté pour le comprendre. On commence par créer un fichier lexique_simple.lex qui contiendra le code suivant :
⚠ (:source lang=c header="lexique_simple.lex" linenum:)
/*
<:vspace>
En Flex, on commence par les eventuelles declarations C que l'on met entre les balises %{ et %}.
Entre ces balises, j'ai inclus la stdlib et la stdio de C. J'ai aussi declare 1 variable de type int.
La variable lineno correspond au numero de ligne.
La variable error est un booleen. Il est a true si une erreur est detectee.
Il existe la variable globale yylineno dans Flex que l'on peut ajouter en option. C'est cense gerer le numero de ligne tout seul mais pour des raisons inexpliquees, elle ne s'incremente pas chez moi.
Je prefere donc gerer le mecanisme de numero de ligne moi-meme.
<:vspace>
*/
<:vspace>
%{
<:vspace>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
void lexeme(void);
unsigned int lineno=1;
bool error=false;
<:vspace>
%}
<:vspace>
/*
<:vspace>
Juste apres on declare les eventuelles variables Flex. Ce sont nos terminaux associes reconnus par des expressions regulieres.
<:vspace>
*/
<:vspace>
/* [[:digit:]] equivaut a [0-9] */
<:vspace>
nombre 0|[1-9][[:digit:]]*
<:vspace>
/* [[:alpha:]] equivaut a [a-zA-Z] (tout les caracteres de l'alphabet majuscules et minuscules) et [[:alnum:]] equivaut a [a-zA-Z0-9] (tout les caracteres alphanumeriques) */
<:vspace>
variable [[:alpha:]][[:alnum:]]*
<:vspace>
/*
<:vspace>
Entre les %% on ecrit toutes les actions a chaque fois que l'analyseur detectera des lexemes (terminaux) de Simple.
On ecrit donc l'ensemble des terminaux de Simple.
<:vspace>
*/
<:vspace>
%%
<:vspace>
{nombre} {
printf("\tNombre trouve a la ligne %d. Il s'agit du nombre %s et comporte %d chiffre(s)\n",lineno,yytext,yyleng);
}
<:vspace>
"afficher" {lexeme();}
<:vspace>
"=" {lexeme();}
<:vspace>
"+" {lexeme();}
<:vspace>
"-" {lexeme();}
<:vspace>
"*" {lexeme();}
<:vspace>
"/" {lexeme();}
<:vspace>
"(" {lexeme();}
<:vspace>
")" {lexeme();}
<:vspace>
"et" {lexeme();}
<:vspace>
"ou" {lexeme();}
<:vspace>
"non" {lexeme();}
<:vspace>
";" {lexeme();printf("\n");}
<:vspace>
"vrai" {lexeme();}
<:vspace>
"faux" {lexeme();}
<:vspace>
"\n" {lineno++;}
<:vspace>
/* Si j'avais defini l'action de variable au debut, l'analyseur ne verrait plus les lexemes comme afficher ou supprimer mais comme des variables portant ce nom. L'ordre de definition des regles pour chaque lexemes n'est donc pas sans logique en Flex */
<:vspace>
{variable} {
printf("\tVariable trouvee a la ligne %d. Il s'agit de %s et comporte %d lettre(s)\n",lineno,yytext,yyleng);
}
<:vspace>
/* L'analyseur ne fait rien pour les espaces et tabulations */
<:vspace>
" "|"\t" {}
<:vspace>
/* Le point est tout le reste qui n'a pas ete defini precedemment. Il est donc a mettre en dernier. */
<:vspace>
. {
fprintf(stderr,"\tERREUR : Lexeme inconnu a la ligne %d. Il s'agit de %s et comporte %d lettre(s)\n",lineno,yytext,yyleng);
error=true;
}
<:vspace>
<:vspace>
%%
<:vspace>
/*
<:vspace>
J'ecris ici mes fonctions C apres le %%
Ma fonction main appellera la fonction de parsing yylex() qui sera construite a la compilation de la source Flex. C'est une fonction qui parse et detecte les lexemes (non terminaux) que nous avons defini dans notre programme Flex.
Le main n'est pas obligatoire. On peut utiliser la fonction main par defaut de Flex (qui ne fait qu'appeler yylex() seulement), il faut dans ce cas specifier main en option.
<:vspace>
*/
<:vspace>
int main(){
printf("Debut de l'analyse lexicale :\n");
yylex();
printf("Fin de l'analyse !\n");
printf("Resultat :\n");
if(error){
printf("\t-- Echec : Certains lexemes ne font pas partie du lexique du langage ! --\n");
}
else{
printf("\t-- Succes ! --\n");
}
return EXIT_SUCCESS;
}
<:vspace>
/*
<:vspace>
La variable globale yytext contient le lexeme courant lu par l'analyseur.
<:vspace>
*/
<:vspace>
void lexeme(){
printf("\tLexeme '%s' trouve a la ligne %d\n",yytext,lineno);
}
<:vspace>
/*
<:vspace>
La fonction yywrap() est appelee des que l'analyseur lexicale detecte le caractere EOF (End Of File). Elle doit retourner 1 pour mettre fin a l'analyse.
Cette fonction n'est pas obligatoire. On peut utiliser la fonction yywrap() par defaut de Flex (qui ne fait que retourner 1 juste), il faut dans ce cas specifier noyywrap en option.
<:vspace>
*/
<:vspace>
int yywrap(){
printf("\tFin de fichier detecte !\n");
return 1;
}
(:sourcend:)
On compile notre fichier Flex :
⚠ (:source lang=bash:)
flex -o lexique_simple.c lexique_simple.lex
(:sourcend:)
Flex génère une source C qui contient la fonction yylex(). Ici je lui ai demandé à la compilation que le nom de fichier sorti soit lexique_simple.c. Sinon par défaut le fichier de sortie s'appelle yy.lex.c. On peut aussi le spécifier en option dans le code flex. On compile maintenant le fichier C généré :
⚠ (:source lang=bash:)
gcc -o lexique_simple lexique_simple.c
(:sourcend:)
On obtient alors un exécutable. C'est notre analyseur lexicale du langage Simple prêt à tourner !
Créons maintenant un fichier programme.simple où on va écrire un programme en Simple. On va volontairement mettre des caractères spéciaux à la dernière ligne. Il devrait nous avertir des erreurs lexicales.
⚠ (:source lang=text header="programme.simple" linenum:)
monEntier = 6 ; monBooleen = faux ;
<:vspace>
afficher monEntier ;
afficher monBooleen ;
afficher 4 ;
afficher non ( ( vrai et faux ) ou vrai ) ;
afficher 6/3 ;
<:vspace>
@#!%^$
(:sourcend:)
On donne ce programme à notre analyseur lexicale :
⚠ (:source lang=bash:)
./lexique_simple < programme.simple
(:sourcend:)
L'analyseur nous renvoit tout les lexèmes qu'il a reconnu un par un. A chaque instruction il fait un saut de ligne.
⚠ (:source lang=text:)
Debut de l'analyse lexicale :
Variable trouvee a la ligne 1. Il s'agit de monEntier et comporte 9 lettre(s)
Lexeme '=' trouve a la ligne 1
Nombre trouve a la ligne 1. Il s'agit du nombre 6 et comporte 1 chiffre(s)
Lexeme ';' trouve a la ligne 1
<:vspace>
Variable trouvee a la ligne 1. Il s'agit de monBooleen et comporte 10 lettre(s)
Lexeme '=' trouve a la ligne 1
Lexeme 'faux' trouve a la ligne 1
Lexeme ';' trouve a la ligne 1
<:vspace>
Lexeme 'afficher' trouve a la ligne 3
Variable trouvee a la ligne 3. Il s'agit de monEntier et comporte 9 lettre(s)
Lexeme ';' trouve a la ligne 3
<:vspace>
Lexeme 'afficher' trouve a la ligne 4
Variable trouvee a la ligne 4. Il s'agit de monBooleen et comporte 10 lettre(s)
Lexeme ';' trouve a la ligne 4
<:vspace>
Lexeme 'afficher' trouve a la ligne 5
Nombre trouve a la ligne 5. Il s'agit du nombre 4 et comporte 1 chiffre(s)
Lexeme ';' trouve a la ligne 5
<:vspace>
Lexeme 'afficher' trouve a la ligne 6
Lexeme 'non' trouve a la ligne 6
Lexeme '(' trouve a la ligne 6
Lexeme '(' trouve a la ligne 6
Lexeme 'vrai' trouve a la ligne 6
Lexeme 'et' trouve a la ligne 6
Lexeme 'faux' trouve a la ligne 6
Lexeme ')' trouve a la ligne 6
Lexeme 'ou' trouve a la ligne 6
Lexeme 'vrai' trouve a la ligne 6
Lexeme ')' trouve a la ligne 6
Lexeme ';' trouve a la ligne 6
<:vspace>
Lexeme 'afficher' trouve a la ligne 7
Nombre trouve a la ligne 7. Il s'agit du nombre 6 et comporte 1 chiffre(s)
Lexeme '/' trouve a la ligne 7
Nombre trouve a la ligne 7. Il s'agit du nombre 3 et comporte 1 chiffre(s)
Lexeme ';' trouve a la ligne 7
<:vspace>
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de @ et comporte 1 lettre(s)
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de # et comporte 1 lettre(s)
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de ! et comporte 1 lettre(s)
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de % et comporte 1 lettre(s)
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de ^ et comporte 1 lettre(s)
ERREUR : Lexeme inconnu a la ligne 9. Il s'agit de $ et comporte 1 lettre(s)
Fin de fichier detecte !
Fin de l'analyse !
Resultat :
-- Echec : Certains lexemes ne font pas partie du lexique du langage ! --
(:sourcend:)
Les derniers caractères spéciaux n'ont pas été reconnu. Il ne font pas partie de l'ensemble des terminaux de Simple. Le programme ne passe pas à l'analyse lexicale (la première étape de la compilation).
Créons maintenant un second fichier contenant des instructions sans aucun sens et ne répondant pas à la syntaxe de Simple :
⚠ (:source lang=text header="programme_faux.simple" linenum:)
68 afficher;
france japon usa = 85;
ecrire 78 et 49
japon = 118 et afficher japon;
vrai+faux=19;
(:sourcend:)
Passons ce fichier à notre analyseur lexical.
⚠ (:source lang=bash:)
./lexique_simple < programme_faux.simple
(:sourcend:)
Le résultat de l'analyse est le suivant :
⚠ (:source lang=text:)
Debut de l'analyse lexicale :
Nombre trouve a la ligne 1. Il s'agit du nombre 68 et comporte 2 chiffre(s)
Lexeme 'afficher' trouve a la ligne 1
Lexeme ';' trouve a la ligne 1
<:vspace>
Variable trouvee a la ligne 2. Il s'agit de france et comporte 6 lettre(s)
Variable trouvee a la ligne 2. Il s'agit de japon et comporte 5 lettre(s)
Variable trouvee a la ligne 2. Il s'agit de usa et comporte 3 lettre(s)
Lexeme '=' trouve a la ligne 2
Nombre trouve a la ligne 2. Il s'agit du nombre 85 et comporte 2 chiffre(s)
Lexeme ';' trouve a la ligne 2
<:vspace>
Variable trouvee a la ligne 3. Il s'agit de ecrire et comporte 6 lettre(s)
Nombre trouve a la ligne 3. Il s'agit du nombre 78 et comporte 2 chiffre(s)
Lexeme 'et' trouve a la ligne 3
Nombre trouve a la ligne 3. Il s'agit du nombre 49 et comporte 2 chiffre(s)
Variable trouvee a la ligne 4. Il s'agit de japon et comporte 5 lettre(s)
Lexeme '=' trouve a la ligne 4
Nombre trouve a la ligne 4. Il s'agit du nombre 118 et comporte 3 chiffre(s)
Lexeme 'et' trouve a la ligne 4
Lexeme 'afficher' trouve a la ligne 4
Variable trouvee a la ligne 4. Il s'agit de japon et comporte 5 lettre(s)
Lexeme ';' trouve a la ligne 4
<:vspace>
Lexeme 'vrai' trouve a la ligne 5
Lexeme '+' trouve a la ligne 5
Lexeme 'faux' trouve a la ligne 5
Lexeme '=' trouve a la ligne 5
Nombre trouve a la ligne 5. Il s'agit du nombre 19 et comporte 2 chiffre(s)
Lexeme ';' trouve a la ligne 5
<:vspace>
Fin de fichier detecte !
Fin de l'analyse !
Resultat :
-- Succes ! --
(:sourcend:)
L'analyseur a sans surprise reconnu tout les mots. Le fichier passe bien à l'analyse lexicale. Cependant on voit bien que la syntaxe n'est pas du tout bonne, elle ne respecte clairement pas la grammaire de Simple défini au chapitre précédent. L'analyse lexicale ne suffit donc pas pour faire de la compilation. Il est essentiel de faire une analyse syntaxique. C'est ce que nous allons tout de suite voir au chapitre suivant.
<< Syntaxe du langage Simple | Analyseur lexical | Analyseur syntaxique >>
Thomas - (CC BY-NC-SA 3.0 FR)