drik's wiki informatique Languages ADA 95

Présentation du langage

Historique

ada 95
  • Fortran (1956)
  • Cobol / Algol / PL1 (1960)
  • Basic (1965)
  • Simula (1967)
  • Pascal (1969)
  • Ada (1974)
  • C (1978)
  • C++ (1986)

En 1970, l'attention du département de la Défense des Etats-Unis fut attirée par la tendance à l'augmentation des coûts des logiciels développés dans ses services. Avant cette époque, les coûts de matériels dépassaient largement les coûts de logiciels.

De 1968 à 1973, le DoD (United States Department of Defense) enregistra un accroissement de 51% du coût direct de ses logiciels, alors même que les coûts des matériels diminuaient de façon spectaculaire. Après avoir étudié le problème, il apparut clairement :

  • que de très nombreux langages de programmation différents étaient utilisés.
  • que certaines applications étaient programmées dans des langages inadéquats.
  • que certains langages ne supportaient pas les principes de la programmation moderne des logiciels.
  • qu'il manquait d'environnements de développement de logiciels.

A cette époque il existait au moins 450 langages généraux pour les systèmes du DoD, bien que suivant la source citée, le nombre réel variait de 500 à 1500.

Nombreux étaient les projets liés à des vendeurs particuliers ou à des technologies obsolètes ; c'est pourquoi nous avons vu des projets utiliser COBOL pour des applications temps-réel, ou des langages d'assemblage pour des systèmes de gestion....

Comme la majorité des coûts de logiciels au DoD était associée à des systèmes informatiques embarqués, le DoD tourna son intérêt vers ce domaine d'application. Par définition, un système informatique embarqué est un système qui forme une partie d'un système plus vaste dont le but principal n'est pas d'effectuer des calculs. Les systèmes embarqués ont des exigences de programmation spécifiques :

  • traitement parallèle,
  • contrôle en temps réel,
  • traitement d'exception,
  • contrôle d'Entrées/Sorties spécialisées.

A l'époque il n'existait pas encore de langage de haut niveau correspondant aux exigences des systèmes embarqués. Le DoD demanda donc la définition du cahier des charges pour un langage de haut niveau capable de satisfaire toutes les exigences en matière de qualité des logiciels. Ce langage devait aussi être unique et à usage général.

  • 1975 : Début de la définition du cahier des charges.
  • 1977 : Appel d'offre international pour rechercher des spécifications applicables au nouveau langage.
  • 1979 : Ada devient le nom officiel du langage de haut niveau du DoD.
  • 1980 : Manuel de référence Ada qui marque la fin de l'effort de conception et de test du langage.
  • 17/02/83 : Le manuel de référence du langage Ada est approuvé comme norme ANSI.
  • 1995 : Suite à une révision du langage, un nouveau standard est créé.

Un aperçu du langage

Généralités

Un programme Ada se compose d'une ou plusieurs unités de programme dont la plupart peuvent être compilées séparément. Toutes ces unités contiennent une spécification (interface) et un corps (réalisation). Les unités de programme comprennent :

  • les sous-programmes
  • les paquetages
  • les unités génériques
  • les tâches
Unité de programme Caractéristiques Applications
Sous-programme Action Séquentielle Unité programme principal. Définition de contrôle fonctionnel.
Définition d'opérations sur des types.
Paquetage Ensemble de ressources Ensemble nommé de déclarations.
Groupement d'unités de programme reliées.
Type de données abstrait.
Machines abstraites à états.
Unité Générique Modèle Composants logiciels réutilisables.
Tache Action parallèle Actions concurrentes.
Routage de messages.
Contrôle de ressources.
Interruptions.
Résumé des unités de programme Ada

Eléments de base du langage

Jeu de caractères

On peut écrire toute construction du langage Ada au moyen d'un jeu de caractères constitué de :

  • lettres majuscules :
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  • chiffres :
0 1 2 3 4 5 6 7 8 9 
  • caractères spéciaux de base :
" ' ( ) * + , - . / : ; < = > _ | &
  • les caractères de formatage :
espace, tabulation, marques de fin de ligne, fin de page, fin de fichier
  • lettres minuscules :
a b c d e f g h i j k l m n o p q r s t u v w x y z 
  • des caractères spéciaux ne rentrant pas dans les constructions du langage :
#! $ ? @ [ ] { } ~ ^ %
Unités lexicales

Les éléments de base du langage appelés unités lexicales sont construits à partir du jeu de caractères Ada. Les unités lexicales comprennent :

  • les identificateurs,
  • les littéraux numériques ou les nombres,
  • les littéraux caractères ou les caractères,
  • les littéraux chaînes ou les chaînes,
  • les délimiteurs,
  • les commentaires.

Les identificateurs sont formés d'une lettre suivie d'une série de lettres, de chiffres, et/ou de caractères "trait bas" (pour améliorer la lisibilité). Les mots réservés (Ada en possède 68) sont :

abort    digits    loop       record    when
abs      do        mod        rem       while
accept   else      new        renames   with
access   elsif     not        requeue   xor
aliased  end       null       return
all      entry     of         reverse
and      exception or         select
array    exit      others     separate
at       for       out        subtype
begin    function  package    tagged
body     generic   pragma     task
case     goto      private    terminate
constant if        procedure  then
declare  in        protected  type
delay    is        raise      until
delta    limited   range      use
Mots réservés Ada

Ada n'impose aucune limitation à la longueur des noms d'identificateurs définis par l'utilisateur à condition qu'ils tiennent sur une seule ligne.

Exemple d'identificateurs valides
Couleurs_de_l_arc_en_ciel
INTERROGER_TERMINAUX
capteur_37

Ada ne fait aucune différence entre les noms écrits en majuscules ou en minuscules. Donc les identificateurs COMPTEUR_37, compteur_37, CompTeur_37 sont identiques.

Les littéraux numériques représentent des valeurs entières ou réelles. Les nombres peuvent être exprimés dans n'importe quelle base de 2 à 16. Les traits bas sont autorisés entre les chiffres d'un nombre pour améliorer la lisibilité et sont ignorés par le compilateur.

Exemples de littéraux numériques entiers légaux
7
1_000_000   --identique à 1000000
1_00_00_00  --même valeur
1e6         --même valeur
2#1100#     --équivalent en base 2 de 12 en base 10
16#C#       --équivalent en base 16 de 12 en base 10
Exemples de littéraux numériques réels légaux
0.125
3.141_592_65  --valeur de PI
2.78e-3       --équivalent à 0.00278
1.0e6         --équivalent à 1000000.0
16#F.0#       --équivalent en base 16 de 15.0 en base 10.

Les nombres réels exigent au moins un chiffre de chaque côté du point décimal.

Un littéral caractère est constitué d'un caractère graphique encadré par des apostrophes simples.

Exemple
'A'   '*'

Une chaîne est une séquence éventuellement vide de caractères encadrée par des apostrophes doubles.

Exemple
""         --chaîne vide
"Bonjour"  --chaîne de longueur 7
""""       --chaîne dont la valeur est "

Les délimiteurs sont les caractères de formatage et les symboles suivants :

  • délimiteurs simples : ' ( ) * + , - . / : ; < = > | &
  • délimiteurs composés : => .. ** := /= >= <= << >> <>

Les délimiteurs ont une signification spéciale qui dépend de leur contexte.

Les commentaires commencent par un double tiret (--) et se terminent sur la fin de ligne.

Un nombre quelconque d'espaces peut séparer deux unités lexicales adjacentes. Chaque unité lexicale doit tenir sur une ligne mais peut être placée n'importe où sur la ligne à l'exception des commentaires qui ne peuvent pas être en début de ligne et suivi d'une instruction.

Concepts généraux

L'objectif de ce chapitre est de présenter les différentes structures du langage Ada pour vous permettre d'écrire vos premiers programmes Ada rapidement.

Types simples

Un type de donnée abstrait caractérise :

  • un ensemble de valeurs : les valeurs du type T sont les valeurs admissibles pour les variables du type T.
  • un ensemble d'opérations : les opérations du type T définissent les manipulations que l'on peut faire sur les variables du type T.

En Ada on dispose de plusieurs classes de type :

  • les types de données scalaires (entier, réel, énumératif),
  • les types de données composés (tableau, enregistrement...),
  • les types accès de données,
  • les types privés de données,
  • les sous-types et types dérivés.

Dans ce chapitre nous ne parlerons que des trois premiers types. Les autres types seront présentés ultérieurement.

Les types de données scalaires

Les types scalaires (non décomposables) comprennent les types réels, et les types discrets entiers et énumérés.

Les types entiers

Les types entiers définissent des ensembles de nombres entiers c'est-à-dire sans partie fractionnaire. Ada fournit un type entier prédéfini et permet la création de nouveaux types entiers.

Exemple de déclaration de variables de type entier prédéfini INTEGER
A,B,VAR3 : INTEGER;

Les variables ainsi déclarées sont de type entier prédéfini. On peut donc leur appliquer toutes les opérations applicables à des entiers classiques.

On pourra écrire entre autre :

A:=5;        --La variable A reçoit 5
B:=6;
VAR3 := (A+B-4)*2  --VAR3 prend comme valeur le résultat de
                   --l'évaluation de l'expression située à
                   --droite du signe d'affectation' :='
Exemple de déclaration d'un type entier
type INDICE is range 1..50;
I:INDICE;  --I est de type INDICE et ne pourra donc prendre que 
           --des valeurs entières comprises entre 1 et 50 inclus.
Les types réels

Les types réels définissent des ensembles de nombres avec une partie fractionnaire (nombres en point flottant ou en point fixe). Ada fournit un type réel flottant prédéfini.

Exemple de déclaration de variables de type réel prédéfini FLOAT
A : float;  -- A est de type réel prédéfini
Exemple de déclaration de type réel
type MASS is digits 10;  --type point flottant
type VOLTAGE is delta 0.01 range -12.0..+24.0;  --type point fixe


A : VOLTAGE;  --A est une variable réelle qui pourra prendre des
              --valeurs comprises entre -12 et 24 avec une
              --précision de 10-2.
Types énumératifs

Une définition de type énuméré déclare en les énumérant toutes les valeurs du type. Les types énumérés BOOLEAN et CHARACTER sont prédéfinis.

Exemple de déclaration d'un type énuméré
type COULEUR is (VERT, ROUGE, ORANGE);
UNE_COULEUR : COULEUR;

Les types de données composés

Le type tableau

Les tableaux sont des ensembles d'éléments de même type. On peut déclarer des types tableau à une ou plusieurs dimensions.

Exemple de déclaration de types tableau
type ECHIQUIER is array (1..8, 1..8) of INTEGER;
type PIXEL is array (INDEX range 5..10) of VOLTAGE;

E:ECHIQUIER;
P:PIXEL;

L'accès à un composant d'un tableau se fait en utilisant le nom de la variable suivi des indices entre parenthèses ; par exemple E(4,7), P(10).

Les types articles

Les articles sont des ensembles d'éléments de types identiques ou différents.

Exemple de déclaration de types article
type DATE is record     
    JOUR : INTEGER range 1..31;
    MOIS : INTEGER range 1..12;
    ANNEE : NATURAL;
    end record;


AUJOURDHUI: DATE; 

On peut renseigner les différents champs de la structure de la manière suivante :

AUJOURDHUI.JOUR := 12;
AUJOURDHUI.MOIS := 4;

Les types accès de données

Les types accès de données correspondent aux notions de pointeurs que l'on retrouve en PASCAL ou C. Le langage Ada offre la possibilité d'allouer dynamiquement de la mémoire via le type accès.

Exemple de déclaration de types access
type POINTEUR is access DATE;  --Chaque variable de type POINTEUR
--désignera un élément de type --DATE défini plus haut. POINTEUR1 : POINTEUR; --Déclaration d'une variable de type POINTEUR.

L'allocation de mémoire se fera de la manière suivante :

POINTEUR1 := new DATE;  --Allocation dynamique de mémoire pour un élément de type DATE.

On peut ensuite renseigner les différents champs :

POINTEUR1.JOUR := 5;
POINTEUR1.MOIS := 12; 
Remarques
  • les variables de type ACCESS sont les seules variables initialisées par défaut en Ada au moment de la compilation. La valeur par défaut est NULL qui signifie que le pointeur ne désigne aucune zone mémoire.
  • contrairement au PASCAL ou au C, le langage Ada ne possède aucune instruction permettant de restituer une zone mémoire à l'espace libre. Dès qu'une zone mémoire allouée dynamiquement n'est plus désignée par aucun pointeur, la zone est automatiquement rendue à l'espace libre.

Les structures de contrôle

Les structures de contrôle permettent de modifier le déroulement séquentiel du programme.

Contrôle conditionnel

Instruction CASE
Syntaxe
case <expression> is  --<expression> est de type discret entier ou énuméré
  when <choix1> => <suite d'instructions>
  when <choix2> => <suite d'instructions>
  when <choix3> => <suite d'instructions>
  ...
  when others   => <suite d'instructions>
end case;
Exemples
case I is                           case UNE_COULEUR is
  when 1    => J := J + 2;            when VERT  => PASSER;
  when 2|3   => J := -1;              when ORANGE => RALENTIR;
  when others => j := 5;                             ARRETER;
end case;                             when ROUGE  => ATTENDRE;
                                    end case;
Instruction IF..THEN..ELSE
Syntaxe
if <condition> then <suite d'instructions>;

if <condition> then        
  if <condition1> then  
    <suite d'instructions1>;      
    <suite d'instructions1>;
  else                
    elsif <condition2> then
      <suite d'instructions2>;      
      <suite d'instructions2>;
  end if;              
elsif
  ...                 
else
  ...
end if;
Exemples
if I = 1 then                   if I = 1 then  
  J := J + 2;                     J := J + 2;
else                            elsif (J = 2) or (J = 3) then
  if (J = 2) or (J = 3) then      J := -1;
    J := -1;                    else  
  else                            J := 5;
    J := 5;                     end if;
  end if;
end if;
Remarque

Le ELSE se rapporte toujours au dernier IF.

Contrôle itératif

Forme de base LOOP..END LOOP
Syntaxe
<nom de la boucle>  --facultatif
loop
  <suite d'instructions>
end loop;

Sous cette forme, la boucle s'exécute indéfiniment. On peut utiliser une instruction EXIT, provocant la sortie de la boucle.

Instruction EXIT

L'instruction exit permet de quitter une boucle. Le contrôle reprend juste après la fin de la boucle. Cette instruction a plusieurs formes.

Syntaxe
EXIT;           
--Quitter la boucle englobante
EXIT <nom de la boucle>;  --Quitter la boucle nommée
EXIT when <condition>;   --Quitter la boucle englobante si la condition est vraie.
EXIT <nom de la boucle> when <condition>;  
                   --Quitter la boucle nommée si la condition est vraie.
Exemple1
loop
  I := I + 4;
  exit when I > 50;
end loop;
Exemple2

Avec plusieurs niveaux de boucles, il est possible de quitter un niveau de boucle en nommant explicitement la boucle.

BOUCLE_EXTERNE:
loop
  <suite d'instructions>    -- POINT A BOUCLE_INTERNE:
  loop
    <suite d'instructions>  -- POINT B
  end loop BOUCLE_INTERNE; 
  <suite d'instructions>    -- POINT C
end loop BOUCLE_EXTERNE;

Avec exit ou exit BOUCLE_EXTERNE aux points A ou C, le contrôle est transféré à la fin de BOUCLE_EXTERNE.

Avec exit ou exit BOUCLE_INTERNE au point B le contrôle est transféré à la fin de BOUCLE_INTERNE, l'exécution reste sous le contrôle de BOUCLE_EXTERNE.

Avec exit BOUCLE_EXTERNE au point B le contrôle est transféré immédiatement à la fin de BOUCLE_EXTERNE.

Instruction WHILE..END LOOP
Syntaxe
while <condition> loop
  <suite d'instructions>
end loop;
Exemple
I := j;
while I < K loop
  I := I + 4;
  L := I * K;
end loop;
Instruction FOR
Syntaxe
for <variable> in [reverse] <intervalle> loop
  <suite d'instructions>
end loop;
Exemple1
for I in 10..20 loop  -- schéma de boucle de pas 1
  J := K + I;
end loop;
Exemple2
for I in reverse debut..fin loop  --schéma de boucle de pas -1
  T(I) := I;
end loop;
Remarques
  • On doit toujours utiliser une boucle FOR lorsqu'on connaît le nombre d'itérations à exécuter au moment de l'écriture du programme.
  • Les bornes de l'intervalle sont de type discret (entier ou énuméré).
  • L'indice utilisé dans la boucle n'a pas à être déclaré. Il est déclaré de manière implicite lors de l'entrée dans la boucle.
  • L'indice utilisé dans la boucle ne peut pas être manipulé en dehors de la boucle, ni modifié à l'intérieur de la boucle.

Le bloc

Un bloc est une structure permettant de déclarer des objets en cours d'exécution et de travailler sur ces objets.

La forme générale d'un bloc est la suivante :

<Nom de bloc> :  --facultatif
declare
  -- déclarations
begin
  -- corps du bloc
end <nom de bloc>;

Les blocs sont utilisés dans la gestion des erreurs. Ils permettent également de limiter la portée des variables.

Lorsque la fin du bloc est atteinte, les éléments qu'il déclare sont détruits.

Les sous-programmes

Les sous-programmes comme toutes les autres unités de programme Ada se composent de deux parties, une spécification et un corps. La spécification identifie le nom du sous-programme ainsi que les paramètres formels et le type du résultat pour les fonctions. Le corps encapsule une séquence d'instructions qui définit l'algorithme proprement dit. La notion de sous programme Ada correspond aux notions de procédures et de fonctions que l'on retrouve en Pascal ou en C.

Le mode de passage des paramètres

Il y a 3 modes de passage de paramètres :

  • IN : le paramètre formel n'autorise que la lecture du paramètre effectif correspondant.
  • OUT : le paramètre formel ne permet que la modification du paramètre effectif correspondant.
  • IN OUT : le paramètre formel permet la lecture et la modification du paramètre effectif correspondant.

Les procédures :

Déclaration d'une spécification de procédure
procedure <nom de la procédure> (<liste des paramètres formels>) ;

L'utilisation de ce type de déclaration est présentée plus loin.

Déclaration du corps d'une procédure
procedure <nom de procédure> (<liste des paramètres formels>) is
  < types et variables locales>
  begin
  <suite d'instructions>
  end <nom de la procédure>;

Dans <liste de paramètres formels>, chaque paramètre est spécifié par :

<indentificateur> : <mode> 
<type> [:=valeur initiale pour les paramètre en entrée];
Exemple1
procedure EMPILER (ELEMENT: in INTEGER; SUR : in out PILE);
Exemple2
procedure SOMME (A, B : in INTEGER; C : out INTEGER) is
begin
  C := A + B  
end SOMME;

Les fonctions

Déclaration d'une spécification de fonction
function <nom de la fonction> (<liste des paramètres formels>) return <type du résultat>;
Déclaration du corps d'une fonction
function  <nom de la fonction> (<liste des paramètres formels>) return <type du résultat> is
  < types et variables locales>
  begin
  <suite d'instructions> + <1 instruction return>
  end <nom de la fonction>; 

La spécification des paramètres formels est la même que pour une procédure sauf qu'ils sont ici tous en mode in.

Exemple1
function EST_VIDE (MAPILE : in PILE) return BOOLEAN;
Exemple2
function SOMME (A, B : in INTEGER) return INTEGER is
begin
  return A + B;
end SOMME;
Remarques sur le mode de passage des paramètres

Un paramètre in a une valeur donnée par l'unité appelante, et se comporte comme une constante, impossible à modifier.

Dans la procédure SOMME, les variables A et B ne doivent pas être modifiées (ne doivent pas être à gauche d'une affectation). Par contre, leurs valeurs peuvent être testées ou utilisées comme bornes d'intervalle d'une boucle.

Un paramètre out n'est pas défini lors de l'entrée dans une procédure, mais reçoit une valeur dans le corps de la procédure, (il peut donc être à gauche d'une affectation). Par contre, il ne peut pas être utilisé pour initialiser une autre variable (ne peut être à droite d'une affectation), ni même être testé ou utilisé comme borne d'intevalle d'une boucle.

Dans la procédure SOMME :

C := A + B;  --est légale
A := C;      --est illégale 
C := C + 1;  --est illégale.

Un paramètre in out a une valeur au moment de l'appel de la procédure, et est modifiable dans le corps de la procédure.

L'appel des sous-programmes

Un appel de procédure est une instruction.

Un appel de fonction est placé dans une expression.

L'association des paramètres effectifs aux paramètres formels peut se faire par position ou par nom.

Les appels possibles de la procédure SOMME sont les suivants :

SOMME(10, 15, R);                 --association par position
SOMME(A => 10, B => 15, C => R);  --association par nom
SOMME(10, 15, C => R);            --association mixte (déconseillée)

Pour un sous-programme ayant des paramètres d'entrée définis avec valeur par défaut, les paramètres effectifs correspondant peuvent être omis lors de l'appel ou leur valeur peut être modifiée.

Par exemple, pour le sous-programme défini par :

SP (X : in INTEGER := 4 ; Y : in FLOAT := 2.5; Z : out FLOAT);

les appels possibles sont :

SP (ZZ);                 --X = 4, Y = 2.5
SP (2, 4.5, ZZ);         --les valeurs de X et Y sont changées
SP (Y => 5.0, Z => ZZ);  --seule la valeur de Y est changée

La surcharge

La surcharge consiste à utiliser le même nom pour représenter des sous-programmes différents. Un sous-programme surchargé est ambigu s'il est impossible au compilateur de savoir quel sous-programme utiliser. Pour résoudre ce problème, le compilateur considère les types et l'ordre des paramètres formels.

En Ada tout sous-programme peut être surchargé y compris les fonctions et procédures prédéfinies. On peut par exemple surcharger les opérateurs arithmétiques standards comme le "+", le "-" etc...

Exemple
procedure LIRE( I : out INTEGER);
procedure LIRE( F : out FLOAT);
procedure LIRE( V : out VECTEUR);  --VECTEUR est un type tableau.
Exemple de surcharge d'un opérateur prédéfini
function "+"(X, Y : in NOMBRE) return NOMBRE;
function "+"(V1, V2 : in VECTEUR) return VECTEUR;

La surchage des opérateurs prédéfinis n'est possible que par l'utilisation des packages.

Règles de portée et de visibilité

Dans une unité de programme, la portée d'une entité commence à sa déclaration et disparaît à la fin de l'unité. Les portées d'entités portant le même nom peuvent se chevaucher en particulier lors de surcharge de sous-programmes.

La visibilité d'une entité se trouve à l'intérieur de la portée et peut être directe en l'absence de chevauchement ou étendue sinon.

Exemple
	
procedure PORTEE_VISIBLE is
  X: INTEGER;
  Y: INTEGER;
  function F1(X:in FLOAT) return FLOAT is
    begin
    return Y*X;
    end F1;
  ...
  procedure P1(..........) is
    X:FLOAT;
    I:INTEGER;
    begin
    ...
    end P1
  begin
  ...
  end PORTEE_VISIBLE;
Portée de

 X Y   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | | I 
 | | | 
 | | | 
 | | I 
 | |   
 | |   
 X Y   
Visibilité
directe de
       
 X     
 | Y   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | |   
 | | I 
 | | | 
 | | | 
 | | I 
 | |   
 | |   
 X Y
Pour utiliser une entité cachée par une autre 
entité de même nom, on utilise la notation pointée : nom_de_l'unite.nom_de_l'entité.

Par exemple, dans la procédure P1, pour utiliser la variable X définie dans PORTEE_VISIBILITE, on note PORTEE_VISIBILITE.X ce qui étend la visibilité de X.

La notion de package

Un package Ada est un ensemble d'entités ou de ressources de traitement logiquement reliées entre elles. Il permet d'encapsuler les données c'est-à-dire de contrôler exactement la manière dont elles seront manipulées.

Un package Ada comprend :

  • un partie visible appelée INTERFACE,
  • une partie cachée qui est le CORPS du package.
Syntaxe
spécification corps
package <nom du package> is       package body <nom du package> is
  <déclarations>                    <déclarations>
private                           begin
  <déclarations d'objets privés>    <rien ou initialisation>
end <nom du package>              end <nom du package>
La partie spécification (interface)

Cette interface spécifie quelles parties du package peuvent être utilisées et comment. Il est sans importance pour l'utilisateur du package de comprendre la façon dont les opérations sont effectivement implémentées. L'utilisateur d'un package ne peut se référer qu'à ces entités visibles. Par exemple, l'interface entre un homme et une voiture comprend le volant, les freins et l'accélérateur : ce sont les ressources visibles. Le conducteur n'a pas besoin de connaître comment elles marchent, c'est un détail d'implémentation!

La partie spécification d'un package comprend deux parties : la partie visible et la partie privée.

La partie visible déclare les ressources qui peuvent être utilisées à l'extérieur du package. On dit que le package exporte ces entités. Un package peut exporter un nombre quelconques d'éléments qui peuvent être des types, des sous-programmes...La partie privée ne peut apparaître qu'à la fin de la spécification, et elle commence avec le mot réservé private. La partie privée ne peut pas être référencée en dehors du package. Elle existe dans la partie spécification pour permettre le mécanisme de la compilation séparée des unités.

La partie implémentation (le corps du package)

On doit faire correspondre un corps à toute spécification de package, sauf si la spécification ne contient que des types et des objets, auquel cas le corps est optionnel. Les éléments du corps du package ne sont pas accessibles à l'extérieur du package. On respecte ainsi le principe de dissimulation de l'information.

Exemple
package COMPLEXE is
  type NOMBRE is private;
  procedure AFFECTER (X: out NOMBRE; GAUCHE : in Float; DROITE : in Float);
  function "+" (X, Y: in NOMBRE) return NOMBRE;
  function "-" (X, Y: in NOMBRE) return NOMBRE;
  function ACCES_PR (X: in NOMBRE) return Float;
  function ACCES_PI (X: in NOMBRE) return Float;
private
  type NOMBRE is record
    PARTIE_REELLE, PARTIE_IMAGINAIRE : Float;
  end record;
end COMPLEXE;

package body COMPLEXE is
  procedure AFFECTER (X: out NOMBRE; GAUCHE in Float; DROITE : in Float) is 
    begin
    X:=(GAUCHE, DROITE);
    end AFFECTER;

  function "+" (X, Y: in NOMBRE) return NOMBRE is
    begin
    return (X.PARTIE_REELLE + Y.PARTIE_REELLE, X.PARTIE_IMAGINAIRE + Y.PARTIE_IMAGINAIRE);
    end "+";
    ...

begin
end COMPLEXE;
Remarque

Les packages ne doivent pas être soumis à l'éditeur de lien. Ils doivent juste être compilés. Une fois compilés, ils peuvent être utilisés à partir d'un autre programme.

Utilisation d'un package

Un programme peut utiliser un package en utilisant les clauses with et use.

Syntaxe
with <nom du package>; use <nom du package>;
Exemple
with COMPLEXE; use COMPLEXE;
procedure ESSAI is
  C1,C2,C3:NOMBRE;
  begin
  C3:=C1+C2;
  end;
Remarque

Un type private autorise les opérations d'affectation et de comparaison entre des variables de ce type. Ainsi si on reprend l'exemple précédent, on peut écrire C1 := C2 ce qui ne fait appel à aucun sous-programme de COMPLEXE. En utilisant un type limited private, l'affectation et les tests sont interdits. L'utilisateur du package ne pourra plus écrire C1 := C2. Il devra utiliser une procédure (ou fonction) du package COMPLEXE.

Les entrées/sorties

Les entrées/sorties de textes, de caractères, de nombres etc..., se font en utilisant les packages prédéfinis suivants :

ADA.TEXT_IO : permet les entrées/sorties de chaînes de caractères ou de caractères.

ADA.INTEGER_TEXT_IO : permet les entrées/sorties pour des entités de type prédéfini INTEGER.

ADA.FLOAT_TEXT_IO : permet les entrées/sorties pour des entités de type prédéfini FLOAT.

Exemple d'utilisation
with ada.text_io; use ada.text_io;
with ada.integer_text_io; use ada.integer_text_io;
with ada.float_text_io; use ada.float_text_io;


procedure ENTREE_SORTIE is
  I : INTEGER;
  R : FLOAT;


  begin
  put_line("Donner une valeur entiere :"); --Ecriture d'un message et saut de ligne
  get(I);                                  --Lecture au clavier de la variable I
  put(" La valeur saisie est : );          --Ecriture d'un message sans saut
  put(I);                                  --Ecriture de la variable I sans saut
  put_line("Donner une valeur reelle :");  --Ecriture d'un message et saut de ligne
  get(R);                                  --Lecture au clavier de la variable R
  put(" La valeur saisie est : );          --Ecriture d'un message sans saut 
  put(R);                                  --Ecriture de la variable R sans saut
  end ENTREE_SORTIE;
Remarque

Les opérations de lecture (GET) et d'écriture (PUT) sont surchagées.

Pour réaliser des entrées/sorties de type entier, réel ou énuméré construits,

il faut utiliser des packages génériques prédéfinis.

Exemple d'utilisation
with ada.text_io; use ada.text_io;
procedure ENTREE_SORTIE is
  type ENTIER is range 1..100;         --Déclaration d'un type entier
  type REEL is digits 6;               --Déclaration d'un type réel
  type JOUR is (LUN,MAR,MER,JEU,VEN);  --Déclaration d'un type énuméré
  --Déclaration des instances des packages génériques
  package ES_ENTIER is new INTEGER_IO ( ENTIER); use ES_ENTIER;
  package ES_REEL is new FLOAT_IO(REEL);         use ES_REEL;
  package ES_JOUR is new ENUMERATION_IO(JOUR);   use ES_JOUR;
  I : ENTIER;
  R : REEL;
  AUJOURDHUI : JOUR;

  begin
  put_line("Donner une valeur entiere:");  --Ecriture d'un message et saut de ligne
  get(I);                                  --Lecture au clavier de la variable I
  put(" La valeur saisie est : );          --Ecriture d'un message sans saut 
  put(I);                                  --Ecritue de la variable I sans saut
  put_line("Donner une valeur reelle :");  --Ecriture d'un message et saut de ligne
  get(R);                                  --Lecture au clavier de la variable R
  put(" La valeur saisie est : );          --Ecriture d'un message sans saut 
  put(R);                                  --Ecriture de la variable R sans saut
  get(AUJOURDHUI);                         --Lecture de la variable AUJOURDHUI
  put(AUJOURDHUI);                         --Ecriture de la variable AUJOURDHUI
end ENTREE_SORTIE;

Les types

Ada est un langage fortement typé. Chaque variable et expression possèdent un type explicite déterminé lors de la compilation. Un type est un ensemble de valeurs, et un ensemble d'opérations applicables aux objets de ce type.

Un objet est créé et son type précisé au moyen d'une déclaration. Tous les objets doivent être explicitement déclarés. Deux objets sont de même type si leur déclaration se réfère au même nom de type.

Il y a deux sortes d'objets : les constantes non modifiables et les variables.

Déclaration de constantes
<liste_d'identificateurs> : constant [ <type> ] := expression;
Déclaration de variables
 <liste_d'identificateurs> : <type>  [ := expression];

L'initialisation est obligatoire pour une constante, facultative pour une variable.

Exemples:
TAILLE : constante INTEGER :=100;
PI : constant := 3.14;  --il s'agit ici d'un nombre nommé sans précision du type.
TRUC : FLOAT;
X : INTEGER := 1;
ENTIER : INTEGER range 1..10 := 5;  --Déclaration avec intervalle de valeurs.

Nous pouvons résumer les types Ada selon la classification suivante :

  • Composés élémentaires privés
  • tableaux accès scalaires
  • articles - sur sous-programmes réels discrets
  • tâches - sur bojets fixes flottants entiers énumérés
  • étiquetés - binaires - signés
  • protégés - décimaux - modulaires
  • numériques

Types de données scalaires

Types entiers

Une définition de type entier introduit un ensemble d'entiers consécutifs comme valeur du type. Tous les littéraux entiers sont des valeurs d'un type anonyme infini, appelé entier_universel, que nous pouvons imaginer comme représentant tous les entiers du monde mathématique. Une implémentation donnée définira les types entiers comme des types dérivés d'entier_universel.

Les types entiers prédéfinis sont :

  • INTEGER (une variable de type INTEGER aura un intervalle de variation de -32_768 à 32_767 sur une machine 16 bits),
  • LONG_INTEGER,
  • SHORT_INTEGER.
Remarque

Ada doit supporter obligatoirement le type INTEGER, et peut, mais ce n'est pas obligatoire supporter des intervalles d'entiers plus grands (LONG_INTEGER) ou plus court (SHORT_INTEGER).

L'utilisation des types entiers prédéfinis est déconseillée car les valeurs extrêmes dépendent de la machine. Afin de travailler indépendamment de la machine, il faut construire ses propres types.

Constructeur de types entiers avec intervalle de valeurs explicites

Le compilateur choisit la représentation interne la plus appropriée rendant ainsi le code portable.

Syntaxe
type <nom du type> is range Borne1..Borne2;
Exemple
type COMPTEUR_LIGNE is range 0..66;
type BRASSE is range -5_000..0;

On peut ensuite déclarer des variables sur ces types de la manière suivante:

LIGNES : COMPTEUR_LIGNE;
Remarque

Si au cours de la manipulation d'une variable nous tentions de lui affecter une valeur externe à son intervalle de variation, l'exception CONSTRAINT_ERROR serait levée.

Ensemble de valeurs

Ensemble d'entiers consécutifs.

borne1 et borne2 sont des expressions entières représentant respectivement les bornes inférieure et supérieure de l'intervalle.

Ensemble d'opérations
  • additives : + -
  • affectation : :=
  • appartenance : in not in
  • exponentiation : * *
  • multiplicatives : * / mod rem
  • relationnelles : = /= < <= > >=
  • unaires : + - abs
Attributs
ADDRESS   PRED
BASE      SIZE
FIRST     SUCC
IMAGE     VAL
LAST      VALUE
POS       WIDTH
Types prédéfinis:
INTEGER
LONG_INTEGER
NATURAL
POSITIVE
SHORT_INTEGER

L'utilisation des attributs permet de paramétrer les programmes. Par exemple, une boucle sur le domaine de valeurs du type COMPTEUR_LIGNE défini plus haut peut s'écrire:

for I in COMPTEUR_LIGNE'range loop
for I in COMPTEUR_LIGNE'first..COMPTEUR_LIGNE'last loop

L'attribut IMAGE peut être utilisé pour réaliser rapidement des impressions de variables entières en utilisant seulement Ada.text_io (sans passer par une instance de INTEGER_IO). Cet attribut fournit une chaîne de caractères correspondant à la valeur d'une variable de type entier.

Exemple
put(COMPTEUR_LIGNE'image(LIGNE));  --LIGNE étant une variable de type COMPTEUR_LIGNE.

Types réels

Un type réel définit un ensemble de valeurs qui sont des approximations des nombres réels. On considère que les littéraux réels sont du type réel_universel. avec une précision infinie.

Les types réels prédéfinis sont:

FLOAT, LONG_FLOAT, SHORT_FLOAT.

Comme pour les entiers, il est déconseillé d'utiliser ces types prédéfinis.

Constructeur de types point flottant

Les types point flottant permettent de décrire des valeurs avec une précision relative.

Syntaxe
type <nom du type> is digits <nombre>;
type <nom du type> is digits <nombre> range Borne1..Borne2;
Exemples
type MESURE_PLANETAIRE is digits 15;
type VALEUR_DE_MASSE is digits 7 range 0.0..3.0;
Constructeur de types point fixe

Les types point fixe permettent de décrire des valeurs avec une précision absolue.

Syntaxe
type <nom du type> is delta <nombre> range Borne1..Borne2;
Exemples
type  fixe is delta 0.01 range -12.0..+24.0;
Constructeur de types point fixe décimaux
type <nom du type> is delta <nombre> digits <nombre> [range Borne1..Borne2];
Exemple
type FD is delta 0.01 digits 5;  --Une précision de 0.01 et un domaine de valeur compris entre -999.99 et +999.99.
Ensemble de valeurs

Approximation des nombres réels.

Structure
  • digits N range I..S : spécifie une incertitude relative, où N est une valeur entière statique qui représente le nombre de chiffres significatifs et I et S sont des valeurs statiques représentant les bornes inférieure et supérieure de l'intervalle.
  • delta D range I..S : spécifie une incertitude absolue, où D est une valeur réelle statique qui représente le delta et I et S sont des valeurs statiques représentant les bornes inférieure et supérieure de l'intervalle.
  • delta D digits N : spécifie une incertitude absolue, où D est une valeur réelle statique qui représente le delta, N le nombre de chiffres, en base 10, de la représentation.
Ensemble d'opérations
  • additives : + -
  • affectation : :=
  • exponentiation : **
  • multiplicatives : * /
  • relationnelles : = /= < <= > >=
  • unaires : + - abs
Attributs
Points fixes
ADDRESS     MACHINE_OVERFLOWS
AFT         MACHINE_ROUNDS
BASE        MANTISSA
DELTA       SIZE
FIRST       SAFE_LARGE
FORE        SAFE_SMALL
LARGE       SMALL
LAST
Points flottants
ADDRESS       MACHINE_MANTISSA
BASE          MACHINE_OVERFLOWS
DIGITS        MACHINE_RADIX
EMAX          MACHINE_ROUNDS
EPSILON       MANTISSA
FIRST         SAFE_EMAX
LARGE         SAFE_LARGE
LAST          SAFE_SMALL
MACHINE_EMAX  SIZE
MACHINE_EMIN  SMALL
Types prédéfinis
FLOAT
LONG_FLOAT
SHORT_FLOAT
DURATION

Types énumératifs

Un type énumératif Ada permet au programmeur de déclarer explicitement les valeurs possibles pour ce type.

Syntaxe
type <nom du type> is (<liste des valeurs>);
Exemples
type COULEURS is (rouge, bleu, marron, violet);
type CHIFFRE_HEXA is ('A', 'B', 'C', 'D', 'E', 'F');
type POSITION is (Haute, Basse);

On peut ensuite déclarer des variables par :

MA_COULEUR : COULEURS;
TRAIN_D_ATTERRISSAGE : POSITION;

L'utilisation de ces variables peut se faire comme suit :

MA_COULEUR := COULEURS'FIRST;   --Donne la couleur rouge
TRAIN_D_ATTERRISSAGE := Haute;
Remarques
  • La liste de valeurs fournit entre parenthèses ne doit pas comporter de mots réservés Ada.

Ainsi la déclaration:

type GAMME is ( Do, Re, Mi, Fa, Sol, La, Si,);

est illégale car Do est un mot réservé.

  • Si nous tentons d'introduire une valeur en dehors de l'intervalle énumératif, l'exception CONSTRAINT_ERROR est levée.
Ensemble de valeurs

Ensemble des valeurs ordonnées distinctes.

Structure

(E0, E1, ...,En) où Ei est un littéral énumératif .

Ensemble d'opérations
  • affectation : :=
  • appartenance : in not in
  • relationnelle : = /= < <= > >=
Attributs
ADDRESS      PRED
BASE         SIZE
FIRST        SUCC
IMAGE        VAL
LAST         VALUE
POS          WIDTH
Types prédéfinis
BOOLEAN
CHARACTER

Types de données composites

Types tableaux

Un nom de tableau dénote un groupe de composants de même type. Un tableau peut comporter plusieurs dimensions et Ada n'impose aucune limitation au nombre maximal de dimensions des tableaux.

Constructeur de types tableaux contraints

Tous les objets construit sur le type ont les mêmes bornes.

Syntaxe
type <nom du type> is array(<contraintes d'indice>) of <nom de type>;

Les contraintes d'indice sont précisées avec ou sans type. Dans ce dernier cas, les bornes sont des entiers. Le type des indices est discret. Le type des éléments du tableau est quelconque.

Exemple1
type TAB is array (1..30) of FLOAT;
Exemple2
type INDICE is range 0..1_000;
type GRAND_TABLEAU is array(INDICE) of float;
type PETIT_TABLEAU is array(INDICE range 10..49) of float;
Exemple3
type JOUR is (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche);
type HEURES is delta 0.1 range 0.0..24.0;
type FICHE_DE_TRAVAIL is array (JOUR range LUNDI..VENDREDI) of HEURES;
type HEURES_SUP is array (JOUR range SAMEDI..DIMANCHE) of HEURES;
type SEMAINE is array (JOUR) of HEURES;
DEMAIN : constant array (JOUR) of JOUR := (mardi, mercredi, jeudi, vendredi, samedi, dimanche, lundi);
Constructeur de types tableaux non contraints

Un type tableau non contraint doit être considéré comme un modèle de tableau dans lequel un paramètre reste à définir : le domaine des valeurs des indices. Le type tableau non contraint autorise la création de plusieurs objets du même type avec des bornes d'indices différentes. De plus les bornes (appelées contraintes d'indice) ne sont pas nécessairement statiques; elles peuvent être déterminées à l'exécution.

Syntaxe
type <nom du type> is array ( <non de type> range <> ) of < nom de type>;
Exemple1
type VECTEUR is array ( INTEGER range <> ) of float;
V1 : VECTEUR (1..10); V2 : VECTEUR (1..100);
Ensemble de valeurs

Ensemble indicé de valeurs de type identique.

Ensemble d'opérations
  • concaténation : & (tableau mono-dimensionnel)
  • affectation : :=
  • relationnelles : = /= < <= > >=
  • agrégats et tranches
Attributs
ADDRESS     RANGE
BASE        RANGE(J)
FIRST       LENGTH
FIRST(J)    LENGTH(J)
LAST        SIZE
LAST(J)
Types prédéfinis
STRING
Exemple d'utilisation des attributs
with Ada.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;
procedure ATTRIBUTS_TABLEAUX is
  type MATRICE is array(INTEGER range 1..100, INTEGER range 1..100 ) of FLOAT;
  type VECTEUR is array (INTEGER range 1..50) of FLOAT;


  M1 : MATRICE; V1 : VECTEUR;
begin  
  --Lecture du vecteur V1
  for I in V1'first..V1'last loop
    get(V1(I));
  end loop;
  --Lecture de la matrice M1
  for I in M1'range (1) loop
    for J in M1'range(2) loop
      get (M1(I,J));
    end loop;
  end loop;  
end ATTRIBUTS_TABLEAUX;
Les agrégats et les tranches

L'agrégat est une opération spécifique aux types composés. Elle permet de construire une valeur d'un type composé en donnant une valeur à chaque composant.

Les associations de composants peuvent être données soit par leur position (association par position) ; soit en nommant les composants choisis (association nominative) ; soit en mélangeant les deux méthodes.

Association par position sur un tableau :

tab := (1, 3, 7, 5, 9);  --tab étant d'un type tableau de 5 éléments
tab2 := ((1, 2, 3), (4, 5, 6), (7, 8, 9));  --tab2 est d'un type tableau de dimension (3,3)

Association par nom sur un tableau :

tab := (1..4 => 45, 6..8 => 34);   -- 45 de la position 1 à la position 4
                                   -- 34 de la position 6 à la position 8
tab2(5..8) := ( 5|8 => 0, 6|7 => 1);  --utilisation d'une tranche de tableau 
                                      --0 en position 5 et 8
                                      --1 en position 6 et 7

Une façon élégante de nommer tous les éléments restants s'ils doivent avoir la même valeur est d'utiliser le choix OTHERS. Ce choix ne peut apparaître qu'en fin d'agrégat.

tab1 := (OTHERS => 0);  --tous les éléments à 0
tab2 := (4|3|2 => 1, OTHERS => 0);

Association mixte : la notation par position ne peut apparaître qu'en début d'agrégat.

Une tranche est un sous-tableau. On peut faire des affectations par tranche.

tab(I + 1..J + 1) := tab(I..J);  --Décalage de 1 vers la droite de la tranche entre les positions I et J.
Type tableau prédéfini STRING

Ada fournit deux types tableaux prédéfinis appelés STRING et WIDE_STRING, qui sont des types tableaux mono-dimensionnels dont les composants sont de type CHARACTER ou WIDE_CHARACTER.

Exemples d'utilisation
NOM : STRING(1..18);
QUESTION : constant STRING := "Entrez vos données : ";
NOM := "Reservoir" & " numéro 7";  --concaténation de deux chaînes

Types articles

Les articles sont des ensembles d'éléments de types identiques ou différents.

Comme pour le type tableau, on distingue le type article contraint (ou simple) et le type article non contraint ( ou avec discriminant).

Déclaration d'un type article simple
Syntaxe
type <nom du type> is record
     <liste des champs>
     end record;
Exemples
type PERSONNE is record
    NOM,PRENOM : string (1..10);
    AGE : POSITIVE range 0..60;
    end record;
PAUL, PIERRE : PERSONNE;
Ensemble de valeurs

Ensemble de composants de types différents.

Ensemble d'opérations
  • affectation : :=
  • relationnelles : = /=
  • agrégat
Attributs
Type article
ADDRESS
BASE
CONSTRAINED
SIZE
Composant d'article
FIRST_BIT
LAST_BIT
POSITION
Les agrégats

Association par position sur un article :

PIERRE := ("DUPUIS   ", " PIERRE ", 30);  --le champ NOM prend la valeur DUPUIS
                                          --le champ PRENOM prend la valeur PIERRE
                                          --le champ AGE prend la valeur 30

Association par nom sur un article :

PAUL: = (NOM => "DUPONT  ", PRENOM => "PAUL   ", AGE => 30);

Association mixte sur un type article :

ANDRE : = ("DUPONT   ", PRENOM => "PAUL    ", AGE => 30);

Le choix OTHERS peut être utilisé à condition que les champs soient tous de même type.

Déclaration d'un type article avec discriminant
Syntaxe
type <nom de type> (<nom discriminant> : type [:= expression]) is record
    <liste de champ>  -- le discriminant est utilisé ici
    end record;

Il peut existe un ou plusieurs discriminants. Un discriminant est de type discret et peut avoir ou non une valeur initiale.

La technique du discriminant permet de créer des article avec partie variante ou des articles mutables.

Exemple d'article avec partie variante
type SEXE is (MASCULIN, FEMININ);
type PERSONNE (DE_SEXE : SEXE) is record
    NOM,PRENOM : STRING (1..8);
    AGE : POSITIVE;  
    case DE_SEXE is
    when MASCULIN => SITUATION_MILITAIRE : BOOLEAN;
    when FEMININ  => NOM_DE_JEUNE_FILLE : STRING(1..8);
                     NB_ENFANTS : NATURAL;
                     EN_ACTIVITE : BOOLEAN;
    end case;
    end record;

Dans cet exemple, le discriminant n'a pas de valeur par défaut. Toute création d'objet sur ce type doit donner une valeur au discriminant :

LUI : PERSONNE(MASCULIN);  -- article avec 4 champs
ELLE : PERSONNE(DE_SEXE => FEMININ);  -- artcicle avec 6 champs

ou

LUI : PERSONNE(MASCULIN, "DUPONT  ", "PIERRE  ", 30, TRUE);

L'accès au discriminant est possible avec LUI.DE_SEXE, il est toutefois impossible de le modifier.

Les objets du type n'ont pas tous la même forme. L'affectation entre LUI et ELLE est interdite !

Exemple de type article mutable

Un type article avec discriminant peut être utilisé pour gérer des tableaux de taille variable.

type indice is range 1..1000;
type TABLEAU is array (INDICE range <>) of INTEGER;
type T_variable(TAILLE : INDICE) is record
   TAB : TABLEAU(1..TAILLE);
   end record;

La déclaration d'un objet doit être faite avec une valeur pour le discriminant :

T1 : T_VARIABLE(10);  --T1 est un objet contraint et T1.TAB aura toujours 10 éléments (le discriminant n'a pas de valeur par défaut).

Avec la déclaration suivante :

type indice is range 1..1000;
type TABLEAU is array(INDICE range <>) of INTEGER;
type T_variable  ( TAILLE : INDICE := 1) is record
    TAB : TABLEAU (1 .. TAILLE);
    end record;

T1 : T_VARIABLE;  --T1 est un objet non contraint, T1.TAB est un tableau de 1 élément pour le moment,
                  --mais sa taille pourra être modifiée en cours d'exécution.

Autre exemple sur une chaîne de caractères :

type INDICE is range 1..255;
type CHAINE (TAILLE : POSITIVE :=1) is record
    CH : STRING(1..TAILLE);
    end record;


NOM, PRENOM : CHAINE;


NOM := (5, "BYRON");
PRENOM := (8, "ADELAIDE");
NOM.CH := NOM.CH&PRENOM.CH;

Le type ACCESS

Ada fournit le type accès pour permettre de manipuler les situations suivantes:

  • des objets peuvent être créés et détruits de façon imprévisible,
  • plusieurs noms peuvent se référer au même objets,
  • les relations entre objets peuvent changer au cours du temps.
Syntaxe
type <nom de type> is access <nom de type>;
Exemple
type TAMPON is record
     MESSAGE : string (1..10);
    PRIORITE : POSITIVE;
    end record;


type POINTEUR_TAMPON is access TAMPON;

On peut ensuite déclarer des variables de ce type:

PAQUET1, PAQUET2, PAQUET3 : POINTEUR_TAMPON;
Allocation de mémoire

L'allocation de mémoire en cours d'exécution se fait grâce à l'instruction new. On peut travailler sur l'espace alloué dès que l'instruction new a été exécutée.

Exemple1
PAQUET1 := new TAMPON;
PAQUET1.PRIORITE := 5;
Exemple 2
PAQUET2 := new TAMPON'(MESSAGE => "**********", PRIORITE => 1);
Exemple 3
PAQUET3 := new TAMPON'("----------", 2);
Durée de vie des objets dynamiques

Les objets dynamiques sont détruits lorsque nous quittons la portée de leurs objets accès respectifs (en supposant que nous ne gardons pas de pointeurs globaux sur l'objet désigné).

Exemple
declare
   POINTEUR : POINTEUR_TAMPON;
begin
  ...
  POINTEUR := new TAMPON;  -- crée un objet TAMPON
  ...
end;  --L'objet TAMPON est détruit
Remarques
  • Dès qu'une zone de mémoire allouée dynamiquement n'est plus désignée par aucun pointeur, cette zone est restituée à l'espace libre.
  • Les variables de type access sont les seules variables initialiser par défaut à la compilation (valeur NULL).
  • Ne pas confondre l'objet access (le pointeur) et l'objet désigné.
Exemple:
PAQUET1.all  -- se réfère à tout l'objet
PAQUET2      -- l'objet de type access

Ensemble de valeurs : valeurs d'accès sur des objets désignés

Ensemble d'opérations
  • affectation : :=
  • relationnelles : = /=
Attributs
ADDRESS
BASE
SIZE
STORAGE_SIZE
Résumé des types accès

On peut utiliser des valeurs accès pour décrire des relations entre objets, par exemple pour représenter des structures de données comme les listes chaînées, les arbres,...

Exemple de définition d'une liste chaînée
type CELLULE;  --déclaration incomplète
type LIEN is access CELLULE;
type CELLULE is record
    VALEUR : INTEGER;
    SUIVANT : LIEN;
    end record;

TETE, COUR : LIEN;

TETE := new CELLULE;  -- création d'une tête fictive
COUR := new CELLULE'(VALEUR => 10, SUIVANT => NULL);
TETE.SUIVANT := COUR;

L'accès au champ de la cellule qui vient d'être créée se fait par :

COUR.VALEUR ou TETE.SUIVANT.VALEUR.

Le type ACCESS généralisé

Ce type permet de créer des pointeurs sur des variables ou des constantes, ou sur des sous-programmes.

Création de pointeurs sur des variables ou des constantes
Syntaxe
type <nom de type> is access <all ou constant> <nom de type>;

Avec all, une valeur du type accès généralisé peut désigner seulement une variable et peut être utilisée pour mettre à jour cette variable.

Avec constant, une valeur du type accès généralisé peut désigner seulement une constante et ne peut être utilisée pour mettre à jour cette constante. Par contre, on peut lui donner une valeur initiale.

Exemple
type PTR1 is access all INTEGER;
type PTR2 is access constant INTEGER;
P1 : PTR1;  --Déclaration de deux variables 
P2 : PTR2;  --sur les deux types accès

Les objets définis sur le type pointés (ici INTEGER) doivent être déclarés aliased pour permettre l'accès à leurs adresses.

ENTIER1, ENTIER2 : aliased INTEGER; 

Ces deux variables sont de types INTEGER et on va pouvoir récupérer les adresses en utilisant l'attribut access :

P1 := ENTIER1'access;  --P1 donne accès à ENTIER1
P2 := ENTIER2'access;  --P2 donne accès à ENTIER2

L'initialisation des deux objets ENTIER1 et ENTIER2 peut se faire de deux façons :

ENTIER1:=500;  ou  P1.all:=500;  --la modification de ENTIER1 reste possible
ENTIER2:=200;  ou  P2.all:=200;  --la modification de ENTIER2 est impossible
Création de pointeur sur des sous-programme
Syntaxe
type <nom de type> is access function (profil des paramètres) return <profil du résultat>;
type <nom de type> is access procedure (profil des paramètres);
Exemple
procedure EXEMPLE is
  type PTR_F is access function (P : FLOAT) return FLOAT; --ce type accès est défini pour une fonction ayant un seul
                                                          --paramètre de type FLOAT
  function  INTEGRER ( LA_FONCTION PTR_F; MIN,MAX : FLOAT) return FLOAT is
    begin
    ...
    end INTEGRER;

  RES : FLOAT;   ou   T := PTR_F := log'access;
  begin
    RES := INTEGRER (log'access, 1.0, 2.0);
 ou RES := INTGERER(T, 1.0, 2.0);
  end EXEMPLE;

Les types privés

Les types de données privés apparaissent dans les spécifications de packages sous la forme d'un nom de type suivi des mots réservés private ou limited private.

Sous-types et types dérivés

Les sous-types

Un type décrit les propriétés générales d'une entité du monde réel. Un sous-type permet d'exprimer des restrictions applicables seulement à certains objets en introduisant des contraintes.

Un sous-type ne définit pas de nouveau type, il caractérise un sous-ensemble des valeurs d' un type donné, dit type de base. A l'exception des contraintes de précision des types point flottant et point fixe, les contraintes d'un sous-type ne sont pas obligatoirement statiques, mais peuvent être évaluées à l'exécution. Un sous-type est compatible avec son type de base.

Syntaxe
subtype <nom du sous-type> is <nom du type> <contrainte>;
Exemple1
type NOM_DE_MOIS is (janvier, fevrier, mars, avril, mai, juin, juillet, aôut, septembre, octobre, novembre, décembre);
subtype ETE is NOM_DE_MOIS range juin..aôut;
MOIS_COURANT : NOM_DE_MOIS;
VACANCES : ETE;
Exemple2
type NON_NEGATIF is range 0..INTEGER'LAST;
subtype INDEX is NON_NEGATIF range 0..10;
Exemple3
type VECTEUR is array (NATURAL range <>) of float;
subtype VECTEUR_3D is VECTEUR(1..3);
Exemple4
subtype GRAND is NON_NEGATIF range 0..1_000_000;
subtype PETIT is GRAND range 0..10;

Les types dérivés

Contrairement aux sous-types, les types dérivés définissent des types distincts. Ils sont nécessaires lorsque notre abstraction de l'espace du problème contient des objets distincts mais dont la structure est similaire.

Syntaxe
type <nom du type dérivé> is new <nom du type parent>;
type <nom du type dérivé> is new <nom du type parent> <contraintes>;
Exemple1
type MASSE is new FLOAT;
type POIDS is new FLOAT;

MASSE et POIDS sont dérivés du type FLOAT qui est appelé type parent.

On peut ensuite déclarer des variables comme suit:

M : MASSE;
P : POID;

Mais l'addition M + P est illégale car M et P ne sont pas du même type.

Exemple2
type BUDGET is new FLOAT range 0.0..12_000.0;
Remarque

Un type dérivé hérite des opérations applicables au type parent.

Explication et exemple

Reprenons le type DATE.

type DATE is record
    jour : INTEGER range 1..31;
    MOIS : INTEGER range 1..12;
    ANNEE : NATURAL;
    end record;

Si nous surchargeons l'opérateur "+" comme suit :

function "+"(D1 : in DATE ; D2 : in DATE) return DATE is ...

Soit le type dérivé suivant :

type MA_DATE is new DATE;

le sous-programme "+" est également dérivé. Toute variable de type MA_DATE pourra utiliser ce sous-programme. Cependant, si nous déclarons d'autres opérations sur DATE après la déclaration de MA_DATE, ces sous-programmes ne seront pas dérivés.

Les exceptions

Une exception désigne un événement qui provoque la suspension de l'exécution normale du programme. Cet événement peut être la violation d'une contrainte ou un état qui demande un traitement spécial.

Dans le premier cas l'exception est prédéfinie (exemple : CONSTRAINT_ERROR), dans le second cas, elle est définie par le programmeur.

Déclarer, lever, traiter une exception

Déclaration d'exceptions

Une déclaration d'exception est similaire dans sa forme à une déclaration d'objet. Elle consiste en une liste d'identificateurs, un signe deux-points, et le mot réservé exception. Elle se trouve dans toute partie déclarative.

Syntaxe
<nom de l'exception> : exception;
Exemple
MATRICE_SINGULIERE : exception;
ERREUR_DE_PARITE : exception;
Remarque

Le nom d'une exception utilisateur a la même portée qu'une déclaration d'objet, bien que l'effet d'une exception puisse s'étendre au delà de sa portée.

Lever d'une exception

Lever une exception avec l'instruction RAISE, consiste à signaler qu'une situation anormale est survenue.

Syntaxe
raise <nom de l'exception définie>;
raise;
Exemple
raise MATRICE_SINGULIERE;

L'instruction raise suspend le traitement séquentiel normal et propage l'exception jusqu'à ce qu'elle soit traitée.

Remarque

Le mot réservé raise peut apparaître partout où une levée d'exception est autorisée : dans la partie exécutable d'un bloc, d'un sous-programme. Ces unités de traitement peuvent être dans un package.

Traiter une exception

Lorsqu'il se produit une exception comme une division par zéro, dans la plupart des langages on suspend le traitement et le système d'exploitation reprend le contrôle. Dans un système temps réel fiable, nous ne pouvons pas permettre au programme de se terminer anormalement; nous devons pouvoir intercepter l'exception. C'est pourquoi Ada nous permet d'écrire des traites-exceptions pour rattraper aussi bien les exceptions prédéfinies que celles définies par l'utilisateur.

Lorsqu'une exception est levée dans une unité, le traitement de cette unité est abandonné et le contrôle est donné au traite-exception, s'il existe. Un traite-exception peut apparaître à la fin d'un bloc ou d'un corps de sous-programme .

Syntaxe
exception
  when <nom de l'exception> => <traitement à réaliser>
  when <nom de l'exception> => <traitement à réaliser>
  ...
  when others => <traitement à réaliser>
end;
Exemple
procedure P is
-- déclarations
  ERREUR, NE_MARCHE_PAS : exception;

  begin

  -- instructions

  if INCIDENT then
    raise ERREUR;
  end if;

  --instructions

  if TOUT_VA_MAL then
    raise NE_MARCHE_PAS;
  end if;

  --instructions
  
EXCEPTION

  when ERREUR           => faire _le_nécessaire;
  when NE_MARCHE_PAS    => remettre_un_peu_d'ordre;
  when CONSTRAINT_ERROR => faire_autre_chose;
  end P;

Les exceptions prédéfinies

Les exceptions prédéfinies sont levées implicitement. La liste des exceptions prédéfinies est la suivante :

CONSTRAINT_ERROR : levée lors de la violation d'une contrainte d'intervalle, d'indice ou de discriminant.

PROGRAM_ERROR :

  • levée si l'on tente d'accéder à un sous-programme, à un package non encore élaborés.
  • levée si une fonction atteint le end sans avoir exécuté d'instruction return.
Remarque:

L'implémentation peut également lever cette exception si elle détecte un comportement erroné.

STORAGE_ERROR : levée lorsque l'espace dynamique alloué à une entité est épuisé.

DATA_ERROR : levée lorsque ce que l'on a lu ne peut être interprété selon le type désiré.

Propagation des exceptions

Lorsqu'une exception est levée dans une suite d'instructions, l'exécution de la suite est abandonnée : s'il y a un traite-exception, l'exécution se poursuit avec les instructions du traite-exception.

Règle

Si une exception levée n'est pas traitée localement, elle se propage au niveau de la structure englobante jusqu'à ce qu'elle soit traitée, ou qu'elle parviennent à l'environnement.

  • une exception dans une partie déclarative est toujours propagée à l'unité englobante.
  • une exception peut-être propagée au delà de sa portée. Dans ce cas, elle ne peut-être traitée qu'avec la clause when others.

Utilisation des exceptions

On ne doit pas utiliser les exceptions pour simuler des goto implicites. Nous devons essayer d'identifier les conditions d'erreur possibles et nous n'utilisons les exceptions que pour prévoir leur traitement.

Plusieurs types d'actions sont possibles lorsqu'on lève une exception:

  • abandonner l'exécution de l'unité,
  • essayer l'opération à nouveau,
  • utiliser une méthode différente,
  • réparer la cause de l'erreur.
Remarque

Il est généralement déconseillé d'ignorer l'exception.

Exemple 1

Une procédure PROC appelle une fonction qui peut (de temps en temps) faire une division par zéro (qui lève CONSTRAINT_ERROR).

Solution 1
function FONC(.....) return TRUC is 
  begin 
  return C/D;
  end; 


procedure PROC is
  T: TRUC;
  begin
    T := FONC;
  exception
    when CONSTRAINT_ERROR => ...;
  end PROC;
Solutions 2

Traiter l'exception au niveau de l'appel, FONC reste inchangée.

procedure PROC is
  T: TRUC;
  begin
  ...
  begin
    T: = FONC;
  exception
    when CONSTRAINT_ERROR => ...;
  end;
  ...
  end PROC;
Solutions 3

Traiter l'exception dans FONC

function FONC(.....) return TRUC is 
  begin
  ...
  begin
  return C / D;
  exception
    when CONSTRAINT_ERROR => ...;
  end;
  end FONC;

procedure PROC is
  T : TRUC;
  begin
  T := FONC;
  end PROC;
Exemple2

Lecture contrôlée d'un entier.

procedure LIRE (ENTREE: out INTEGER) is
  begin
  loop
    begin
    put(">");
    get(ENTREE); SKIP_LINE;
    exit;
    exception
      when OTHERS => SKIP_LINE;
                     PUT_LINE("Reponse invalide, entrez une valeur entière");
    end;
  end loop;  
  end LIRE;

Si l'entrée est valide, nous passons à l'instruction suivante et nous sortons de la boucle.

Si l'utilisateur saisit une donnée qui n'est pas entière, l'exception DATA_ERROR est levée. Comme le bloc est à l'intérieur d'une boucle, après écriture du message, le processus est réitéré jusqu'à ce que l'utilisateur entre une réponse valide .

Exemple 3
function  MULT  (U,V ; FLOAT) return FLOAT is
  begin
  return U*V;
  exception
    when CONSTRAINT_ERROR => return FLOAT'LARGE;
  end MULT;
Exemple 4
procedure TRUC is
  type VECTEUR is array (POSITIVE range <>) of FLOAT;
  ...
  function MAX return INTEGER is
    begin
    ...
    end MAX;
  
  procedure SOLUTION is
    TAB : VECTEUR(1..MAX);
    ...
    begin
    ...
    end SOLUTION;

  ...
  
  begin
  ...           
  begin SOLUTION; ou SOLUTION;
  ...
  exception
    when OTHERS => REPARER;
  end;
  exception
    when OTHERS => REPARER;
  end TRUC;
Exemple 5
procedure INVERSION_MATRICE is
  SINGULARITE : EXCEPTION;
  type MATRICE is array(INTEGER range <> ; INTEGER range <>) of FLOAT;
  NB_MATRICE : POSITIVE;
  procedure INVERSER(M : in out MATRICE ) is
    DETERMINANT : FLOAT;
    EPSILON : constant FLOAT := 1.0E-10;
    begin
    --calcul du déterminant de la matrice
    if ABS(DETERMINANT) < EPSILON then
      raise SINGULARITE;
    end if;
    
    --fin du calcul de l'inverse
    end INVERSER;

  procedure TRAITER_UNE_MATRICE is
    M : MATRICE;
    begin
    LIRE(M);
    INVERSER(M);
    ECRIRE(M);
    exception
      when SINGULARITE => put ( "matrice singulière");
    end TRAITER_UNE_MATRICE;

  begin
    --lire NB_MATRICE
    for i in 1..NB_MATRICE loop
      TRAITER_UNE_MATRICE;
    end loop;
  end INVERSION_MATRICE;

Les packages

Un package Ada est un ensemble d'entités ou de ressources de traitement logiquement reliées entre elles. Il permet d'encapsuler ces ressources c'est-à- dire de contrôler exactement la manière dont elles seront manipulées.

Forme des packages

Un package Ada comprend :

  • une partie visible appelée INTERFACE,
  • une partie cachée qui est le CORPS du package,

qui peuvent être compilées séparément.

Syntaxe
package <nom du package> is
  <déclarations>
private
  <déclarations d'objets privés>
end <nom du package>;

Package body <nom du package> is 
  <déclarations des types et variables locales>
  <sous-programmes>
begin
  <rien ou initialisation>
end <nom du package>;

La partie spécification (interface)

Cette spécification regroupe toute les entités que le package exporte, c'est-à-dire toutes les informations nécessaires à la bonne utilisation des entités exportées.

La partie spécification d'un package comprend deux parties :

  • la partie visible accessible à l'utilisateur,
  • éventuellement, une partie privée , nécessaire au compilateur et non accessible à l'utilisateur (mais utilisée dans le corps du package).

Le corps du package (la partie implémentation)

Le corps comporte deux parties :

  • une partie déclarative qui regroupe les objets nécessaires au fonctionnement interne du package, ainsi que la réalisation concrète des unités de programmes déclarées dans le spécification du package (corps des sous-programmes).
  • une partie initialisation qui est exécutée au moment de l'élaboration du package. La plupart du temps, cette partie est vide.
Remarque

La partie spécification est obligatoire. Par contre, le corps peut être absent si le package exporte seulement des types ou des variables.

Un exemple simple

L'exemple suivant est une ébauche de boîte à outils pour la gestion de matrices et de vecteurs de réels.

package MATRICES is
  type VECTEUR is array(POSITIVE range <>) of FLOAT;
  type MATRICE is array(POSITIVE range <>, POSITIVE range <>) of FLOAT;
  
  --Définition des opérations d'entrée/sortie;
  procedure LIRE (V : out VECTEUR);
  procedure LIRE (M : out MATRICE);
  procedure ECRIRE (V : in VECTEUR);
  procedure ECRIRE (M : in MATRICE);

  --Opérations de manipulation des vecteurs et des matrices;
  function IDENTITE(TAILLE : in positive) return MATRICE;
  function "+"(V1, V2 : in VECTEUR) return VECTEUR;
  function "+"(M1, M2 : in MATRICE) return MATRICE;
  function "*"(M1, M2 : in MATRICE) return MATRICE;
  etc...
end MATRICES;

L'utilisation de ce package est faite sans connaître le corps.

with MATRICES; use MATRICES;
procedure UTILISATION is
  -- Déclaration de 2 matrices;
  M1 : MATRICE(1..10, 1..5) := (OTHERS => (others => 0.0));
  M2 : MATRICE(1..5, 1..10) := (OTHERS => (others => 0.0));
  --Création d'une matrice identité d'ordre 10;
  MI : MATRICE(1..10, 1..10) := IDENTITE(10);
  --Déclaration de 3 vecteurs;
  V1, V2, V3 : VECTEUR(1..10);
  -- Déclaration d'une matrice 10e10;
  MRES : MATRICE(1..10, 1..10);

begin
  LIRE(M1); LIRE(M2);
  MRES := M1 + M2; ECRIRE(MRES);
  LIRE(V1); LIRE(V2);
  V3 := V1 + V2; ECRIRE(V3);
  ...
  end UTILISATION;

Le corps du package est le suivant :

with text_io; use text_io;
with ADA.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;


package body MATRICES is
--Définition d'une opération de lecture contrôlée d'un réel, interne au package
  procedure LIRE(R : out FLOAT) is
    begin
    loop
      begin
        put (">"); get (R);
        SKIP_LINE;
        exit;
      exception
        when OTHERS => SKIP_LINE;
                       PUT_LINE("Réponse invalide, entrez une valeur réelle");
      end;
    end loop;
    end LIRE;

  --Corps des opérations d'entrée/sortie;
  procedure LIRE(V : out VECTEUR) is  -- lire un vecteur;
    begin
    for I in V'range loop
      LIRE(V(I));
    end loop;
    end LIRE;

  procedure LIRE(M : out MATRICE) is  --lire une matrice;
    begin
    for I in M'range(1) loop
      for J in M'range(2) loop
        LIRE(M(I,J));
      end loop;
    end loop;
    end LIRE;

  procedure ECRIRE(V : in VECTEUR) is  --écrire un vecteur;
    begin
    for I in V'range loop
      PUT(V(I));
    end loop;
    end ECRIRE;
  
  procedure ECRIRE(M : in MATRICE) is  --écrire une matrice;
    begin
    for I in M'range(1) loop
      for J in M ' range (2) loop
        PUT(M(I, J));
      end loop;
    end loop;
    end ECRIRE;

  --Corps des opérations de manipulation des vecteurs et des matrices
  function IDENTITE(TAILLE : in positive) return MATRICE is
    RESULTAT : MATRICE(1..TAILLE, 1..TAILLE) := (others => (others => 0.0));
    begin
    for I in  1..TAILLE loop
      RESULTAT(I, I) := 1.0;
    end loop;
    return RESULTAT;
    end IDENTITE;

  function "+"(V1, V2 : in VECTEUR) return VECTEUR is --addition de 2 vecteurs
    begin
    declare
    R : VECTEUR (1..V1'LENGTH);
    begin
    for I in R'range loop
      R(I) := V1(I) + V2(I);
    end loop;
    return R;
    end;
    end "+";

  function "*"(M1, M2 : in MATRICE) return MATRICE is --produit de 2 matrices
    begin
    declare
    R : MATRICE(1..M1'LENGTH(1), 1..M2'LENGTH(2));
    CUMUL : FLOAT;
    begin
    for I in R'range (1) loop
      for J in R'range (2) loop
        CUMUL := 0.0;
        for k in M1'range (2) loop
          CUMUL := CUMUL + M1(I, K) * M2(K, J);
        end loop;
        R(I, J) := CUMUL;
      end loop;
    end loop;
    return R;
    end;
    end "*";

    --et la fonction "+" sur les matrices.
    end MATRICES;

Packages et types privés

Nous avons mentionné que les packages permettent de respecter le principe d'abstraction. Un type privé va permettre de protéger certains objets en rendant leur manipulation impossible en dehors des procédures et des fonctions prévues à cet effet. La structure interne d'un type privé est inaccessible de l'extérieur du package.

Il existe deux classes de types privés :

  • les types privés simples,
  • les types privés limités.

Les types privés simples : private

La seule information disponible à l'extérieur du package est celle donnée par la partie visible du package. Le nom du type est disponible, mais l'ensemble des valeurs ou la structure du type sont cachés.

Les seules opérations que l'on peut appliquer aux objets d'un type privé sont les sous-programmes déclarés dans la partie visible, ainsi que l'opérateur d'affectation et les tests d'inégalité et d'égalité.

A l'intérieur d'une spécification de package, nous pouvons non seulement définir des types privés mais également déclarer des constantes du type privé (mais nous ne pouvons pas définir de variables du type privé avant l'élaboration de la partie privée).

Exemple1
package GESTIONNAIRE is
  type MOT_DE_PASSE is private;
  PASSE_NUL : constant MOT_DE_PASSE;
  function OBTENIR return MOT_DE_PASSE;
  function EST_VALIDE (M: in MOT_DE_PASSE) return BOOLEAN;
private
  type MOT_DE_PASSE is range 0..7_000;
  PASSE_NUL : constant MOT_DE_PASSE := 0;
end GESTIONNAIRE;

MOT_DE_PASSE est un type privé. Un utilisateur peut définir son mot de passe en utilisant la fonction OBTENIR. Par contre, il lui est impossible de donner une autre valeur à son mot de passe. Nous pouvons utiliser librement PASSE_NUL à l'extérieur du package de la manière suivante:

MON_MOT : MOT_DE_PASSE := PASSE_NUL;

Les types limités privés : limited private

Les seules opérations que l'on peut appliquer à des objets d'un type limité privé sont les sous-programmes déclarés dans la partie visible du package.

Applications des packages

Collection nommée de déclarations

Le package exporte des objets et des types, n'exporte pas d'autres unités de programmes. Ce type de package n'a pas de corps (car il n'y a pas de déclaration de sous-programme dans l'interface).

Exemple
package INFO_DATE is 
  type NOM_JOUR is (lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche);
  type NOM_MOIS is (janvier, février, mars, avril, mai, juin, juillet, août, septembre, octobre, novembre, décembre);
  type NUM_JOUR is range 1..31;
  type NUM_MOIS is range 1..12;
  type NUM_ANNEE is  range 1900..3000;
end INFO_DATE;
Une utilisation
with INFO_DATE; use INFO_DATE;
procedure UTIL is
  type DATE is record
      JOUR : NUM_JOUR;
      MOIS : NOM_MOIS;
      ANNEE : NUM_ANNEE;
      end record;


  begin
  ...
  end UTIL;

Groupement d'unités de programme reliées

Le package n'exporte pas de type ni d'objet, il n'exporte que des unités de programmes. On retrouve ici la notion habituelle de bibliothèque (mathématique, statistique...). Les sous-programmes définis dans l'interface sont indépendants les uns des autres et sans mémoire. L'ordre des appels est quelconque.

Exemple
package FONC_TRIG is
  function COS(ANGLE : in FLOAT) return FLOAT;
  function SIN(ANGLE : in FLOAT) return FLOAT;
  function TAN(ANGLE : in FLOAT) return FLOAT;
  ...
end FONC_TRIG;


Package body FONC_TRIG is
  --Corps des sous-programmes définis dans l'interface
end FONC_TRIG;

Type de données abstraits

Un type de données abstrait (TDA) est une structure de donnée et les différentes opérations définies sur cette structure. En Ada cela se traduit par un package exportant un seul type ( de préférence privé ou limité privé) et des sous-programmes agissant sur ce type.

Les opérations définies sur un TDA sont de trois types :

  • l'itérateur qui réalise le parcours de la structure,
  • le sélecteur qui interroge l'état de l'objet courant,
  • le constructeur qui modifie l'état de l'objet courant.

L'itérateur et le constructeur sont exprimés sous forme de procédures, le sélecteur sous forme de fonction.

A partir d'un TDA, il est possible de créer plusieurs objets similaires.

Parmi les TDA les plus connus, on trouve les types PILE, FILE, LISTE CONTIGUE, LISTE CHAINEE, ARBRE, ... Ces structures n'ont aucune utilité par elles-mêmes, elles ne servent qu'à organiser, stocker, gérer d'autres entités.

Exemple

Package de gestion d'une file circulaire d'entiers.

package TDA_FILE is 
  type TYPE_FILE (TAILLE: POSITIVE) is limited private;
  FILE_PLEINE, FILE_VIDE : exception;
  --Sélecteurs;
  function EST_VIDE (LA_FILE : in TYPE_FILE) return BOOLEAN;
  function EST_PLEINE (LA_FILE : in TYPE_FILE) return BOOLEAN;
  --Constructeurs;
  procedure VIDER (LA_FILE : in out TYPE_FILE); 
  procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in INTEGER);
  procedure RETIRER(DE_LA_FILE : in out TYPE_FILE; V : out INTEGER);
private
  type TAB is array(POSITIVE range <>) of INTEGER; 
  type TYPE_FILE(TAILLE : POSITIVE) is record
    NB_ELEMENT : NATURAL :=0;
    PREMIER : POSITIVE := 1;
    DERNIER : POSITIVE := TAILLE;
    TABLE : TAB(1..TAILLE);
    end record;
end TDA_FILE;

package body TDA_FILE is
  procedure VIDER (LA_FILE : in out TYPE_FILE) is
    begin
    LA_FILE.NB_ELEMENT := 0;
    LA_FILE.PREMIER := 1;
    LA_FILE.DERNIER := LA_FILE.TAILLE;
    end VIDER;

  function EST_VIDE (LA_FILE : in TYPE_FILE) return BOOLEAN is
    begin
    return LA_FILE.NB_ELEMENT = 0;
    end EST_VIDE;

  function EST_PLEINE (LA_FILE : in TYPE_FILE) return BOOLEAN is
    begin
    return LA_FILE.NB_ELEMENT = LA_FILE.TAILLE;
    end EST_PLEINE;

  procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in INTEGER) is
    N : POSITIVE renames A_LA_FILE.TAILLE;
    begin
    if EST_PLEINE (A_LA_FILE) then
      raise FILE_PLEINE;
    end if;
    A_LA_FILE.DERNIER := A_LA_FILE.DERNIER rem N + 1;
    A_LA_FILE.TABLE (A_LA_FILE.DERNIER) := V;
    A_LA_FILE.NB_ELEMENT := A_LA_FILE.NB_ELEMENT + 1;
    end AJOUTER;

  procedure RETIRER(DE_LA_FILE : in out TYPE_FILE ; V : out INTEGER) is
    N : POSITIVE renames DE_LA_FILE.TAILLE;
    begin
    if EST_VIDE(DE_LA_FILE) then
      raise FILE_VIDE;
    end if;
    V := DE_LE_FILE.TABLE(DE_LA_FILE.PREMIER);
    DE_LA_FILE.PREMIER := DE_LA_FILE.PREMIER rem N + 1;
    DE_LA_FILE.NB_ELEMENT := DE_LA_FILE.NB_ELEMENT - 1;
    end RETIRER;
end TDA_FILE;

TYPE_FILE a été déclaré limité privé pour interdir l'affectation et les comparaisons d'objets de ce type entre eux.

Exemple d'utilisation de ce package
with TDA_FILE; use TDA_FILE;
procedure UTIL is
  F1 : TYPE_FILE(TAILLE => 100);
  F2 : TYPE_FILE(TAILLE => 200);
  ...
  begin
  AJOUTER(A_LA_FILE => F1, V => 40);
  AJOUTER(A_LA_FILE => F2, V => 50);
  ...
  exception 
    when FILE_PLEINE => ...;
    when FILE_VIDE => ...;
  end UTIL;

Les machines abstraites à états

Une machine abstraite est une structure de données cachée et les opérations définies sur cette structure. Le package traduisant une machine abstraite présente une spécification ne contenant que des sous-programmes (et éventuellement des types annexes pour la définition des paramètres).

La structure de données est cachée dans le corps du package qui garde une trace de l'état courant de la structure. Ce sont les sous-programmes de l'interface qui permettent de modifier cet état.

Avec une machine abstraite, un seul objet est créé (sauf si elle est générique).

Exemple
package MACHINE_FILE is
  FILE_PLEINE, FILE_VIDE : exception;
  procedure VIDER ;
  function EST_VIDE return BOOLEAN;
  function EST_PLEINE return BOOLEAN;
  procedure AJOUTER(V : in INTEGER);
  function RETIRER return INTEGER;
end MACHINE_FILE;


with ADA.TEXT_IO; use ADA.TEXT_IO;
with ADA.INTEGER_TEXT_IO; use ADA.INTEGER_TEXT_IO;
package body MACHINE_FILE is
  function TAILLE return POSITIVE is
    N : POSITIVE;
    begin
    PUT("taille de la file : ");
    GET(N);
    return N;
    end TAILLE;

  --Définition interne de la file;
  NB_ELEMENT : POSITIVE := TAILLE;
  subtype INDICE is INTEGER range 1..NB_ELEMENT;
  TABLE : array(INDICE) of INTEGER;
  PREMIER, DERNIER : INDICE := 1;

  procedure VIDER  is
    begin
    PREMIER := 1;
    DERNIER := 1;
    end VIDER;

  function EST_PLEINE return BOOLEAN is
    begin
    return ((DERNIER mod NB_ELEMENT) + 1) = PREMIER;
    end EST_PLEINE;

  function EST_VIDE return BOOLEAN is
    begin
    return PREMIER = DERNIER;
    end EST_VIDE;

  procedure AJOUTER (V : in INTEGER) is
    SUIVANT : INDICE := (DERNIER mod NB_ELEMENT) + 1;
    begin
    if SUIVANT = PREMIER then
      raise FILE_PLEINE;
    end if;
    TABLE (DERNIER) := V;
    DERNIER := SUIVANT;
    end AJOUTER;

  function RETIRER return INTEGER is
    V : INTEGER;
    begin
    if EST_VIDE then
      raise FILE_VIDE;
    end if;
    V := TABLE(PREMIER);
    PREMIER := (PREMIER mod NB_ELEMENT) + 1;
    return V;
    end RETIRER;
end MACHINE_FILE;

Utilisation de ce package:

with MACHINE_FILE; use MACHINE_FILE; -- Le file existe;
procedure UTIL is
  E : INTEGER;
  begin
  AJOUTER(V => 10);  -- Ajoute un élément à la file;
  E := RETIRER;  --Sortie d'un élément;
  end UTIL;

Compléments

Retour sur les types privés

L'utilisation d'un type privé permet de sauvegarder l'intégrité des objets du type, en préservant la confidentialité de la structure du type.

Dans certains cas, les interdictions induites par un type privé peuvent sembler trop strictes. Le concepteur d'un package peut autoriser certaines opérations. Ces autorisations sont données en redéfinissant les opérateurs interdits, dans l'interface du package.

Exemple
package P1 is
  type T1 is private;
  a : constant T1;


  -- Définition d'opérations sur le type T1;
  function "<" (x, y : T1) return BOOLEAN;
private
  type T1 is range 1..100;
  a : constant := 2;
end P1;

package body P1 is
  --Corps des opérations sur T1;
  function "<" (x, y : T1) return BOOLEAN is
    begin
    return INTEGER(x) < INTEGER(y);
    end "<";
  ...
end P1;

Un peu d'héritage

Soient les structures de package suivantes :

Package PAC1 is
  type T1 is range 1..100;
  procedure O1 (P1 : in T1 ; P2 : out T1);
  function F1 (P : in T1) return STRING;
end PAC1;


with PAC1; use PAC1;
package PAC2 is
  subtype S1 is T1 range 1..10;
  procedure O2 (P2 : in S1 ; R : out BOOLEAN);
end PAC2;


with PAC2; use PAC2;
procedure UTIL is
  V1 : S1;
  R1 : BOOLEAN;
  V2 : T1;  --Impossible, PAC1 n'est pas dans le contexte de UTIL;
  begin
  O2(V1, R1);
  --Les opérations de PAC1 ne sont pas accessibles;
  ...
  end UTIL;
Solution 1

La solution la plus directe est de rajouter PAC1 dans le contexte de UTIL.

Solution 2

Une autre solution consiste à renommer dans PAC2 les éléments de PAC1 susceptibles d'être utilisés. Après ce « renommage », ces éléments sont considérés comme appartenant à PAC1.

PAC1 reste inchangé et PAC2 devient :

with PAC1; use PAC1;
package PAC2 is
  subtype S1 is T1 range 1..10;
  procedure O1(P1 : in S1 ; P2 : out S1) renames PAC1.O1;
  procedure O2(P2 : in S1 ; R : out BOOLEAN);
end PAC2;


with PAC2; use PAC2;
procedure UTIL is
  V1, V2 : S1;
  R1 : BOOLEAN;
  Begin
  V1 := 1;
  O1(V1, V2);
  O2(V2, R1);
  end UTIL;
Solution 3

Utiliser la dérivation de type.

PAC1 reste inchangé et PAC2 devient :

with PAC1; use PAC1;
package PAC2 is
  type D1 is new T1;
  subtype S1 is T1 range 1..10;
  procedure O2(P2 : in S1 ; R : out BOOLEAN);
end PAC2;


with PAC2; use PAC2;
procedure UTIL is
  V1, V2 : D1;
  R1 : BOOLEAN;
  V3 : S1;
  begin
  V1 := 2;
  V3 := 3;
  O1(V1, V2);
  O2(V3, R1);
  end UTIL;

La généricité

Introduction

Jusqu'ici, nous avons vu comment réaliser des packages et des procédures en donnant seulement leurs spécifications, tout en gardant secret leur réalisation. Ada permet de faire mieux encore.

Prenons un exemple : soient un programme de traitement des graphes des stations de métro et un programme de gestion des graphes de connections électroniques. Ces deux programmes n'ont à priori rien à voir entre eux, et pourtant !. Le programme de gestion des graphes est commun aux deux.

Cet exemple est caractéristique de la généricité : programmer générique, c'est programmer en "blanc" en pensant à la réutilisabilité.

Quelles sont les raisons qui peuvent amener à concevoir générique:

  • il existe des algorithmes "naturellement" génériques pour certains traitements : tris, gestion de listes, gestion de graphes, gestion de piles, . Cela couvre aussi la notion de type abstrait;
  • pour paramétrer un traitement par des procédures ou des fonctions différentes.

Pour notre exemple de gestion de graphes, il faut construire un modèle générique sur les nouds et les arcs du graphe. Le type du contenu des nouds sera le paramètre générique. Les opérations à fournir dans les spécifications sont:

  • créer un noud avec telle valeur,
  • créer un arc entre deux nouds,
  • attribuer un coût ( ou un poids) à un arc,
  • donner les nouds voisins d'un noud,
  • etc.

L'une des difficultés, comme pour un package non générique, est de clore la liste des opérations.

Qui peut-être générique ? Toutes les unités de compilation : procédure, fonction, package.

La généricité est donc la propriété qui permet d'engendrer différentes versions d'un modèle, dit modèle générique. La fabrication d'une version s'appelle une instanciation et une version, une instance.

L'instanciation consiste à:

  • fournir un identificateur qui désignera l'unité de programme ( la version),
  • donner une liste de paramètres effectifs correspondant aux paramètres formels génériques.

Définition d'une unité générique

Une unité générique est composée de trois parties:

  • la déclaration des paramètres génériques introduite par le mot clé generic,
  • la spécification de l'unité générique,
  • le corps de l'unité générique.
Définition d'un sous-programme générique
generic
  <partie générique>
  procédure <nom> (paramètres);
  procédure <nom> (paramètres) is
  <déclarations>
begin
  <instructions>
end <nom>;
Définition d'un package générique
generic
  <partie générique>
package <nom > is
  <déclarations>
private
  <déclarations d'objets privés>
end <nom>;

package body <nom> is
  <déclarations des types et variables locales>
  <corps des sous-programmes>
begin
  <rien ou initialisation>
end <nom >;

La création d'une instance d'un package est définie par:

package <identificateur> is new <nom du package générique>(<paramètres effectifs>);

La création d'une instance d'un sous-programme est définie par :

Procedure <identificateur> is new <nom de la procedure générique>(<paramètres effectifs>);
function <identificateur> is new <nom de la fonction générique>(<paramètres effectifs>);

Paramètres génériques

Les paramètres de la généricité sont classés en trois types. Ils peuvent être:

  • des types,
  • des constantes ou des variables,
  • des sous-programmes.

Les types

La Figure VI-1 résume les formes des paramètres formels de la généricité et définit quels types effectifs sont associables.

Formes des types de la généricité et des types effectifs compatibles
type DISCRET is (<>); --à associer à tout type discret (types entier et énumération)
type ENTIER is range <>; --à associer à tout type entier
type MODULAIRE is mod <>; --à associer à tout type modulaire
type FIXE is delta <>; --à associer à tout type point fixe
type FIXE_DECIMAL is delta <> digits <>; --à associer à tout type point fixe décimal
type FLOTTANT is digits <>; --à associer à tout type  point flottant
type TABLEAU_CONTRAINT is array (INDEX) of ENTIER; --à associer à  tout type tableau contraint de mêmes dimensions,
                                                   --types d'index, et type de composant
type TABLEAU_NON_CONTRAINT is array (INDEX range <>) of ENTIER; --à associer à tout type tableau non contraint de mêmes
                                                                --dimensions, types d'index et types de composants.
type USAGE_GENERAL is limited private; --à associer à n'importe quel type
type ELEMENT is private; --à associer à n'importe quel type autorisant l'affectation et le test d'(in)égalité
type PTR is access ELEMENT; --à associer à un type accès
EXEMPLE

Réalisation d'une procédure générique de recherche dans un tableau d'éléments de type discret.

generic
  type ELEMENT is (<>);  --le type des éléments du tableau
  type INDEX is (<>);   --le type de l'indice du tableau
  type VECTEUR is array(INDEX range <>) of ELEMENT; --le type du tableau

  procedure RECHERCHE(VEC : in VECTEUR ; VALEUR : in ELEMENT; OK : out BOOLEAN ; IND : out INDEX );
  --la convention d'appel de la procédure

  procedure RECHERCHE(VEC : in VECTEUR ; VALEUR : in ELEMENT; OK : out BOOLEAN ; IND : out INDEX ) is
    TROUVE : BOOLEAN := FALSE;
    begin
    for I in VEC'range loop
      TROUVE := VEC(I) = VALEUR;
      if TROUVE then IND := I;
        EXIT;
      end if;
    end loop;
    OK := TROUVE;
    end RECHERCHE;

Une utilisation possible :

with RECHERCHE; 
procedure UTIL is
  type ENTIER is range 50 ..100;
  type JOUR is (LUN, MAR, MER, JEU, VEN, SAM, DIM);
  type LISTE is array(1..100) of JOUR;
  type TAB is array(POSITIVE range 1..50) of ENTIER;


  --création d'une version pour un tableau de jours
  procedure RECH is new RECHERCHE(ELEMENT => JOUR, INDEX  => INTEGER, VECTEUR => LISTE);

  --création d'une version pour un tableau d'entiers
  procedure RECH is new RECHERCHE(ELEMENT => ENTIER, INDEX => POSITIVE, VECTEUR => TAB);

  L : LISTE (LUN, MER, VEN, DIM);   --Une liste de jours
  DAY : JOUR := VEN; 
  RES : boolean; 
  POSITION : INTEGER;
  T : TAB(1..5) := (2, 4, 6, 8, 10);  -- Un tableau d'entiers
  INDICE : POSITIVE;
  begin
  RECH(VEC => L, VALEUR => DAY, OK => RES, IND => POSITION);
  RECH(VEC => T, VALEUR => 8, OK => RES, IND => INDICE);
  ...
  end UTIL;

Pour généraliser complètement cette procédure, il suffit de déclarer ELEMENT en private ou limited private. Dans ce dernier cas, le test d'égalité doit être redéfini.

On peut alors faire une recherche dans un tableau d'enregistrements (cas ou ELEMENT est privé) :

with RECHERCHE;
procedure UTIL is
  type PERSONNE is record
      AGE : POSITIVE;
      NOM : STRING(1..5);
      end record;


  type TAB is array(POSITIVE range 1..50) of PERSONNE;


  --création d'une version pour un tableau de personnes
  procedure RECH is new RECHERCHE(ELEMENT => PERSONNE, INDEX  => POSITIVE, ..., VECTEUR => TAB);

  begin
  ...
  end UTIL;

Constantes et variables

Ada permet de définir des constantes et des variables paramètres de la généricité. La déclaration de tels paramètres prend la forme d'une déclaration de constante, en ajoutant le mot clé in, ou d'une variable avec in out. Le mode out n'est pas possible.

Lorsqu'on utilise de tels paramètres, on doit faire correspondre au paramètre formel une constante ou une variable du même type.

L'avantage de cette classe de paramètres génériques est de permettre d'instancier une unité de programme en fournissant des limites constantes aux algorithmes ou aux objets abstraits.

Exemple
generic
  LIGNE : in INTEGER := 24;
  COLONNE : in INTEGER := 80;
package TERMINAL is ...

Des instances possibles :

package MINITEL is new TERMINAL(24, 40);
package IMPRIMANTE is new TERMINAL(66, 132);
package CONSOLE is new TERMINAL;

Paramètres sous-programmes

La notion de sous-programmes en paramètres génériques est nécessaire pour deux raisons :

  • en Ada, on ne peut utiliser des sous-programmes en tant que paramètres d'autres sous-programmes. Cette contrainte peut être contournée en Ada95 en utilisant des pointeurs sur les sous-programmes. Mais on peut passer des sous-programmes comme paramètres de la généricité.
  • un type formel générique privé ou limité privé, n'autorise que l'utilisation des opérations :=, /=, = pour le type privé, et interdit toute opération pour le type limité privé, dans le corps de l'unité générique. Pour réintroduire des opérations interdites, on définit ces opérations sous la forme de sous-programmes paramètres génériques.

Les sous-programmes formels génériques sont décrits dans une partie générique comme une déclaration de sous-programme précédée de la clause with. Trois formulations sont possibles :

1ère forme:
generic
  ...
  with <nom de sous-programme>;
package <nom du package> ...

Avec cette définition, il n'existe pas de valeur par défaut pour ce sous-programme. Lors de l'instanciation, le nom d'un sous-programme effectif est obligatoire.

2ème forme:

Un sous-programme de même nom que le paramètre formel peut être pris par défaut dans l'environnement d'instanciation .

generic
  ...
  with <définition d'une procédure ou fonction générique> is <>;
package <nom du package> ...
3ème forme:

Un sous-programme de nom nom prédéfini ou redéfini est pris par défaut.

generic
  ...
  with <nom de sous-programme> is <nom>;
package <nom du package> ...
Utilisation pour généraliser l'emploi de sous-programmes

Dans l'exemple suivant, la fonction INTEGRER calcule l'intégrale d'une fonction entre deux valeurs MIN et MAX. La fonction à intégrer constitue un paramètre générique. De plus, INTEGRER doit pouvoir être utilisée pour tout type flottant.

Spécification de la fonction générique INTEGRER
generic
  type FLOTTANT is digits <>;
  with function F ( X : FLOTTANT ) return FLOTTANT;
  function INTEGRER (MIN,MAX : FLOTTANT) return FLOTTANT;
Remarque

le sous-programme paramètre générique est une fonction à une variable, à associer à une fonction effective à une variable lors de l'instanciation.

Corps de la fonction générique INTEGRER
function INTEGRER(MIN, MAX : FLOTTANT) return FLOTTANT is
  RESULTAT : FLOTTANT;
  begin
  --tout ce qu'il faut pour calculer l'intégrale
  return RESULTAT;
  end INTEGRER;
Utilisation de la fonction générique
with INTEGRER;
procedure UTIL is
  type REEL is digits 6;


  --Définition de la fonction à intégrer
  function A_INTEGRER ( V : REEL) return REEL is
    begin
    return X * X + 1.0;
    end A_INTEGRER;

  --Création d'une instance de INTEGRER
  function INTEGRER_F is new INTEGRER(FLOTTANT => REEL; F => A_INTEGRER);

  R : REEL;
  begin
  R := INTEGRER_F(MIN => 0.0, MAX : 0.1); --Appel de l'instance;
  ...
  end UTIL;
Utilisation liée au type privé

Pour réaliser une procédure de tri d'un tableau d'éléments de type quelconque, il est nécessaire de définir quels sont les éléments à placer dans la partie générique.

La définition du tableau doit être suffisamment général : l'indice doit être de type discret et le type des éléments quelconque (entier, réel, article, ...).

Il est donc nécessaire d'utiliser un type privé pour définir le type des éléments du tableau. Or, pour réaliser un algorithme de tri, un opérateurs de comparaison (autre que /= et =) est indispensable.

Spécification d'un sous-programme de tri générique
generic
  type OBJET is private;  --tout type avec /=, =, :=
  type INDICE is (<>);    --tout type discret
  type TABLEAU is array(INDICE) of OBJET;  --tout tableau contraint
  with function "<="(OP1, OP2 : in OBJET) return BOOLEAN is <>;  --une fonction de comparaison
  procedure TRI_TOUT(TAB : in out TABLEAU);
    Corps du sous-programme de tri générique (méthode de sélection simple)

  procedure TRI_TOUT(TAB : in out TABLEAU) is
    J : INDICE; U : OBJET;
    begin
    for I in TAB'first..INDICE'pred(TAB'last) loop
      J := I;
      for K in INDICE'succ(I)..TAB'last loop
        if TAB(K) <= TAB(J) then
          J := K;
        end if;
      end loop;
      U := TAB(J); TAB(J) := TAB(I);
      TAB(I) := U;
    end loop;
  end TRI_TOUT;
Utilisation
with TRI_TOUT;
procedure ESSAI is
  type NATIONALITE is (ITALIEN, FRANCAIS, ALLEMAND, BELGE);
  type TAB_REEL is array(NATIONALITE) of FLOAT;

  --une instance pour un tri par ordre croissant
  procedure TRI_CROISSANT is new TRI_TOUT(OBJET => FLOAT, INDICE => NATIONALITE, TABLEAU => TAB_REEL);

  --une instance pour un tri décroissant
  procedure TRI_DECROISSANT is new TRI_TOUT(OBJET => FLOAT, INDICE => NATIONALITE, TABLEAU => TAB_REEL, "<=" => ">=");
    T1 : TAB_REEL(1.0, 6.7, 4.5, 7.3);  --un tableau de réels

  begin
  TRI_CROISSANT(TAB => T1);
  TRI_DECROISSANT(TAB => T1);
  ...
  end ESSAI;
Utilisation pour un tableau d'enregistrement

Pour des éléments de type enregistrement, il n'existe pas de fonction de comparaison <= prédéfinie. Cet opérateur doit être créé dans l'environnement de l'instanciation.

with TRI_TOUT;
procedure TRI_RECORD is
  type personne is record
       nom : STRING(1..4);
       TAILLE : POSITIVE;
       end record;

  type TAB_PERS is array(1..3) of PERSONNE;

  function "<="(P1, P2 : PERSONNE) return BOOLEAN is
    begin
    return P1.TAILLE <= P2.TAILLE ;
    end "<=";

  --lors del'instanciation, la fonction est facultative
  procedure TRIER is new TRI_TOUT(OBJET  => PERSONNE, INDICE => INTEGER, TAB    => TAB_PERS);
    T : TAB_PERS := (("TITI, 183), ("TOTO", 157), ("LULU", 120);
    begin
    TRIER(TAB => T);
    ...
    end TRI_RECORD;
Utilisation d'une unité générique dans une autre unité générique

Dans la procédure TRI_TOUT, l'échange de deux variables est utilisée. Cette opération est souvent utilisée dans d'autres contextes et peu donc être générique.

Procédure d'échange générique
Generic
  type ELEMENT is private;
  procedure ECHANGE(A, B : in out ELEMENT);
  procedure ECHANGE(A, B : in out ELEMENT) is
    TEMP : ELEMENT;
    begin
    TEMP := A; A := B; B := TEMP;
    end ECHANGE;

La procédure TRI_TOUT peut alors s'écrire :

with ECHANGE;
generic
  type OBJET is private;  --tout type avec /=, =, :=
  type INDICE is (<>);    --tout type discret
  type TABLEAU is array(INDICE) of OBJET; --tout tableau contraint

  with function "<="(OP1, OP2 : OBJET) return BOOLEAN is <>; --une fonction de comparaison

  procedure TRI_TOUT(TAB : in out TABLEAU);
  procedure TRI_TOUT(TAB : in out TABLEAU) is
    procedure SWAP is new ECHANGE(OBJET);
      J : INDICE; U : OBJET;
      begin
      for I in TAB'first..INDICE'pred(TAB'last) loop
        J := I;
        for K in INDICE'succ(I)..TAB'last loop
          if TAB(K) <= TAB(J) then 
            J:= K; 
          end if;
        end loop;
        SWAP(TAB(I), TAB(J));
      end loop;
    end TRI_TOUT;
Les packages génériques

Les packages définissant des TDA ou des machines abstraites sont en générale indépendants des objets qu'ils manipulent. Ils doivent donc être génériques.

Les deux packages de définition d'une file s'écrivent alors :

generic
  type ELEMENT is private;
package TDA_FILE is 
  type TYPE_FILE(TAILLE : POSITIVE) is limited private;
  FILE_PLEINE, FILE_VIDE : exception;

  --Sélecteurs;
  function EST_VIDE(LA_FILE : in TYPE_FILE) return BOOLEAN;
  function EST_PLEINE(LA_FILE : in TYPE_FILE) return BOOLEAN;

  --Constructeurs;
  procedure VIDER(LA_FILE : in out TYPE_FILE);
  procedure AJOUTER(A_LA_FILE : in out TYPE_FILE ; V : in ELEMENT);
  procedure RETIRER (DE_LA_FILE : in out TYPE_FILE ; V : out ELEMENT);
private
  type TAB is array (POSITIVE range <>) of ELEMENT; 
  type TYPE_FILE(TAILLE : POSITIVE) is record
      NB_ELEMENT : NATURAL :=0;
      PREMIER : POSITIVE := 1;
      DERNIER : POSITIVE := TAILLE;
      TABLE : TAB(1..TAILLE);
      end record;
end TDA_FILE;

package body TDA_FILE is
  procedure VIDER(LA_FILE : in out TYPE_FILE) is
    begin
    LA_FILE.NB_ELEMENT := 0;
    LA_FILE.PREMIER := 1;
    LA_FILE.DERNIER := LA_FILE.TAILLE;
    end VIDER;

  function EST_VIDE(LA_FILE : in TYPE_FILE) return BOOLEAN is
    begin
    return LA_FILE.NB_ELEMENT = 0;
    end EST_VIDE;

  function EST_PLEINE(LA_FILE : in TYPE_FILE) return BOOLEAN is
    begin
    return LA_FILE.NB_ELEMENT = LA_FILE.TAILLE;
    end EST_PLEINE;

  procedure AJOUTER(A_LA_FILE : in out TYPE_FILE; V : in ELEMENT) is
    N : POSITIVE renames A_LA_FILE.TAILLE;
    begin
    if EST_PLEINE (A_LA_FILE) then
      raise FILE_PLEINE;
    end if;
    A_LA_FILE.DERNIER := A_LA_FILE.DERNIER rem N + 1;
    A_LA_FILE.TABLE(A_LA_FILE.DERNIER) := V;
    A_LA_FILE.NB_ELEMENT := A_LA_FILE.NB_ELEMENT + 1;
    end AJOUTER;

  procedure RETIRER(DE_LA_FILE : in out TYPE_FILE ; V: out ELEMENT) is
    N : POSITIVE renames DE_LA_FILE.TAILLE;
    begin
    if EST_VIDE(DE_LA_FILE) then
      raise FILE_VIDE;
    end if;
    V := DE_LE_FILE.TABLE(DE_LA_FILE.PREMIER);
    DE_LA_FILE.PREMIER := DE_LA_FILE.PREMIER rem N + 1;
    DE_LA_FILE.NB_ELEMENT := DE_LA_FILE.NB_ELEMENT - 1;
    end RETIRER;
end TDA_FILE;
Exemple d'utilisation de ce package
with TDA_FILE;  --Pas de clause use ici!!!
procedure UTIL is
  package FILE_ENTIER is new TDA_FILE(ELEMENT => INTEGER); 
  use FILE_ENTIER;
  F1 : TYPE_FILE(TAILLE => 100);
  F2 : TYPE_FILE(TAILLE => 200);
  ...

  begin
  AJOUTER(A_LA_FILE => F1, V => 40);
  AJOUTER(A_LA_FILE => F2, V => 50);
  ...
  exception 
    when FILE_PLEINE => ...;
    when FILE_VIDE => ...;
  end UTIL;
Redéfinition de la machine abstraite
generic
  TAILLE : POSITIVE :=100;
  type ELEMENT is private;

package MACHINE_FILE is
  FILE_PLEINE, FILE_VIDE : exception;
  procedure VIDER ;
  function EST_VIDE return BOOLEAN;
  function EST_PLEINE return BOOLEAN;
  procedure AJOUTER (V : in ELEMENT);
  function RETIRER return ELEMENT;
end MACHINE_FILE;

package body MACHINE_FILE is
  TABLE : array(TAILLE) of ELEMENT;
  PREMIER, DERNIER : INDICE := 1;

  procedure VIDER  is
    begin
    PREMIER := 1;
    DERNIER := 1;
    end VIDER;

  function EST_PLEINE return BOOLEAN is
    begin
    return ((DERNIER mod NB_ELEMENT) + 1) = PREMIER;
    end EST_PLEINE;

  function EST_VIDE return BOOLEAN is
    begin
    return PREMIER = DERNIER;
    end EST_VIDE;

  procedure AJOUTER (V : in ELEMENT) is
    SUIVANT : INDICE := (DERNIER mod TAILLE) + 1;
    begin
    if SUIVANT = PREMIER then
      raise FILE_PLEINE;
    end if;
    TABLE (DERNIER) := V;
    DERNIER := SUIVANT;
    end AJOUTER;

  function RETIRER return ELEMENT is
    v : ELEMENT;
    begin
    if EST_VIDE then
      raise FILE_VIDE;
    end if;
    V := TABLE(PREMIER);
    PREMIER := (PREMIER mod TAILLE) + 1;
    return V;
    end RETIRER;
end MACHINE_FILE;
Utilisation de ce package:
with MACHINE_FILE;
procedure UTIL is
  package FILE_REEL is new MACHINE_FILE(200, FLOAT);
  package FILE_CHARACTER is new MACHINE_FILE(300, CHARACTER);

  begin
  FILE_REEL.AJOUTER(V => 10.0);  --Ajoute un élément à la file de réels
  FILE_CHARACTER.AJOUTER(V => 'C'); --Ajoute un élément à la file de caractères
  ...
  end UTIL;

Dans l'environnement du langage, un certains nombres de package génériques sont disponibles. Leur définition est donnée en annexe.

Compilation séparée et composition incrémentale

Dans ce chapitre, nous étudions les aspects organisationnels. Les sous-programmes, les packages sont des unités permettant de décomposer logiquement un programme (en modules logiques). Ada met à la disposition du programmeur un ensemble d'outils permettant l'organisation physique du programme (en modules physiques).

La compilation séparée

Les sous-programmes, les packages sont des modules de compilation. S'ils sont définis dans un autre sous-programme ou un package, il peuvent être séparés physiquement de l'unité englobante en utilisant l'option separate. Cette option signale que le sous-programme se trouve physiquement dans un autre fichier.

Reprenons l'exemple du package MATRICES :

(La spécification de ce package reste inchangée.)

package MATRICES is
  type VECTEUR is array  (POSITIVE range <>) of FLOAT;
  type MATRICE is array (POSITIVE range <>, POSITIVE range <>) of FLOAT;

  --Définition des opérations d'entrée sortie
  procedure LIRE(V : out VECTEUR);
  procedure LIRE(M : out MATRICE);
  procedure ECRIRE(V : in VECTEUR);
  procedure ECRIRE(M : in MATRICE);

  --Opérations de manipulation des vecteurs et des matrices
  function IDENTITE (TAILLE : in positive) return MATRICE;
  function "+"(V1, V2 : in VECTEUR) return VECTEUR;
  function "+"(M1, M2 : in MATRICE) return MATRICE;
  function "*"(M1, M2 : in MATRICE) return MATRICE;
  ...
end MATRICES;

Dans le corps du package, on peut décider de séparer les opérations de manipulation des vecteurs et des matrices et de garder seulement les opérations d'entrée/sortie.

with text_io; use text_io;
with ADA.FLOAT_TEXT_IO; use ADA.FLOAT_TEXT_IO;

package body MATRICES is
  --Définition d'une opération de lecture contrôlée d'un réel, interne au package;
  procedure LIRE(R : out FLOAT) is
    begin
    loop
      begin
      put (">"); get (R);
      SKIP_LINE;
      exit;
      exception
        when OTHERS => SKIP_LINE;
                       PUT_LINE("Réponse invalide, entrez une valeur réelle");
      end;
    end loop;
    end LIRE;

  --Corps des opérations d'entrée sortie;
  procedure LIRE(V : out VECTEUR)  --lire un vecteur
    begin
    for I in V ' range loop
      LIRE(V(I));
    end loop;
    end LIRE;

  procedure LIRE(M : out MATRICE) is  --lire une matrice
    begin
    for I in M'range (1) loop
      for J in M'range (2) loop
        LIRE(M(I, J));
      end loop;
    end loop;
    end LIRE;

  procedure ECRIRE(V : in VECTEUR) is  --écrire un vecteur
    begin
    for I in V ' range loop
      PUT(V(I));
    end loop;
    end ECRIRE;

  procedure ECRIRE ( M  : in MATRICE) is  --écrire une matrice
    begin
    for I in M'range (1) loop
      for J in M'range (2) loop
        PUT(M(I, J));
      end loop;
    end loop;
    end ECRIRE;

  --Corps des opérations de manipulation définis séparemment
  function IDENTITE(TAILLE : in positive) return MATRICE is separate;
  function "+" (V1,V2 : in VECTEUR) return VECTEUR is separate;
  function "*" (M1,M2 : in MATRICE) return MATRICE is separate;
  --et la fonction "+" sur les matrices
end MATRICES;

Les corps des sous-programmes sont soit dans le même fichier, soit chacun dans un fichier. Il est bien sûr nécessaire de préciser à quelle unité un sous-programme séparé appartient à l'aide d'une clause separate(nom de l'unité) :

separate (MATRICES)
  function IDENTITE ( TAILLE : in positive) return MATRICE is
    RESULTAT : MATRICE(1..TAILLE, 1..TAILLE) := (others => (others => 0.0));
    begin
    for I in 1..TAILLE loop
      RESULTAT(I, I) := 1.0;
    end loop;
    return RESULTAT;
    end IDENTITE;

separate (MATRICES)
  function "+"(V1, V2 : in VECTEUR) return VECTEUR is 
    begin
    declare
      R : VECTEUR (1..V1'LENGTH);
    begin
    for I in R'range loop
      R(I) := V1(I) + V2(I);
    end loop;
    return R;
    end;
   end "+";

separate (MATRICES)
  function "*"(M1, M2 : in MATRICE) return MATRICE is 
    begin
    declare
      R : MATRICE (1..M1'LENGTH(1), 1..M2'LENGTH(2));
      CUMUL : FLOAT;
    begin
    for I in R'range (1) loop
      for J in R'range (2) loop
        CUMUL := 0.0;
        for k in M1'range (2) loop
          CUMUL := CUMUL + M1(I, K) * M2(K, J);
        end loop;
        R(I,J) := CUMUL;
      end loop;
    end loop;
    return R;
    end;
    end "*";

Composition incrémentale : unité parent, unité enfant

Afin de faciliter la mise au point de très grosses applications et surtout leur évolution, la notion de packages hiérarchiques à été introduite. Il est possible de rajouter des fonctionnalités à un package, sans avoir à recompiler le package.

Reprenons l'exemple du package MATRICES : ce package contient des opérations d'entrées/sorties, des opérations de manipulation de matrices, des opérations de manipulation de vecteurs et naturellement, la définition des différents types nécessaires. On va donc définir un package parent pour la définition des types, puis des packages enfants pour chaque ensembles d'opérations. La définition d'un package enfant est donnée par : nom du package parent . nom du package enfant.

Définition du package parent :

package MATRICES is
  type VECTEUR is array (POSITIVE range <>) of FLOAT;
  type MATRICE is array(POSITIVE range <>, POSITIVE range <>) of FLOAT;
end matrices;

Définition d'un package enfant pour les entrées /sorties :

package MATRICES.ES is
  procedure LIRE(V : out VECTEUR);
  procedure LIRE(M : out MATRICE);
  procedure ECRIRE(V : in VECTEUR);
  procedure ECRIRE(M : in MATRICE);
end MATRICES.ES;

Définition d'un package enfant pour la manipulation des vecteurs :

package MATRICES.VECTEURS is
  function "+"(V1, V2 : in VECTEUR) return VECTEUR;
end MATRICES.VECTEURS; 

Définition d'un package enfant pour la manipulation des matrices :

packages MATRICES.MATRICES is
  function IDENTITE(TAILLE : in positive) return MATRICE;
  function "+"(M1, M2 : in MATRICE) return MATRICE;
  function "*"(M1, M2 : in MATRICE) return MATRICE;
end MATRICES.MATRICES;

Chacun des ces packages enfants a un corps défini avec les mêmes algorithmes que précédemment. Ils ne sont donc pas rappelés.

L'utilisation de cette hiérarchie peut être faite de différentes façons. Si l'utilisateur n'a besoin que de la définition des types tableaux, il précisera seulement with MATRICES dans la clause de contexte de son unité. S'il souhaitent utiliser la définition des types tableaux et les opérations d'entrées/sorties, il précisera with MATRICES, MATRICES.ES ; etc...

Pour rajouter des opérations diverses sur les matrices, par exemple, il n'est pas nécessaire de modifier l'existant. On peut créer un package enfant au package MATRICES.MATRICES qui peut s'appeler MATRICES.MATRICES.DIVERS !

Approche objet

Concepts fondamentaux de la programmation orientée objet

La programmation structurée est basée sur une décomposition en actions.

La programmation orientée objet travaille avec une décomposition en objet.

les instructions élémentaires sont les mêmes, mais leur regroupement est différent.

On trouve dans l'approche objet trois principes fondamentaux :

  • l'encapsulation : un objet regroupe à la fois se attributs et ses opérations associées,
  • l'indépendance temporelle : le comportement d'un objet est indépendant du contexte dans lequel il est appelé,
  • l'indépendance spatiale : les informations relatives à une même entité sont physiquement dans le même module.
  • La programmation orientée objet apporte de nouvelles notions :
  • notion de classe : une classe regroupe des objets ayant des propriétés communes (factorisation des propriétés),
  • notion d'héritage : une sous-classe est définie à partie d'une classe avec des propriétés supplémentaires. La sous-classe hérite des propriétés et des opérations de la classe parente,
  • notion de polymorphisme : permet d'écrire des programmes de même but, donc de même nom, mais avec des implémentations différentes selon la nature des objets sur lesquels ils portent,
  • notion de liaison dynamique : qui est la capacité à associer le service surchargé correct en fonction de la référence de la classe,
  • notion de généricité : pour décrire des données et des opérations indépendantes déléments de la classe.

Traduction en ADA

  • une classe : un package contenant une définition d'un type étiqueté (tagged) sur la base d'un type enregistrement, et les opérations portant sur ce type,
  • l'héritage : obtenu par dérivation du type étiqueté avec des attributs et des opérations supplémentaires, des méthodes de même spécification que dans la classe parente, mais adaptées au type dérivé,
  • la liaison dynamique : une opération dans une classe est définie à l'échelle de la classe en utilisant l'attribut 'class dans la définition des paramètres.

Exemple

Définition de la classe ARTICLE

package CLASSE_ARTICLE is
  type PRIX is delta 0.01 digits 9;
  type QUANTITE is range 0..10000;
  type ARTICLE is tagged record
       REFERENCE : STRING(1..4);
       DESIGNATION : STRING(1..10);
       PRIX_HT : PRIX;
       QUANTITE_STOCK : QUANTITE;
       end record;

  procedure GET (UN_ARTICLE : out ARTICLE);
  procedure PUT (UN_ARTICLE : in ARTICLE);
  function PRIX_TTC (UN_ARTICLE : in ARTICLE) return PRIX;
  function VALORISER_STOCK(UN_ARTICLE : in ARTICLE'CLASS) return PRIX;
end CLASSE_ARTICLE;

with ADA.TEXT_IO; use ADA.TEXT_IO;
package body CLASSE_ARTICLE is
  package ES_PRIX is new DECIMAL_IO(PRIX); use ES_PRIX;
  package ES_QUANTITE is new INTEGER_IO(QUANTITE); use ES_QUANTITE;

  procedure GET (UN_ARTICLE : out ARTICLE) is
    begin
    PUT("Donnez la reference sur 4 caracteres : ");
    GET(UN_ARTICLE.REFERENCE); NEW_LINE;
    PUT("Donnez la designation sur 10 caracteres : "); 
    GET(UN_ARTICLE.DESIGNATION);NEW_LINE;
    PUT("Donnez le prix hors taxe : ");
    GET(UN_ARTICLE.PRIX_HT); NEW_LINE;
    PUT(" Donnez le stock : ");
    GET(UN_ARTICLE.QUANTITE_STOCK); NEW_LINE;
    end GET;

  procedure PUT(UN_ARTICLE : in ARTICLE) is
    begin
    PUT("Reference : ");
    PUT(UN_ARTICLE.REFERENCE); NEW_LINE;
    PUT("Designation: ");
    PUT(UN_ARTICLE.DESIGNATION); NEW_LINE;
    PUT("Prix hors taxe : ");
    PUT(UN_ARTICLE.PRIX_HT); NEW_LINE;
    PUT("Stock :" );
    PUT(UN_ARTICLE.QUANTITE_STOCK); NEW_LINE;
    end PUT;

  function PRIX_TTC(UN_ARTICLE : in ARTICLE) return PRIX is
    begin
    PUT_LINE("Application methode PRIX_TTC de CLASSE_ARTICLE");
    return UN_ARTICLE.PRIX_HT * 1.0;
    end PRIX_TTC;

  function VALORISER_STOCK(UN_ARTICLE : in ARTICLE'CLASS) return PRIX is
    begin
    return PRIX_TTC(UN_ARTICLE) * INTEGER(UN_ARTICLE.QUANTITE_STOCK);
    end VALORISER_STOCK;
end CLASSE_ARTICLE;

Définition de la sous-classe ARTICLE_LUXE

with CLASSE_ARTICLE; use CLASSE_ARTICLE;
package CLASSE_ARTICLE_LUXE is
  type ARTICLE_LUXE is new ARTICLE with record
       MARQUE : STRING(1..10);
       end record;
  procedure GET(UN_ARTICLE_LUXE : out ARTICLE_LUXE);
  procedure PUT(UN_ARTICLE_LUXE : in ARTICLE_LUXE);
  function PRIX_TTC(UN_ARTICLE_LUXE : in ARTICLE_LUXE) return PRIX;
end CLASSE_ARTICLE_LUXE;

with ADA.TEXT_IO;use ADA.TEXT_IO;

package body CLASSE_ARTICLE_LUXE is
  procedure GET (UN_ARTICLE_LUXE : out ARTICLE_LUXE) is
    begin
    GET(ARTICLE(UN_ARTICLE_LUXE));
    PUT("Donner la marque sur 10 caracteres : ");
    GET(UN_ARTICLE_LUXE.MARQUE);NEW_LINE;
    end GET;

  procedure PUT(UN_ARTICLE_LUXE : in ARTICLE_LUXE) is
    begin
    PUT(ARTICLE(UN_ARTICLE_LUXE));
    PUT("De marque : ");
    PUT(UN_ARTICLE_LUXE.MARQUE);NEW_LINE;
    end PUT;

  function PRIX_TTC(UN_ARTICLE_LUXE : in ARTICLE_LUXE) return PRIX is
    begin
    PUT_LINE("Application methode PRIX_TTC de CLASSE_ARTICLE_LUXE");
    return UN_ARTICLE_LUXE.PRIX_HT * 1.330;
    end PRIX_TTC;
end CLASSE_ARTICLE_LUXE;

Définition de la sous-classe VETEMENT

with CLASSE_ARTICLE; use CLASSE_ARTICLE;
package CLASSE_VETEMENT is
  type VETEMENT is new ARTICLE with null record;
end CLASSE_VETEMENT;

Définition de la sous-classe ADULTE

with CLASSE_ARTICLE;  use CLASSE_ARTICLE;
with CLASSE_VETEMENT; use CLASSE_VETEMENT;
package CLASSE_ADULTE is
  type TAILLE is range 34..56;
  type VETEMENT_ADULTE is new ARTICLE with record
       TAILLE_ADULTE : TAILLE;
       end record;

  procedure GET(UN_VETEMENT_ADULTE : out VETEMENT_ADULTE);
  procedure PUT(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE);
  function PRIX_TTC(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) return PRIX;
end CLASSE_ADULTE;

with ADA.TEXT_IO;use ADA.TEXT_IO;
package body CLASSE_ADULTE is
  package ES_ENT is new INTEGER_IO(TAILLE);use ES_ENT;
  procedure GET(UN_VETEMENT_ADULTE : out VETEMENT_ADULTE) is
    begin
    GET(ARTICLE(UN_VETEMENT_ADULTE));
    PUT("Donner la taille : ");
    GET(UN_VETEMENT_ADULTE.TAILLE_ADULTE);NEW_LINE;
    end GET;

   procedure PUT(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) is
     begin
     PUT(ARTICLE(UN_VETEMENT_ADULTE));
     PUT("De taille : ");
     PUT(UN_VETEMENT_ADULTE.TAILLE_ADULTE); NEW_LINE;
     end PUT;

   function PRIX_TTC(UN_VETEMENT_ADULTE : in VETEMENT_ADULTE) return PRIX is
     begin
     PUT_LINE("Application methode PRIX_TTC deCLASSE_ADULTE");
     return UN_VETEMENT_ADULTE.PRIX_HT * 1.206;
     end PRIX_TTC;
end CLASSE_ADULTE;

Définition de la sous-classe ENFANT

with CLASSE_ARTICLE;  use CLASSE_ARTICLE;
with CLASSE_VETEMENT; use CLASSE_VETEMENT;
package CLASSE_ENFANT is
  type AGE is range 0..16;
  type VETEMENT_ENFANT is new VETEMENT with record
       TAILLE_AGE : AGE;
       end record ;
  procedure GET(UN_VETEMENT_ENFANT : out VETEMENT_ENFANT);
  procedure PUT(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT);
  function PRIX_TTC(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) return PRIX;
end CLASSE_ENFANT;

with ADA.TEXT_IO;use ADA.TEXT_IO;
package body CLASSE_ENFANT is
  package ES_ENT is new INTEGER_IO(AGE);use ES_ENT;
  procedure GET(UN_VETEMENT_ENFANT : out VETEMENT_ENFANT) is
    begin
    GET(ARTICLE(UN_VETEMENT_ENFANT));
    PUT("Donner la taille en age : ");
    GET(UN_VETEMENT_ENFANT.TAILLE_AGE); NEW_LINE;
    end GET;

  procedure PUT(UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) is
    begin
    PUT(ARTICLE(UN_VETEMENT_ENFANT));
    PUT("De taille : ");
    PUT(UN_VETEMENT_ENFANT.TAILLE_AGE);
    end PUT;

  function PRIX_TTC (UN_VETEMENT_ENFANT : in VETEMENT_ENFANT) return PRIX is
    begin
    PUT_LINE("Application methode PRIX_TTC de CLASSE_ENFANT");
    return UN_VETEMENT_ENFANT.PRIX_HT * 1.005;
    end PRIX_TTC;
end CLASSE_ENFANT;

Programme de test

with CLASSE_ARTICLE, CLASSE_ARTICLE_LUXE, CLASSE_VETEMENT, CLASSE_ENFANT, CLASSE_ADULTE;
use CLASSE_ARTICLE, CLASSE_ARTICLE_LUXE, CLASSE_VETEMENT, CLASSE_ENFANT, CLASSE_ADULTE;
with ADA.TEXT_IO; use ADA.TEXT_IO;

procedure TEST1 is
  package ES_FIXE is new DECIMAL_IO(PRIX); use ES_FIXE;
  ARTICLE1 : ARTICLE_LUXE;
  ARTICLE2 : VETEMENT_ADULTE;
  ARTICLE3 : VETEMENT_ENFANT;
  PRIX_DE_L_ARTICLE, VALEUR_DU_STOCK : PRIX;

  begin
  PUT_LINE("Traitement d'un article de luxe : ");
  GET(ARTICLE1);
  PUT(ARTICLE1);
  PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE1);
  PUT(" Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
  PUT_LINE(" Valeur du stock TTC : ");
  VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE1);
  PUT(VALEUR_DU_STOCK); NEW_LINE(2);
  PUT_LINE("Traitement d'un vetement adulte :");
  GET(ARTICLE2);
  PUT(ARTICLE2);
  PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE2);
  PUT("Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
  PUT_LINE("Valeur du stock TTC : "); 
  VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE2);
  PUT(VALEUR_DU_STOCK); NEW_LINE(2);
  PUT_LINE("Traitement d'un vetement enfant : ");
  GET(ARTICLE3);
  PUT(ARTICLE3);
  PRIX_DE_L_ARTICLE := PRIX_TTC(ARTICLE3);
  PUT("Prix TTC : "); PUT(PRIX_DE_L_ARTICLE); NEW_LINE;
  PUT_LINE("Valeur du stock TTC : ");
  VALEUR_DU_STOCK := VALORISER_STOCK(ARTICLE3);
  PUT(VALEUR_DU_STOCK); NEW_LINE(2);
  end TEST1;
informatique, languages, drik, wiki, types, exceptions, packages, langage, incrementale, compilation, composition, concepts, separee, objet, presentation, genericite, generaux, approche, unite, sous, donnees, prives, type, access, definition, parametres, programmes, exception, controle, scalaires, simples, package, traiter, derives, exemple, enfant, lever, programmation, applications, fondamentaux, introduction, complements, utilisation, predefinies, propagation, generalise, traduction, historique, structures, visibilite, generiques, composites, surcharge, generique, orientee, declarer, entrees, sorties, notion, apercu, regles, parent, portee, forme, classe, programme, private, article, partie, implementation, specification, conditionnel, declarations, declaration, enumeratifs, generalites, abstraites, procedures, constantes, collection, groupement, fonctions, interface, variables, abstraits, articles, elements, iteratif, composes, tableaux, heritage, vetement, machines, limites, limited, passage, entiers, reliees, adulte, unites, nommee, simple, retour, acces, reels, etats, corps, appel, test, base, mode, luxe, bloc