Cet article est le second d’une série d’articles présentant les étapes de développement d’un langage de programmation. La série d’articles débute ici.
La première étape effectuée par un compilateur (ou un interpréteur) est l’analyse lexicale (ou « scanner » ou « lexer ») d’un code source. L’analyseur lexical permet de transformer les informations du code source (mots clés, identificateurs, constantes, etc.), nommées lexème, en jeton (« token »). Un jeton est un type d’information qui sera plus facile à travailler et à manipuler que du texte dans les autres étapes de la compilation.
Par exemple, le programme Java suivant:
/**
* Programme exemple
*
* @author Louis Marchand (prog@tioui.com)
* @version 0.1, vendredi jui 02, 2021 16:57:14 EDT
*
* Distribué sous licence MIT.
*/
public class Test {
/**
* Exécution du programme.
*
* @param aArguments Les paramêtres du programme.
*/
public static void main(String[] aArguments) {
System.out.println("Bonjour à tous");
}
}
sera convertie en la suite de jeton suivant:
COMMENTAIRE PUBLIC CLASS IDENTIFICATEUR ACCOLADE_OUVRANTE COMMENTAIRE
PUBLIC STATIC VOID IDENTIFICATEUR PARENTHESE_OUVRANTE IDENTIFICATEUR
CROCHET_OUVRANT CROCHET_FERMANT IDENTIFICATEUR PARENTHESE_FERMANTE
ACCOLADE_OUVRANTE IDENTIFICATEUR POINT IDENTIFICATEUR POINT IDENTIFICATEUR
PARENTHESE_OUVRANTE CHAINE PARENTHESE_FERMANTE POINT_VIRGULE ACCOLADE_FERMANTE
ACCOLADE_FERMANTE
Ici, bien entendu, c’est une représentation imagée en texte de ce qui est stocké par l’analyseur lexical. En réalité, il s’agit d’une instance propre au langage qui sera utilisé pour créer le compilateur (par exemple, une structure si le compilateur est créé en C ou une classe si le compilateur est créé en C++). Minimalement, un jeton peut être tout simplement un index (entier) unique par type de lexème différent. Par contre, en général, on garde dans le jeton certaines informations qui pourront aider les prochaines étapes de la compilation à s’exécuter comme ils se doivent. Par exemple, on peut stocker dans le jeton, le nom du fichier source, la ligne et la colonne (qui pourront être utilisés pour donner des messages d’erreur plus efficace lors de la compilation) ou le texte d’origine du jeton (qui pourra être utilisé pour interpréter les constantes ou les identificateurs par exemple).
La création d’un analyseur lexical se fait en spécifiant un fichier de descriptions à un générateur d’analyseur lexical. Un fichier de description contient une série d’expression régulière qui permettra de reconnaître les lexèmes du langage. Une fois exécuter, le générateur d’analyseur lexical créera du code source qui pourra être intégré dans le code source du compilateur afin d’effectuer sa tâche.
Il existe une grande quantité de générateurs d’analyseur lexical qui peuvent créer du code source dans différents langages de programmation. Par exemple, « Lex » génère en C, « Flex » génère en C ou C++, « C# Lex » génère en C#, « JLex » génère en Java, etc. Dans le cas du langage Triumph qui est présentement en cours de développement, le compilateur sera créé dans le langage de programmation Eiffel. Le générateur d’analyseur lexical qui sera utilisé devra donc être capable de générer du code source Eiffel. C’est pour cette raison que j’utiliserai le générateur « gelex » de la série d’outils Gobo.
Voici un lien vers le fichier de description que j’ai créé pour le langage de programmation Triumph: ici. Il s’agit ici d’une version initiale qui pourrait changer en cours de développement.
Voici les différentes sections du fichier de description:
- Les commentaires sont débutent par des « –« ;
- Les lignes 1 à 19 représentent l’entête de la classe Eiffel qui sera généré par le générateur d’analyseur lexical « Gelex »;
- La ligne 21 représente des options de génération. En gros, j’indique à Gelex de générer un analyseur lexical qui gère la position des lexèmes dans le fichier source, qui ne possède aucune règle par défaut dans le cas où aucun lexème ne serait trouvé pour une séquence donnée (je n’en ai pas besoin) et qui devra créer le fichier source Eiffel « triumph_scanner.e »;
- Les lignes 23 à 49 déclarent des expressions régulières que je pourrai me servir dans le reste du fichier;
- La ligne 47 indique à Gelex qu’il s’agit du début des descriptions des lexèmes;
- Les lignes 49 à 55 correspondent aux expressions régulières qui représentent les séparateurs de lexème dans le langage (ces caractères sont complètement ignorés puisqu’ils ne génèrent pas de jeton);
- Les lignes 58 à 150 représentent tous les opérateurs du langage Triumph (on voit que plusieurs opérateurs peuvent générer le même jeton, comme « > » ou « <« );
- Les lignes 155 à 310 représentent tous les mots-clés du langage Triumph (encore une fois, on voit que plusieurs opérateurs peuvent générer le même jeton, comme « or » ou « and »);
- La ligne 317 représente les identificateurs (Nom de classes, attributs, méthodes, variables, argument, etc.),
- Les lignes 323 à 337 représentent les constantes entières en version décimale, hexadécimale, octale ou binaire;
- Les lignes 339 à 342 permettent de représenter une erreur plus intéressante qu’un simple « syntaxe error » lorsque le code source présente une mauvaise représentation de nombre octal ou binaire (cette description aurait pu ne pas être déclarée et le langage aurait fonctionné quand même);
- Les lignes 347 à 349 représentent les constantes en virgule flottante;
- Les lignes 354 à 368 représentent les constantes caractères et chaînes de caractères;
- La ligne 374 représente les commentaires dans le langage Triumph;
- La description à la ligne 378 détecte la fin du fichier source et arrête l’analyseur lexical;
- Puisque, pour chaque lexème, les descriptions sont testées en ordre de leurs apparitions dans le fichier de description, la description à la ligne 380 permet de donner une erreur dans le cas ou un lexème n’aurait pas été détecté par l’analyseur;
- La ligne 386 indique à Gelex qu’il s’agit de la fin des descriptions du fichier
- Le reste du fichier représente le pied de classe de la classe Eiffel qui sera généré par Gelex.
Malgré qu’il y a quelques possibilités d’erreur gérée par l’analyseur lexical, on peut facilement voir que cet outil ne vérifie pas que le code source analysé respecte le langage cible. Tout ce que cette étape a fait, c’est de valider que les lexèmes sont valident et ensuite transformer ces derniers en jeton. Par exemple, malgré que le code Java suivant ne fasse aucun sens:
static "Coucou" [ System class
} variable if
un analyseur lexical Java pourrait tout de même faire l’entièreté de sont travail puisque tous les lexèmes sont valident en Java.
Afin de vérifier que les jetons générés respectent réellement le langage, le compilateur devra faire une analyse plus en détail de ces jetons. La prochaine étape d’analyse se nomme l’analyseur syntaxique et sera le contenu du prochain article.