Préprocesseur
En informatique, un préprocesseur est un programme qui procède à des transformations sur un code source, avant l'étape de traduction proprement dite (compilation ou interprétation). Les préprocesseurs sont des exemples typiques de langages spécialisés. Selon le niveau de leur analyse du texte source, on distingue des préprocesseurs lexicaux et des préprocesseurs syntaxiques.
Préprocesseurs lexicaux
Les préprocesseurs lexicaux sont les préprocesseurs de plus bas niveau, au sens où ils requièrent uniquement un mécanisme d'analyse lexicale. Ces préprocesseurs se contentent de procéder à des substitutions de chaînes de caractères en fonction de règles définies par l'utilisateur. On les utilise généralement pour instancier des macros, inclure d'autres fichiers (par opposition à des fonctionnalités de plus haut niveau telles que l'inclusion de modules/paquets/unités/composants) et permettre la compilation ou/et l'inclusion conditionnelle.
Préprocesseur C/C++
Le préprocesseur le plus utilisé est Préprocesseur C (CPP) ; il est employé à chaque étape dans le monde C/C++.
Inclusion
L'usage le plus fréquent du préprocesseur C est la directive #include "<>" dont le rôle est de recopier le contenu d'un fichier dans le fichier courant.
Si cet usage du préprocesseur permet d'ajouter à moindre coût la réutilisation de code à un langage de programmation, il s'agit d'une technique lente, inefficace, et qui nécessite l'ajout manuel de directives de compilation conditionnelle pour éviter d'inclure et de compiler plusieurs fois un fichier d'en-tête.
Depuis les années 1970, des alternatives plus rapides, plus sûres et plus efficaces sont connues et employées dans la majorité des langages de programmation : Java comporte des paquets, Pascal des unités, Modula, OCaml, Haskell ou Python des modules. De même, le langage D, conçu comme remplacement de C et C++ est capable d’« importer » du code depuis un autre fichier.
Macros
Le mécanisme des macros est fréquemment utilisé en C pour définir de petits extraits de code qui seront réutilisés à divers endroits du programme.
Les langages de programmation plus modernes se passent généralement de méta-programmation par macro-expansion de chaînes de caractères, et utilisent généralement des fonctions/méthodes traditionnelles, recopiées par le compilateur en ligne en fonction d'optimisations automatiques ou guidées. De même, plutôt que d'utiliser des macros pour définir des structures de données, ces langages utilisent souvent du polymorphisme paramétrique (aussi appelé templates, alias méthodes/classes génériques).
Les langages Lisp, cependant, sont intégralement conçus autour d'une utilisation similaire des macros, mais à l'aide de techniques bien plus puissantes.
Compilation conditionnelle
Le préprocesseur C permet aussi la compilation conditionnelle, ce qui permet de disposer de plusieurs versions d'un même programme ou d'un extrait dans un même fichier source. Typiquement, les programmeurs C utilisent cette technique pour pouvoir compiler différemment le programme en fonction de son état d'avancement, de la plateforme de destination, des tests désirés, ou pour s'assurer que les fichiers d'en-tête ne sont inclus qu'une seule fois.
La majorité des langages de programmation modernes n'utilisent pas cette fonctionnalité et dépendent plutôt d'une utilisation des habituels opérateurs if…then…else…, laissant au compilateur la tâche de supprimer le code inutile.
Autres préprocesseurs lexicaux
- Le langage généraliste m4 est utilisé essentiellement dans la conception de systèmes de compilation multiplate-forme.
- Le langage généraliste php (Hypertext Preprocessor) est utilisé essentiellement pour la conception de sites web.
Préprocesseurs syntaxiques
La notion de préprocesseur syntaxique a été introduite avec le langage Lisp. Son rôle est de transformer des arbres de syntaxe en fonction de règles définies par l'utilisateur. Pour certains langages de programmation, ces règles sont écrites dans le même langage que le programme (introspection statique). C'est le cas de Lisp ou d'OCaml. Dans d'autres cas, il s'agit d'un langage externe entièrement, tel que XSLT pour XML, ou de sa contrepartie statiquement typée CDuce.
Les préprocesseurs syntaxiques sont typiquement utilisés pour personnaliser la syntaxe d'un langage, pour étendre un langage avec de nouvelles primitives ou pour transformer un langage de programmation généraliste en un langage spécialisé.
Personnalisation de la syntaxe
Un bon exemple de personnalisation de syntaxe est l'existence de deux syntaxes distinctes pour le langage OCaml. Un programme peut être écrit indifféremment à l'aide de la « syntaxe normale » ou de la « syntaxe révisée », et peut être réaffiché à la demande dans une syntaxe ou l'autre.
De même, un grand nombre de programmes écrits en OCaml personnalisent la syntaxe du langage pour lui ajouter de nouveaux opérateurs.
Extension d'un langage
Le meilleur exemple d'extension d'un langage à l'aide de macros est Lisp. Alors que les langages de la famille, par eux-mêmes, ne sont que des noyaux fonctionnels minimalistes (moins de 10 instructions) et dynamiquement typés, la distribution standard de Scheme ou de Common Lisp permet la programmation impérative ou orientée objets, ou encore le typage statique. Toutes ces fonctionnalités sont implantées à l'aide du préprocesseur syntaxique.
De la même manière, la gestion d'expressions rationnelles ou la génération de code statiquement vérifiées et fortement typées peuvent être ajoutées à la syntaxe et la sémantique d'OCaml à l'aide de macros, de même que des fibres (ou coroutines, ou générateurs), des monades ou la manipulation transparente d'arbres XML.
Spécialisation d'un langage
Une fonctionnalité inhabituelle de Lisp est la possibilité d'utiliser des macros pour transformer le langage en un langage spécialisé. Typiquement, dans tout projet Lisp suffisamment large, une partie de l'effort est concentré sur l'adaptation du langage lui-même à la tâche visée, et la construction, par exemple, de dialectes pour SQL ou pour l'affichage ou l'interface graphique, etc. D'après des programmeurs Lisp expérimentés, cette fonctionnalité leur permet de diviser par 7 à 20 fois le temps et la longueur du code nécessaire pour achever un projet.
Le préprocesseur MetaOCaml fournit des fonctionnalités similaires pour la conception de langages spécialisés externes. Ce préprocesseur, à partir de la description de la sémantique d'un langage (i.e. un interpréteur), à l'aide d'interprétation durant la compilation et de génération de code, convertit cette description en un compilateur dans le langage Ocaml.