Programmation Ada : packages et applications en ligne de commande

Dans le volet précédent de cette série, nous avons examiné comment configurer un environnement de développement Ada, ainsi que comment compiler et exécuter une application Ada simple. En nous appuyant sur cette base, nous allons maintenant voir comment créer des applications plus complexes, ainsi que comment analyser et utiliser les arguments transmis aux applications Ada sur la ligne de commande (CLI). Après tout, transmettre des indicateurs et des chaînes aux applications CLI lorsque nous les lançons est une partie cruciale de l'interaction de l'utilisateur, ainsi que lors de l'automatisation des systèmes, comme c'est le cas des services système.

La manière dont un programme est construit est également essentielle, car un code bien organisé facilite la maintenance et favorise la réutilisabilité du code, par exemple grâce à la modularité. Dans Ada, vous pouvez organiser des sous-programmes (c'est-à-dire des fonctions et des procédures) de manière déclarative en tant qu'unités autonomes, ainsi qu'intégrer des sous-programmes dans d'autres sous-programmes. Une autre option concerne les packages, qui correspondent à peu près aux espaces de noms C++, tandis que les types balisés sont l'équivalent des classes. Dans l'article précédent nous avons déjà vu l'utilisation d'un package, lorsque nous utilisions le Ada.Text_IO package pour afficher le texte vers la CLI. Dans cet article, nous verrons comment écrire le nôtre tout en gérant les entrées en ligne de commande, après un mot sur le rôle de la phase de liaison lors de la construction d'une application Ada.

Reliure et liaison

La tâche principale du classeur dans GNAT est d'effectuer l'élaboration. Dans la plupart des langages, un éditeur de liens est exécuté immédiatement après le compilateur, reliant les objets de code compilés à l'aide de symboles définis et exportés dans un binaire fini. En revanche, le but de l'élaboration est de prendre le résultat du compilateur et de déterminer (élaborer) la manière dont les unités s'emboîtent en utilisant les informations écrites dans .ali fichiers (abréviation de Ada Library Information) par le compilateur. Si aucune erreur n'est détectée, le classeur génère un programme principal, qui est compilé et lié avec les autres fichiers objets dans le binaire final.

Le principal avantage de cette approche est celui de la cohérence, car par exemple en C et C++, il est possible de lier des objets incompatibles, ce qui entraîne un comportement étrange et des plantages étranges qui n'ont aucun sens jusqu'à ce que vous réalisiez que par exemple vous utilisiez les mauvais fichiers d'en-tête pour aller avec une bibliothèque particulière. Lorsqu'il s'agit de créer des liens avec des bibliothèques ou des fichiers objets écrits dans un autre langage qu'Ada, ce processus d'élaboration est contourné et les problèmes peuvent ne survenir qu'à la première exécution de l'application. Bien qu'Ada définisse la prise en charge d'une interface facile avec C, COBOL et Fortran (et C++ dans GNAT) via le package Interfaces, il s'agit d'un compromis à garder à l'esprit.

Nous examinerons l'interface avec d'autres langages dans un prochain article, mais pour l'instant, nous considérerons uniquement le code Ada pur.

Commencer une dispute

La chaîne de texte que nous transmettons au shell avec le nom de l'application est analysée et rendue disponible dans le code de l'application sous la forme d'une collection de fragments de texte, divisés par les espaces qui les séparent. Utiliser ces données de manière efficace peut s'avérer quelque peu délicat, surtout si l'on souhaite offrir à l'utilisateur la possibilité de spécifier des drapeaux et des données spécifiques à ces drapeaux. Pour cette raison, vous souhaiterez peut-être utiliser une bibliothèque existante ou similaire qui fournit des fonctionnalités telles que la définition d'un message d'aide et l'analyse automatique des indicateurs.

À titre d'exemple, le projet Sarge que j'ai écrit pour C++ et Ada fournit ces fonctionnalités, ainsi qu'un exemple de projet que nous pouvons analyser avec le code de la bibliothèque. La version Ada se présente sous la forme d'un package unique appelé Sargeque l'on peut ensuite utiliser avec l'application : with Sarge;

Parce que contrairement à la version C++, Ada Sarge est uniquement contenu dans un package et non dans un type balisé, nous ne créons pas d'instance. Cela signifie que pour par exemple définir un indicateur que la bibliothèque doit analyser, nous écrivons ce qui suit :

Sarge.setArgument(+"h", +"help", +"Get help.", False);

Cela demandera à Sarge de rechercher le long (-h) et court (--help) drapeaux. Le booléen false indique que nous ne nous attendons pas à ce qu'une chaîne suive cet indicateur. Le + Le préfixe sur les littéraux de chaîne dans la liste d'arguments n'est pas une fonctionnalité standard d'Ada, mais une fonction renommée pour convertir les chaînes limitées en chaînes illimitées :

function "+"(S : in String) return Unbounded_String renames Ada.Strings.Unbounded.To_Unbounded_String;

Le type de chaîne par défaut dans Ada est une chaîne limitée, ce qui signifie qu'elle a une longueur fixe. Puisque nous ne savons pas à l'avance quelle sera la longueur d'un nom d'indicateur ou d'une chaîne d'argument CLI, nous utilisons des chaînes illimitées du Ada.Strings.Unbounded emballer. Étant donné qu'Ada est un langage fortement typé, la conversion d'une chaîne littérale limitée en une chaîne illimitée doit être effectuée explicitement, mais nous ne souhaitons pas taper le même nom de fonction long encore et encore. Ergo, nous le renommeons en une fonction simple appelée + qui est placé devant les chaînes littérales, ce qui gère cela de manière presque invisible.

Analyse utile

Nous sommes maintenant prêts à analyser l'entrée CLI, qui est gérée par un simple appel à Sarge :

if Sarge.parseArguments /= True then
	put_line("Couldn't parse arguments...");
	return;
end if;

Cela démontre également la façon dont dans Ada les comparaisons sont notées, « égal » étant = et « pas égal » étant /=par rapport à l'opérateur d'affectation étant :=. Avec cette instruction if/else, nous nous assurons que Sarge n'a eu aucun problème à analyser l'entrée CLI à l'aide de l'instruction parseArguments() fonction. Comme cette fonction est trop longue pour être répertoriée ici, n'hésitez pas à consulter l'intégralité du fichier ici, pendant que je soulignerai les éléments saillants ici.

Les fonctions qui nous intéressent sont dans le Ada.Command_Line package, qui nous donne accès à tout ce que le shell a transmis à notre application, comme le nom de l'exécutable :

execName := +Ada.Command_Line.command_name;

Ici encore, nous convertissons la chaîne limitée en chaîne illimitée, avant de passer au filtrage des indicateurs. Les drapeaux courts commencent par un seul tiret, tandis que les drapeaux longs commencent par un double tiret. Bien qu'il ne s'agisse pas d'une norme Ada, il s'agit d'une manière assez conventionnelle de transmettre des indicateurs entre plates-formes. Cela nous permet de différencier les deux types d'indicateurs, car nous vérifions chaque argument CLI dans une boucle, comme dans cette version condensée de la fonction réelle :

for arg_i in 1..Ada.Command_Line.argument_count loop
    arg := +Ada.Command_Line.Argument(arg_i);

    if Ada.Strings.Unbounded.Slice(arg, 1, 1) = "-" then
        if Ada.Strings.Unbounded.Slice(arg, 1, 2) = "--" then
            -- Long form of the flag.
            -- First delete the preceding dashes.
            arg := Ada.Strings.Unbounded.Delete(arg, 1, 2);
            if not argNames.contains(arg) then
                Ada.Strings.Unbounded.Text_IO.put_line("Long flag " & arg & " wasn't found");
                return False;
            end if;
                
            flag_it := argNames.find(arg);
            args(argNames_map.Element(flag_it)).parsed := True;
        end if;
    end if;
end loop;

Il se passe pas mal de choses dans cette boucle, mais essentiellement nous lisons chacune des valeurs d'argument transmises sur la CLI en commençant à l'index 1 (car 0 est toujours le nom de l'exécutable), en boucle jusqu'à ce que nous atteignions le nombre total d'arguments, après auquel la boucle se termine. Chaque argument est à nouveau converti en chaîne illimitée, après quoi nous vérifions dans une comparaison s'il commence par un tiret. Si c'est le cas, nous pouvons vérifier s'il s'agit d'un type d'indicateur court ou long. Dans cette version condensée, nous recherchons uniquement la forme longue, mais la version courte est similaire.

S'il s'agit d'un indicateur inconnu (c'est-à-dire non défini via setArgument), puis nous renfloussons et retournons false. Dans la version complète, nous garantissons également que si un indicateur nécessite une valeur pour le suivre, c'est bien le cas. Les indicateurs et les valeurs sont stockés à la fois dans une carte et dans un vecteur, qui sont deux des conteneurs de bibliothèque standard.

Arguments contenus

La sortie de l'application de test Sarge avec l'indicateur -h.
La sortie de l'application de test Sarge avec l'indicateur -h.

Lorsque nous définissons un drapeau à rechercher dans Sarge, il est ajouté au argNames carte, ainsi que la args vector, ce dernier contenant le Record complet (comme une structure en C). La carte est utilisée pour fournir un index facilement consultable sur le vecteur qui contient les enregistrements. Puisque ce sujet nous amène à plonger assez profondément dans les conteneurs dans Ada, cela sera traité en profondeur dans l'article suivant de cette série.

Nous terminerons cet article en expliquant rapidement comment l'API Sarge est utilisée pour accéder aux arguments CLI analysés. Comme on peut le voir sur la capture d'écran, nous pouvons demander le nombre total d'indicateurs trouvés, et demander par indicateur s'il a été trouvé, ou obtenir directement sa valeur (le cas échéant) s'il a été trouvé. Cela nous permet ensuite d'agir sur n'importe quel indicateur trouvé, ainsi que sur un segment de texte de fin (par exemple, un chemin de fichier), transformant quelque chose comme Sarge en un package modulaire qui peut être utilisé avec n'importe quelle application basée sur CLI sans avoir à écrire tous les codez à nouveau juste pour analyser les arguments CLI.

Comme indiqué, dans le prochain article, nous examinerons l'utilisation des conteneurs standard Ada, ainsi que les types d'enregistrements. N'hésitez pas à vous exprimer dans les commentaires sur tout sujet connexe que vous aimeriez voir abordé.

François Zipponi
Je suis François Zipponi, éditorialiste pour le site 10-raisons.fr. J'ai commencé ma carrière de journaliste en 2004, et j'ai travaillé pour plusieurs médias français, dont le Monde et Libération. En 2016, j'ai rejoint 10-raisons.fr, un site innovant proposant des articles sous la forme « 10 raisons de... ». En tant qu'éditorialiste, je me suis engagé à fournir un contenu original et pertinent, abordant des sujets variés tels que la politique, l'économie, les sciences, l'histoire, etc. Je m'efforce de toujours traiter les sujets de façon objective et impartiale. Mes articles sont régulièrement partagés sur les réseaux sociaux et j'interviens dans des conférences et des tables rondes autour des thèmes abordés sur 10-raisons.fr.