Le compilateur MLang

Installer

Mlang est implanté en OCaml. L’utilisation du gestionnaire de paquets OCaml opam est fortement recommandée. Vous pouvez l’installer via votre gestionnaire de paquet préféré s’il distribue opam, ou bien en vous referant à la documentation d’opam. Mlang a également quelques autres dépendances, dont une vers la librairie de calcul de flotants MPFR. Si vous êtes sous Debian, vous pouvez simplement utiliser la commande suivante pour installer toutes les dépendances externes à OCaml :

$ sudo apt install \
	libgmp-dev \
	libmpfr-dev \
	git \
	patch \
	unzip \
	bubblewrap \
	bzip2 \
	opam

Si vous n’avez jamais utilisé opam, commencez par lancer :

$ opam init
$ opam update

Enfin, vous pouvre initialiser le projet mlang avec

$ make init

Note pour les utilisateurs d’opam confirmés : la commande make init crée un switch local où seront installées les dépendances OCaml de mlang.

Cette commande initialise le dossier ir-calcul dans lequel sont poussés le code de calcul primitif de l’impot sur le revenu.

Si besoin, la commande

$ make deps

réinstallera les dépendances OCaml et mettra à jour le dossier ir-calcul.

Une fois compilé, vous pouvez soit appeler mlang via la commande :

$ opam exec -- mlang

tant que vous êtes dans le dossier depuis lequel vous avez compilé, soit l’installer localement avec la commande :

opam install ./mlang.opam

Utiliser MLang

Le binaire mlang prend en argument le fichier M à exécuter. Il peut également prendre en argument une liste de fichiers, auquel cas leur traitement sera équivalent au traitement d’un seul et même fichier dans lequel serait concatené le contenu de chaque fichier.

Les options principales sont :

  • -A: le nom de l’application à traiter;

  • -b: le mode d’utilisation, ou backend;

  • --mpp_function: le nom de la fonction principale à traiter.

Mode interpreteur

Le mode interpreteur de mlang utilise un fichier IRJ (voir Les fichiers IRJ) pour exécuter le code M directement depuis sa représentation abstraite.

Voici une commande simple pour invoquer l’interpreteur :

$ mlang test.m \
	-A mon_application \
	-b interpreter \
	--mpp_function hello_world \
	--run_test test.irj \
	--without_dfgip_m

Mode transpilation

Le mode transpilation de mlang permet de traduire le code M dans un autre langage. En 2025, seul le langage C est supporté. Voici une commande simple pour traduire un fichier M en C :

$ mlang test.m \
	-A mon_application \
	-b dgfip_c \
	--mpp_function hello_world
	--dgfip_options='' 
	--output output/mon-test.c

NB: le dossier output doit avoir été créé en amont.

Options DGFiP

Les options DGFiP sont à usage interne. Elles sont spécifiées dans l’option --dgfip_options.

       -b VAL
           Set application to "batch" (b0 = normal, b1 = with EBCDIC sort)

       -D  Generate labels for output variables

       -g  Generate for test (debug)

       -I  Generate immediate controls

       -k VAL (absent=0)
           Number of debug files

       -L  Generate calls to ticket function

       -m VAL (absent=1991)
           Income year

       -O  Optimize generated code (inline min_max function)

       -o  Generate overlays

       -P  Primitive calculation only

       -r  Pass TGV pointer as register in rules

       -R  Set application to both "iliad" and "pro"

       -s  Strip comments from generated output

       -S  Generate separate controls

       -t  Generate trace code

       -U  Set application to "cfir"

       -x  Generate cross references

       -X  Generate global extraction

       -Z  Colored output in chainings

Comportement de Mlang

Le compilateur Mlang effectue son traitement en quatre étapes :

  • la traduction dans un format abstrait interne;

  • un pré-traitement pour le simplifier;

  • une vérification pour analyser la cohérence du code;

  • le traitement du code M, que ce soit son interprétation ou sa compilation.

Le module Driver (et plus précisément la fonction Driver.main) correspond au point d’entrée de mlang.

Traduction

Le langage M est parsé selon les règles spécifiées dans La syntaxe du M. Elle est effecuée par les modules Mparser, Mlexer et Parse_utils.

Pré-traitement

Le prétraitement est une opération purement syntaxique. Elle est effectuée par les modules Expander et Mir. Son but est triple :

  • éliminer les constructions relatives aux applications non-sélectionnées ;

  • remplacer les constantes par leur valeur numérique ;

  • remplacer les expressions numériques débutant par somme avec des additions ;

  • éliminer les <multi-formule>s en les remplaçant par des séries de <formule>s.

Toute substitution transformant un programme M syntaxiquement valide en un texte ne correspondant à aucun programme M provoque l’échec du traitement.

Cas des applications

On élimine du programme M:

  • les déclarations des applications non-sélectionnées ;

  • les déclarations des enchaîneurs ne spécifiant pas une application sélectionnée ;

  • les déclarations des règles ne spécifiant pas une application sélectionnée ;

  • les déclarations des vérifications ne spécifiant pas une application sélectionnée ;

  • les déclarations des cibles ne spécifiant pas une application sélectionnée.

Dans les déclarations des règles spécifiant une application sélectionnée, on élimine les enchaîneurs qui ne sont plus déclarés dans le programme M obtenu.

Cas des constantes

Pour prétraiter un programme, on le parcourt du début à la fin. Pour chaque <variable> rencontrée, si elle correspond à une contante défini précédemment, alors il est remplacé dans le programme par la valeur numérique correspondante.

Un intervalle de la forme <naturel:début>..<symbole:const> est converti par substitution de la constante const en l’intervalle <naturel:début>..<naturel:fin>, avec fin le naturel correspondant à const.

Exemple : considérons le programme suivant :

fin : const = 10;
… 
pour i = 9-fin :
  Bi = Bi + fin;

Par substitution de la constante fin, il est remplacé dans un premier temps remplacée par le programme :

fin : const = 10;`
… 
pour i = 9..10 :
  Bi = Bi + fin;

puis dans un second temps par le programme :

fin : const = 10;
…
pour i = 9..10 :
  Bi = Bi + 10;

Interprétation des indices

Pour rappel, les indices ont la forme suivante :

<indices> ::= <indice> ; …
<indice> ::= <minuscule> = <intervalle> , …

avec :

  • <minuscule> ::= [a-z]

  • <majuscule> ::= [A-Z]

  • <intervalle> ::= <majuscule>+ | <majuscule> .. <majuscule> | <naturel> (.. <naturel> | - <variable>)?

Chaque <indice> associe à une lettre minuscule une série de chaînes de caractères de même taille. Cette série est composée de la succession de chaque à droite du symbole =.

Les séries associées aux sont définis comme suit :

  • <majuscule>+ est la série composée de chacune des majuscules prises séparément, donc de taille 1 (par exemple : AXF représente la série A, X, F);

  • <majuscule:/début/>****<majuscule:/fin/> est la série composée de toutes les majuscules comprises entre /début/ et /fin/, bornes comprises, suivant l’ordre alphabétique, donc de taille 1 (par exemple : A..D représente la série A, B, C, D);

  • <naturel:début>..<naturel:fin> est la série composée de tous les nombres naturels entre début et fin, bornes comprises, la taille des éléments étant égale à la taille du plus grand naturel en base 10; les naturels trop petits pour avoir la taille requise sont complétés par des 0 à gauche (par exemple : 9..11 représente la série 09, 10, 11);

  • <naturel>-<variable> est converti lors du prétraitement des constantes en un intervalle de la forme <naturel>..<naturel>.

Cas des expressions somme(…)

Pour une expression numérique somme ( <indice:ind>: <expression numerique:expr> ), l’expression expr sera remplacée par la somme des expressions construites ainsi : pour chaque chaîne de caractères de la série introduite par ind, les symboles apparaîssant dans expr sont remplacés par ceux dans lesquels la lettre minuscule de /ind/ est substituéé par la chaine de caractère.

La somme ainsi générée est parenthésée.

Une expression numérique somme ( <indice:ind> ; <indices:inds> : <expression numérique:expr> ), est remplacée par la somme des expressions somme <indices:inds> : expr'> ) avec expr' prenant sa valeur dans la série sub(ind, expr).

La procédure est ensuite appliquée récursivement à cette somme.

La somme ainsi générée est parenthésée.

Notons que les sous-sommes récursivement produites n’ont pas besoin de l’être car l’addition est associative.

On applique cette transformation à toutes les expressions somme(…) tant qu’il en existe dans le programme.

Exemple. Considérons l’expression suivante :

  • somme(i = XZ ; j = 9..10 : Bi + Bj)

Elle est dans un premier temps remplacée par la somme suivante :

  • (somme(j = 9..10 : BX + Bj) + somme(j = 9..10 : BZ + Bj))

puis dans un second temps par la somme :

  • (BX + B09 + BX + B10 + BZ + B09 + BZ + B10)

On remarque que seule l’expression globale est parenthésée car l’addition est associative.

Cas des multi-formules

On commence par substituer toutes les expressions somme(…) apparaissant dans les multi-formules.

Puis on transforme les multi-formules en séries de formules en suivant la méthode décrite ci-dessous.

Pour chaque multi-formule rencontrée, de la forme pour <indices> : <formule>, dès que les constantes apparaîssant dans les ont été substitués, on remplace cette multi-formule par la série de formules générée de la manière suivante.

Pour une multi-formule pour <indice:ind> : <formule:f>, la formule f sera remplacée par la série des formules construites ainsi : pour chaque chaîne de caractères de la série introduite par ind, les symboles apparaîssant dans f sont remplacés par ceux dans lesquels la lettre minuscule de ind est substituéé par la chaïne de caractère.

On notera sub(ind, f) la série constituée des formules ainsi régérées.

Une multi-formule pour <indice:ind> ; <indices:inds> : <formule:f>, est remplacée par la série des multi-formules pour <indices:inds> : f'> avec f' prenant sa valeur dans la série sub(ind, f).

La procédure est ensuite appliquée récursivement à ces multi-formules.

Exemple. Considérons la multi-formule suivante :

pour i = XZ ; j = 9..10 :
  Bi = Bi + Bj;

Elle est dans un premier temps remplacée par la série suivante :

pour j = 9..10 :
  BX = BX + Bj;
 
pour j = 9..10 :
  BZ = BZ + Bj;

Puis dans un second temps par la série des formules :

BX = BX + B09;
BX = BX + B10;
BZ = BZ + B09;
BZ = BZ + B10;

Vérification de cohérence

De nombreuses constructions sont valides à la traduction, mais brisent certains invariants nécessaires à la bonne exécution du code : double déclaration d’attributs, nom de variable déjà utilisé, variable mal typée… L’ensemble de ces vérifications est accessible dans le module Validator du frontend. Le sous module Validator.Err définit l’ensemble des erreurs levées par cette étape de vérification.

NB : seule la première erreur rencontrée par le validateur est levée.

Traitement

Interpreteur

L’interpréteur utilise la représentation interne du code M pour lancer le calcul à partir d’un fichier IRJ (voir Les fichiers IRJ). L’interprétation est effecuée par le module Test_interpreter.

Transpilation

La transpilation traduit le code dans le langage spécifié (en 2025, seul le C est transpilable). La transpilation est effecutée dans le module Bir_to_dgfip_c.