Les deux niveaux de syntaxe de Common Lisp

Les langages de programmation traditionnels comme C, C++, Java, Fortran, Eiffel, etc, ont une syntaxe définie par les suites de tokens acceptées par le compilateur. Ces suites sont le plus souvent définies par une grammaire sans contexte complétée par des phrases en anglais ou français. Ces phrases expriment des restrictions par rapport à ce qu'il est possible d'exprimer avec la grammaire sans contexte. Pour le langage C, par exemple, la grammaire exprime la possibilité d'écrire une affectation comme x = y, mais les restrictions sur le type de x et y sont exprimées dans le texte annexe et non pas par la grammaire.

Un programmeur d'un de ces langages pense typiquement en terme de ces suites de tokens pour créer un programme valable. Il n'a en général aucune idée du fait que le compilateur stocke les programmes sous la forme d'arbres syntaxiques. D'ailleurs, chaque compilateur peut stocker les programmes de sa propre façon et certains compilateurs simples peuvent même traiter la suite de tokens telle qu'elle.

Chaque construction du langage a sa propre collection de règles sous forme de suite de symboles de la grammaire. Chaque type de boucle, chaque conditionnelle, etc ; ce sont des formes spéciales du langage. Chaque forme spéciale a de plus une explication de la règle d'évaluation à utiliser, à savoir dans quel ordre les différentes parties sont exécutées.

Common Lisp utilise une grammaire à deux niveaux. Le premier niveau est presque trivial : une expression est soit une constante, soit une variable, soit une parenthèse ouvrante suivie d'une suite d'expressions suivie d'une parenthèse fermante. Cette syntaxe est implémentée par la fonction read, qui fait partie de la spécification du langage Common Lisp. Autrement dit, l'analyseur syntaxique du langage Common Lisp fait partie du langage même. Le résultat de cette analyse syntaxique est une représentation interne du programme sous la forme de données du langage : constantes, symboles, listes, etc. Chaque programme Common Lisp a donc une représentation normalisée sous la forme de donnée Common Lisp.

La représentation interne d'un programme Common Lisp est donc une traduction immédiate de la syntaxe superficielle. Elle est tellement immédiate que le programmeur Common Lisp pense à son programme en termes de représentation interne.

La syntaxe du deuxième niveau définit les structures internes acceptées par le compilateur. Comme avec les langages traditionnels, chaque construction du langage a ses propres règles. Mais en Common Lisp cette syntaxe est exprimée sous forme de listes et d'éléments de listes. Le deuxième niveau n'est pas trivial. Il est aussi compliqué que la syntaxe d'un langage traditionnel.

Le fait d'avoir une représentation interne structurée d'un programme Common Lisp, simplifie énormément l'écriture de programmes dont le but est de manipuler (analyser, transformer, générer) d'autres programmes. C'est exactement ce que fait le programmeur Common Lisp lorsqu'il écrit une macro. Le code source de la macro (l'appel) est analysé par le corps de la macro et ce dernier est chargé de générer un programme pour remplacer l'appel. Le code source et le remplacement sont manipulés sous la forme de représentation interne. Il serait impossible d'envisager un système de macros avec une représentation à manipuler sous forme de suite de caractères ou suites de tokens.

Irene DURAND
2011-10-14