Le langage PIR, troisième partie

Copyright © 2009 Christian Aperghis-Tramoni

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

Abordons maintenant le troisième partie de la série consacrée à PIR. Nous allons nous intéresser ici aux structures de données et à leur utilisation.

Vous pouvez télécharger l'ensemble des programmes présentés sur mon site : http://www.dil.univ-mrs.fr/~chris/Documents/progs03.pod

Introduction.

Nous avons maintenant une bonne connaissance de la syntaxe de PIR, il est temps de rentrer dans le détail des structures de données. Nous allons ainsi constater que le langage dispose de nombreuses options que nous mettrons en pratique sur plusieurs exemples.

Puis nous avancerons dans la programmation en définissant des macro instructions qui vont nous permettre d'accroître la lisibilité des programmes.

les listes statiques.

Le langage PIR dispose de deux types de listes statiques, les listes typées et les listes non typées.

La liste statique non typée.

C'est une liste standard de longueur fixe. Le type des valeurs qu'elle contient n'étant pas défini il est possible d'y stocker n'importe quel type de scalaire.

  coruscant chris$ cat liste.pir 
  .sub main
    .local pmc liste
    liste = new "Array"
    liste = 3
    liste[0] = 1
    liste[1] = "Pi"
    liste[2] = 3.14159265
    say "Edition de la liste."
    $I0 = liste[0]
    say $I0
    $S0 = liste[1]
    say $S0
    $N0 = liste[2]
    say $N0
  .end
  coruscant chris$ parrot liste.pir 
  1
  Pi
  3.14159265
  coruscant chris$

Ces quelques lignes nous montrent les principales étapes de la déclaration de la structure.

Déclaration de son nom : .local pmc liste.

Attachement à un PMC représentatif : liste = new "Array".

Définition de la longueur qui est fixe liste = 3.

Et, en fin de compte accès à la structure : liste[indice].

Si on oublie de fixer la longueur, celle ci est considérée comme étant égale à zéro et, tout accès se soldera par le message d'erreur :

  FixedArray: index out of bounds!

Noter aussi l'obligation qui est faite de passer par l'intermédiaire d'un scalaire pour afficher un élément de liste.

  $I0 = liste[0]
  say $I0

L'erreur classique qui consisterait à écrire :

  say liste[0]

Se solde par un message d'erreur qui peut surprendre :

  error:imcc:The opcode 'say' (say<0>) was not found.
  Check the type and number of the arguments
	in file 'liste.pir' line xx

Par contre, il est parfaitement autorisé de redimensionner la liste.

  coruscant chris$ cat liste.pir 
  .sub main
    .local pmc liste
    liste = new "Array"
    liste = 2
    liste[0] = 1
    liste[1] = "Pi"
    print "Liste de longueur "
    $I0 = liste
    say $I0
    $I0 = liste[0]
    say $I0
    $S0 = liste[1]
    say $S0
  # Redimensionnement de la liste.
    liste = 4
    liste[2] = 3.14159265
    liste[3] = "Fin de la liste"
    print "Liste de longueur "
    $I0 = liste
    say $I0
    $I0 = liste[0]
    say $I0
    $S0 = liste[1]
    say $S0
    $N0 = liste[2]
    say $N0
    $S0 = liste[3]
    say $S0
  .end
  coruscant chris$ parrot liste.pir 
  Liste de longueur 2
  1
  Pi
  Liste de longueur 4
  1
  Pi
  3.14159265
  Fin de la liste
  coruscant chris$

La liste initialement de longueur 2 a été redimensionnée à 4 et son contenu a été préservé.

Appliquons maintenant tout ceci sur un exemple un peu plus complet.

  coruscant chris$ cat liste.pir 
  .sub main
    .local pmc STDIN
    STDIN = getstdin
    .local pmc liste
    .local int i
    .local int j
    .local int long
    liste = new "Array"
    print "Quel est la longueur de la liste ? "
    $S0 = readline STDIN  # Lecture de la chaine de caracteres.
    chopn $S0, 1          # On retire \n.
    long = $S0            # On convertit en entier.
    liste = long          # On declare la longueur.
    i = 0
  BOUCLE:
    if i >= long goto FIN
    liste[i] = i          # Chaque element prend la
    i += 1                # valeur de l'indice correspondant.
    goto BOUCLE
  FIN:
    say "Fin d'affectation."
    say "Relecture de la liste."
    i = 0
  LECTURE:
    if i >= long goto TERMINE
    j = liste[i]           # On relit la liste et on affiche
    print j                # les valeurs.
    print "  "
    i += 1
    goto LECTURE
  TERMINE:
  say "\nFin du programme"
  .end
  coruscant chris$ parrot liste.pir
  Quel est la longueur de la liste ? 5
  Fin d'affectation.
  Relecture de la liste.
  0  1  2  3  4  
  Fin du programme
  coruscant chris$

Les listes statiques typées.

Dans le programme que nous venons de voir, la liste est de longueur fixe et non typée. C'est la déclaration liste = new "Array" qui nous a permis de la déclarer.

Il existe une possibilité de travailler sur des listes de longueur fixe typées, elles seront déclarées au moyen des PMC FixedIntegerArray, FixedFloatArray, FixedStringArray, FixedBooleanArray et FixedPMCArray.

Une fois ainsi déclarées, ces structures ne peuvent contenir qu'une valeur du type spécifié. Si ce n'est pas le cas, l'Auto Boxing entrera en action pour procéder aux conversions indispensables.

Leur utilisation sera identique à celle d'une liste non typée, création et déclaration de la longueur.

    .local pmc liste_entiers
    liste_entiers = new "FixedIntegerArray"
    .local pmc liste_chaines
    liste_chaines = new "FixedStringArray"
    .local pmc liste_flottants
    liste_flottants = new "FixedFloatArray"
    .local pmc liste_booleens
    liste_booleens = new "FixedBooleanArray"
    .local pmc liste_pmc
    liste_pmc = new "FixedPMCArray"
  # Declaration des longueurs
    liste_entiers = Lg1
    liste_chaines = Lg2
    liste_flottants = Lg3
    liste_booleens = Lg4
    liste_pmc = Lg5

L'indexation a la même forme que dans les listes non typées.

    liste_entiers[index]
    liste_chaines[index]
    liste_flottants[index]
    liste_booleens[index]
    liste_pmc[index]

Les listes dynamiques.

Ce sont des structures typées, mais leur longueur n'est pas fixée et s'adaptera au contexte.

Elles seront déclarées au moyen des PMC ResizableIntegerArray, ResizableFloatArray, ResizableStringArray, ResizableBooleanArray et ResizablePMCArray.

A noter qu'il n'existe pas dans la définition des PMC une structure permettant de représenter une liste dynamique non typée. Toutefois, la longueur étant modifiable sans perte d'information, il est parfaitement possible d'en émuler une.

  coruscant chris$ cat liste.pir 
    .sub main
    .local pmc liste
    .local int i, taille
    liste = new "Array"
    i = 0
  CREATION:
    if i >= 5 goto IMPRIME
    taille = i + 1
  # Redimensionnement de la liste en fonction de l'indice.
    liste = taille
    liste[i] = i
    i += 1
    goto CREATION
    i = liste
  IMPRIME:
    unless i goto FINI
    i -= 1
    $I0 = liste[i]
    print $I0
    print "  "
    goto IMPRIME
  FINI:
    say "\nFin du programme."
  .end
  coruscant chris$ parrot liste.pir
  4  3  2  1  0
  Fin du programme.
  coruscant chris$

Pour illustrer le comportement d'une liste dynamique, je reprend le programme classique qui calcule les lignes successives du triangle de Pascal [Pascal]. On peut parfaitement adapter cet algorithme à une structure de ce type. La méthode de calcul pour n lignes ne pose que peu de problèmes. Toutefois, la programmation classique nous oblige à réserver une matrice n*n qui ne sera occupée que à un peu plus de 50 % de sa capacité, seuls (n+1)/2n éléments sur les n*n sont utilisés.

L'algorithme indique que l'on calcule un élément p[i,j] par rapport aux deux éléments de la ligne précédente p[i-1,j] + p[i-1,j-1]. Ecrit d'une manière élégante en Perl, cet algorithme utilise une unique liste (@pascal) qui va contenir les valeurs générées dont la longueur augmentera de 1 à chaque itération, et un doublet (@doublet) dans lequel seront mémorisées les deux valeurs servant au calcul.

Cet algorithme se présente comme suit :

    # La ligne courante est dans @pascal
    $l = @pascal;
    for ($j=1; $j<=$l; $j++) {
        @doublet = ($doublet[1], $pascal[$j]);
        $pascal[$j] = $doublet[0] + $doublet[1];
    }

Nous allons réécrire cet algorithme en PIR. Pour ce faire, nous utilisons deux types de structures, une liste statique FixedIntegerArray pour mémoriser le doublet de valeurs et une liste dynamique ResizableIntegerArray pour générer les itérations successives.

  coruscant chris$ cat pascal.pir
  .sub "Le triangle de Pascal" :main
    .local pmc STDIN
    STDIN = getstdin
    .local int lignes
    print "Combien de lignes doit-on generer ? "
    $S0 = readline STDIN
    chopn $S0, 1
    
    # Nombre de lignes a generer.
    lignes = $S0
    
    # Vecteur a deux elements.
    .local pmc doublet
    doublet = new 'FixedIntegerArray'
    doublet = 2
    
    # Ligne courante du triangle.
    .local pmc pascal
    pascal = new 'ResizableIntegerArray'
    pascal[0] = 0
    pascal[1] = 1
    
    .local int longueur
    .local int index
    .local int k1
    .local int k2
  ITERATION:
    # Recuperation de la longueur du vecteur.
    longueur = pascal
    lignes -= 1
    if lignes < 0 goto FIN
    
    # Impression du vecteur.
    index = 1
  CONTINUE:
    $S0 = pascal[index]
    # Cadrage du nombre.
    $S0 = concat "    ", $S0
    substr $S1,$S0,-5
    print $S1
    index += 1
    if index < longueur goto CONTINUE
    print "\n"
    
    # Calcul de la ligne suivante à partir de la ligne courante.
    # Indice d'exploration de vecteur.
    index = 1
  CALCUL:
    k1 = doublet[1]
    doublet[0] = k1
    k2 = pascal[index]
    doublet[1] = k2
    k2 = doublet[1]
    k1 += k2
    pascal[index] =  k1
    
    # Progression dans le vecteur.
    index += 1
    if index <= longueur goto CALCUL
    goto ITERATION
  FIN:
    end
  .end
  coruscant chris$ parrot pascal.pir
  Combien de lignes doit-on generer ? 10
  1
  1    1
  1    2    1
  1    3    3    1
  1    4    6    4    1
  1    5   10   10    5    1
  1    6   15   20   15    6    1
  1    7   21   35   35   21    7    1
  1    8   28   56   70   56   28    8    1
  1    9   36   84  126  126   84   36    9    1
  coruscant chris$

Remarquer les lignes

    $S0 = concat "    ", $S0
    substr $S1,$S0,-5
    print $S1

Qui nous permettent de représenter la valeur cadrée à droite sur cinq caractères afin de faire coïncider les puissances équivalentes.

Les instructions split et join.

Ce sont les deux fonctions standards qui permettent de créer une liste à partir d'une chaîne (split) ou inversement de construire une chaîne réunissant les éléments d'une liste.

  coruscant chris$ cat chaine.pir
  .sub main :main
   .local pmc liste
   .local string chaine
   .local int long, i
    liste = new 'ResizableStringArray'
    say "Creation de la liste."
    liste = split ",", "Un,Deux,Trois,Quatre,Cinq"
    long = liste
    print "Longueur de la liste : "
    say long
    say "Visualisation de la liste."
    i = 0
  SUITE:
    if i >= long goto FIN
    $S0 = liste[i]
    print $S0
    print "  "
    i += 1
    goto SUITE
  FIN:
    say "\nCreation de la chaine."
    chaine = join " - ", liste
    say "Visualisation de la chaine."
    say chaine
    say "Fin du programme."
    .end
  coruscant chris$ parrot chaine.pir 
  Creation de la liste.
  Longueur de la liste : 5
  Visualisation de la liste.
  Un  Deux  Trois  Quatre  Cinq  
  Creation de la chaine.
  Visualisation de la chaine.
  Un - Deux - Trois - Quatre - Cinq
  Fin du programme.
  coruscant chris$

Les fonctions push et pop.

C'est au moyen de ces deux fonctions que nous allons pouvoir utiliser une liste telle que nous venons de la définir pour simuler une pile.

  coruscant chris$ cat pile.pir
  .sub test :main
    .local pmc pile
    say "  Empilage des valeurs."
    pile = new "ResizablePMCArray"
    $S0 = "Chaine."
    push pile, $S0
    say $S0
    $I0 = 6174
    push pile, $I0
    say $I0
    $N0 = 2.71828183
    push pile, $N0
    say $N0
    $I0 = pile
    $I1 = 0
    say "  Edition de le liste."
  BOUCLE:
    $S0 = pile[$I1]
    say $S0
    $I1 += 1
    if $I1 < $I0 goto BOUCLE
    say "  Depilage des valeurs."
  POP:
    unless pile goto FIN
    $S0 = pop pile
    say $S0
    goto POP
  FIN:  
  .end
  coruscant chris$ parrot pile.pir
    Empilage des valeurs.
  Chaine de caracteres.
  6174
  2.71828183
    Edition de le liste.
  Chaine de caracteres.
  6174
  2.71828183
    Depilage des valeurs.
  2.71828183
  6174
  Chaine de caracteres.
  coruscant chris$

Les deux instructions push et pop agissent sur la fin de la liste. On dispose des deux instructions correspondantes unshift et shift pour effectuer des opérations identiques en début de liste.

Pour les tester, il suffit de reprendre le programme précédent en remplaçant toute occurrence de push par unshift et toute occurrence de pop par shift.

Nous allons d'ailleurs vite utiliser ces instructions pour explorer un itérateur.

Les hash.

PIR dispose de tous les outils nécessaires pour gérer des hash. Il existe deux manières de déclarer un hash, c'est dans la récupération des clés que la différence se fera sentir.

Si le hash est construit au moyen du PMC "Hash", la récupération des clés se fera dans un ordre qui dépend de la fonction de hachage

Par contre, si le PMC utilisé est "OrderedHash", les valeurs seront récupérées dans l'ordre de création. En fait, le système ajoute une valeur devant la clé pour respecter cette contrainte, les clés ("xxx", "yyy" ...) se présenteront en fait sous la forme ("1xxx", "2yyy" ...), ce qui nuit quelque peu à la transparence

Comme c'était le cas dans le cas des listes, on commence par déclarer le nom de la structure :

    .local pmc hash

Puis on la rattache au PMC adapté :

    hash = new ["Hash"]
    hash = new ["OrderedHash"]

Voyons tout ceci sur un exemple complet.

  coruscant chris$ cat hash.pir
  .sub _main
    .local pmc hash
    hash = new ["Hash"]

    .local int taille
    taille = hash
    print "Taille avant affectations : "
    say taille

    hash['Un'] = 1
    hash['Deux'] = 2
    hash['Trois'] = 3
    hash['Quatre'] = 4
    hash['Cinq'] = 5
    
    taille = hash
    print "Taille apres affectations : "
    say taille
    say "Relecture des informations."
    .local pmc cles
    cles = new 'Iterator', hash
    $S0 = typeof cles
    print "La variable 'cles' est de type : "
    say $S0
  EXPLORE:
    unless cles goto FIN
    $S0   = shift cles
    print "hash{"
    print $S0
    print "} = "
    $I0 = hash[$S0]
    say $I0
    goto EXPLORE
  FIN:
    print "Fin du programme.\n"
    end
  .end
  coruscant chris$ parrot hash.pir
  Taille avant affectations : 0
  Taille apres affectations : 5
  Relecture des informations.
  La variable 'cles' est de type : Iterator
  hash{Un} = 1
  hash{Deux} = 2
  hash{Trois} = 3
  hash{Quatre} = 4
  hash{Cinq} = 5
  Fin du programme.
  coruscant chris$

La relecture des informations du hash se fait par l'intermédiaire d'un itérateur spécifique qui nous permet de récupérer la liste des clés d'accès.

Les clés sont récupérées dans une structure particulière qui est de type Iterator et que l'on déclare comme telle cles = new 'Iterator', hash.

Dans le cas que nous avons traité, l'itérateur est exploré en extrayant ses éléments au moyen de l'instruction $S0 = shift cles.

  coruscant chris$ cat iterateur.pir
  .sub _main
    .local pmc hash_d, hash_o
    hash_d = new ["Hash"]
    hash_o = new ["OrderedHash"]

    hash_d['Un'] = 1
    hash_d['Deux'] = 2
    hash_d['Trois'] = 3
    hash_d['Quatre'] = 4
    hash_d['Cinq'] = 5
    
    hash_o['Un'] = 1
    hash_o['Deux'] = 2
    hash_o['Trois'] = 3
    hash_o['Quatre'] = 4
    hash_o['Cinq'] = 5
    
    .local pmc cles
    cles = new 'Iterator', hash_d
    say "Hash standard."
  EXPL1:
    unless cles goto FIN1
    $S0   = shift cles
    print " "
    print $S0
    goto EXPL1
  FIN1:
    say "\nHash ordonne"
    cles = new 'Iterator', hash_o
  EXPL2:
    unless cles goto FIN2
    $S0   = shift cles
    print " "
    print $S0
    goto EXPL2
  FIN2:
    say "\nFin du programme."
  .end
  coruscant chris$ parrot iterateur.pir
  Hash standard.
   Deux Trois Quatre Cinq Un
  Hash ordonne
   Un Deux Trois Quatre Cinq
  Fin du programme.
  coruscant chris$

La différence entre les deux structures apparaît clairement lors de l'exploration de l'itérateur.

les structures composées.

Il est parfaitement possible de combiner entre elles les structures de hash et de liste, le but étant de construire des structures plus complexes.

C'est ainsi qu'un élément de hash peut contenir un PMC définissant une liste qui à son tour peut stocker des PMC représentatifs d'autres structures.

Dans de telles constructions, les clés permettant de définir chacun des niveaux d'accès peuvent être combinées ensemble et ne former ainsi qu'une unique clé d'accès pour la structure.

Cette technique fonctionne aussi pour construire un hash de hash, une liste de liste ou toute combinaison de ces diverses structures.

Dans l'exemple qui suit, nous allons définir un hash à deux entrées caracteres et entiers. La première clé donnera accès à une liste de chaînes de caractères ResizableStringArray alors que la seconde référencera une liste d'entiers ResizableIntegerArray.

  coruscant chris$ cat structure.pir
  .sub main :main
    .local pmc MonHash, ListeCar, ListeEnt
    MonHash  = new "Hash"
  # Definition et remplissage de la liste de chaines.
    ListeCar = new 'ResizableStringArray'
    ListeCar[0] = "Zero"
    ListeCar[1] = "Un"
    ListeCar[2] = "Deux"
  # Definition de la lste d'entiers.
    ListeEnt = new 'ResizableIntegerArray'
  # Mise en place des structures dans le hash.
    MonHash["caracteres"] = ListeCar
    MonHash["entiers"] = ListeEnt
    $I0 = 0
  # Remplissage de la liste d'entiers.
  REMPLISSAGE:
    if $I0 > 5 goto SUITE
    MonHash["entiers";$I0] = $I0
    $I0 += 1
    goto REMPLISSAGE
  SUITE:
  # Relecture des elements du hash.
    say "Lecture de la liste contenue dans MonHash[caracteres]."
    $I0 = 0
  # Recuperation de la longueur de la liste de chaines.
    $I1 = MonHash["caracteres"]
  LECTH:
  # Edition.
    if $I0 == $I1 goto FIN1
    $S0 = MonHash["caracteres";$I0]
    print $S0
    print "  "
    $I0 += 1
    goto LECTH
  FIN1:
    say "\nLecture de la liste contenue dans MonHash[entiers]."
    $I0 = 0
  # Recuperation de la longueur de la liste des entiers.
    $I1 = MonHash["entiers"]
  LECTC:
  # Edition.
    if $I0 == $I1 goto FIN2
    $I10 = MonHash["entiers";$I0]
    print $I10
    print "  "
    $I0 += 1
    goto LECTC
  FIN2:
    say "\nFin des acces."
  .end
  coruscant chris$ parrot structure.pir
  Lecture de la liste contenue dans MonHash[caracteres].
  Zero  Un  Deux  
  Lecture de la liste contenue dans MonHash[entiers].
  0  1  2  3  4  5  
  Fin des acces.
  coruscant chris$

Noter l'unification de la clé du hash sous la forme :

  MonHash["Cle_du_hash";Indice_de_la_liste]

C'est suivant la même méthode qu'il sera possible de construire des matrices et de les adresser de manière traditionnelle :

  MaMatrice[Indice_ligne;Indice_colonne]

Histoire de compliquer le problème, au lieu de construire une matrice rectangulaire, nous allons appliquer ceci à la construction et au remplissage d'une matrice triangulaire.

  coruscant chris$ cat triangle.pir
  .sub main :main
    .local pmc Triangle
  # Declaration de la struture, une liste de PMC.
    Triangle  = new "ResizablePMCArray"
    $I0 = 0
  LIGNE:
    if $I0 > 5 goto TERMINE
  # Declaration des lignes.
  # Chaque ligne aura une longueur differente.
    $P0 = new "ResizableStringArray"
    $I1 = 0
  COLONNE:
  # Remplissage de la ligne.
    if $I1 > $I0 goto LIGNE_SUIVANTE
    $S0 = $I0
    $S0 .= "-"
    $S1 = $I1
    $S0 .= $S1
    $P0[$I1] = $S0
    $I1 += 1
    goto COLONNE
  LIGNE_SUIVANTE:
  # Affectation de la ligne a la structure.
    Triangle[$I0] = $P0
    $I0 += 1
    goto LIGNE
  TERMINE:
    $I0 = 0
  # Relecture de la structure.
  LEC_LIGNE:
    if $I0 > 5 goto FIN
    $I1 = 0
  LEC_COLONNE:
    if $I1 > $I0 goto LEC_SUIVANTE
    $S0 = Triangle[$I0;$I1]
    print $S0
    print "  "
    $I1 += 1
    goto LEC_COLONNE
  LEC_SUIVANTE:
  print "\n"
    $I0 += 1
    goto LEC_LIGNE
  FIN:
  .end
  coruscant chris$ parrot triangle.pir
  0-0  
  1-0  1-1  
  2-0  2-1  2-2  
  3-0  3-1  3-2  3-3  
  4-0  4-1  4-2  4-3  4-4  
  5-0  5-1  5-2  5-3  5-4  5-5  
  coruscant chris$

Chaque ligne ayant une longueur spécifique, on se sert d'une liste redimensionnable pour la générer $P0 = new "ResizableStringArray". Chaque élément de la structure contient un doublet représentatif de son numéro de ligne et de colonne i-j.

Une fois la liste représentative de la ligne n créée, on l'affecte à l'élément n de la structure triangulaire Triangle[$I0] = $P0.

Pour la relecture, il n'y a que peu de problèmes, chaque ligne d'indice i permet d'accéder à une liste qui comporte i éléments. L'adressage se fait donc sous la forme Triangle[$I0;$I1].

Les fonctions lexicales.

Parrot supporte les fonctions lexicales. Cela signifie qu'il est possible d'en définir une par son nom dans le corps d'une autre fonction en faisant en sorte que notre fonction interne ne soit visible et référençable qu'à partir de l'intérieur et accessible de l'extérieur par l'intermédiaire d'une référence. De plus, la fonction interne va hériter de toutes les variables lexicales de la fonction externe tout en ayant la possibilité de définir et d'accéder à ses propres variables qui ne seront ni vues ni modifiables par le monde extérieur à son espace de définition [Fermeture].

Cette notion est d'autant plus importante que PIR ne dispose pas d'un mécanisme qui pourrait s'apparenter à la structure de blocs comme par exemple Perl dans lequel les fonctions peuvent être imbriquées lorsque ceci est nécessaire.

Portée des variables.

La plupart des langages évolués tels Perl, Python ou PHP autorisent l'imbrication des blocs, c'est à dire la présence de blocs internes à d'autres blocs. Les variables peuvent être déclarées privées à un bloc donné. Ainsi chaque bloc peut avoir accès à son jeu de variables lexicales en plus des variables globales déclarées dans un bloc de niveau plus élevé.

Voici un exemple de programme Perl de construction assez classique.

  {
    my $x = 10;
    my $y = "Bonjour";
    my $z = 2.71828183
  {
    my $z = 3.14159265;
    print "$x $y $z\n";
  }
    print "$x $y $z\n";
  }

Dans le bloc interne, les variables $x et $y sont connues et possèdent une valeur ($x = 0 et $y = "Bonjour").

La variable $z lexicalement connue dans le bloc en question est différente de celle déclarée dans le bloc le plus externe.

Le premier print va donc afficher :

  10 Bonjour 3.14159265

Alors que le second va afficher

  10 Bonjour 2.71828183

PIR n'offre pas vraiment de possibilité pour gérer ce type de comportement.

Le code que nous avons défini ci-dessus ne peut avoir, en PIR, qu'une seule transcription :

  .sub "Main" :main
    .param int x
    .param string y
    .param int z
    x = 10
    y = "Bonjour"
    z = 200
  .end

Mais, si le code peut sembler similaire, la portée des variables est totalement différente car elles sont toutes les trois visibles dans la totalité de l'espace de compilation.

C'est pour remédier à ce problème que PIR propose des fonctions lexicales dont le but est de construire du code apte à supporter les imbrications et les variables qui y sont associées.

Comme on ne dispose pas dans PIR de structure de blocs, il n'est pas possible de déclarer comme dans un langage de haut niveau habituel une hiérarchie de variables.

Le seul mécanisme qui va nous permettre de remédier à ce manque, sera la déclaration lexicale.

Les variables lexicales ne seront accessibles que par l'intermédiaire des deux instructions get_lex et set_lex.

Ces deux opérations ne donnent pas seulement accès au registre dans lequel est stockée la valeur, ils permettent aussi d'interagir avec le PMC LexPad qui permet la conservation de la donnée.

Si la valeur n'est pas correctement mémorisée dans le LexPad alors les variables ne seront pas disponibles dans les sous-programmes imbriqués.

Transcrivons tout ceci dans un programme.

  coruscant chris$ cat lexical.pir
  .sub 'externe' :subid('externe')
    .local pmc chaine
    chaine = new 'String'
    chaine = "Bonjour "
    .lex '$chaine', chaine
    print chaine
    say "de la fonction externe !"
    'interne'()
    print chaine
    say "de la fonction externe !"
  .end

  .sub 'interne' :outer('externe')
    .local pmc chaine
    chaine = find_lex '$chaine'
    print chaine
    say "de la fonction interne !"
    .local pmc retour
    retour = new 'String'
    retour = "Au revoir "
    store_lex '$chaine', retour
  .end
  coruscant chris$ parrot lexical.pir
  Bonjour de la fonction externe !
  Bonjour de la fonction interne !
  Au revoir de la fonction externe !
  coruscant chris$

Dans le programme que nous venons d'écrire, la variable chaine déclarée dans externe est passée au sous-programme interne qui l'imprime et en change la valeur avant de rendre la main à la fonction externe.

Nous mettons donc en évidence le fait qu'il est possible de déclarer une sous-routine imbriquée dans l'espace d'une autre routine en utilisant la directive :outer. Cette directive va servir à spécifier quel est le nom de l'unité de compilation qui contient la sous-routine courante.

Lorsqu'il peut y avoir plusieurs sous-routines portant le même nom, la directive :subid sera utilisée dans le sous-programme extérieur. Cette directive va permettre de donner un nom différent et unique auquel les sous-programmes lexicaux pourront faire référence dans leur directive :outer.

A l'intérieur des sous-programmes lexicaux, la directive :lex permet de définir une variable locale en accord avec la portée des règles ainsi définies.

Réalisation d'une fermeture.

Voyons maintenant comment nous allons utiliser ces propriétés pour construire une fermeture. Pour y arriver, nous procédons à une double imbrication d'unités de compilation.

  coruscant chris$ cat fermeture.pir
  .sub Main :main
    $P0 = Constructeur()
    Fermeture ()
    $P0 = new "Integer"
    print "Modification de la valeur lexicale a : "
    $P0 = 10
    say $P0
    .lex "val", $P0
    Fermeture ()
    $P1 = find_lex "val"
    print "Dans Main. Valeur de la variable : "
    say $P1
    Fermeture ()
  .end

  .sub Constructeur :outer(Main)
    # Creation du contexte de le fermeture.
    $P0 = new "Integer"
    $P0 = 3
    .lex "val", $P0
  .end

  .sub Fermeture :outer(Constructeur)
    $P1 = find_lex "val"
    print "Dans la fermeture. Valeur lexicale : "
    say $P1
    $P1+= 1
  .end
  coruscant chris$ parrot fermeture.pir
  Dans la fermeture. Valeur lexicale : 3
  Modification de la valeur lexicale a : 10
  Dans la fermeture. Valeur lexicale : 4
  Dans Main. Valeur de la variable : 10
  Dans la fermeture. Valeur lexicale : 5
  coruscant chris$

La valeur val est la valeur lexicale de la fermeture. Sa modification dans l'unité de compilation main n'affecte en rien la valeur qui est la sienne à l'intérieur de la fermeture.

Les PMC LexPad et LexInfo.

Nous venons de mettre en évidence le fait que les informations sur les variables lexicales d'un sous-programme sont stockées dans deux types de PMC distincts, le PMC LexPad et le PMC LexInfo.

Ces PMC ne sont ni l'un ni l'autre véritablement utilisables par le code PIR, ils sont destinés à la machine virtuelle qui va s'en servir pour mémoriser les informations indispensables sur les variables lexicales.

C'est au moment de la compilation que seront stockées les données sur les variables lexicales en question. Cette information n'est accessible qu'en lecture read only et elle est générée par le compilateur pour représenter ce qui est connu sur les variables lexicales.

Le PMC LexInfo n'est pas automatiquement associé à tous les sous-programmes. Ce n'est que si il est clairement indiqué que l'on désire la création d'un PMC LexInfo que celui ci sera généré. L'un des moyens est l'utilisation de la directive .lex que nous venons d'étudier.

Bien évidemment, cette directive ne peut fonctionner que dans le cas des langages pour lesquels le nom de toutes les variables lexicales est connu au moment de la compilation. Dans le cas des langages pour lesquels cette information n'est pas connue, le sous-programme peut être marqué au moyen de l'indicateur :lex.

Les PMC LexPad sont utilisés pour stocker l'information d'exécution concernant les variables lexicales. Cette information inclut le type de l'information qui leur est associée ainsi que leur valeur.

Le PMC LexPad est créé au moment de l'exécution pour les sous-programmes qui disposent déjà d'un PMC LexInfo.

A chaque appel du sous-programme un nouveau PMC est instancié, ce qui permet de procéder sans difficulté à des appels récursifs.

Notons qu'il n'y a pas de moyen simple pour obtenir une référence aux PMC LexPad et LexInfo d'un sous-programme. Ceci n'est pas important car, de toute façon, ils ne sont pas utiles par eux-mêmes.

Il faut se souvenir que les sous-programmes eux-mêmes peuvent être lexicaux et que, par là même, l'environnement lexical d'une variable donnée peut s'étendre à de multiples sous-programmes et donc à de multiples LeXPad.

Les codes opération find_lex et store_lex cherchent automatiquement et récursivement à travers les LexPad empilés celui qui contient la bonne information sur l'environnement des variables.

Application.

Nous pouvons appliquer tout ce qui vient d'être dit sur un programme classique qui nécessite la gestion d'une pile.

Il s'agit du programme donnant la solution des tours de Hanoï.

Ce programme qui a fait l'objet d'une perle [Hanoï] s'écrit comme suit :

    #!/usr/bin/perl
    # Les tours de Hanoi.
    print "Combien de disques ?\n";
    $x = <STDIN>;
    hanoi($x, "a", "b", "c");

    sub hanoi {
        my ($n, $x, $y, $z) = @_;
        return if $n == 0;
        hanoi($n-1, $x, $z, $y);
        print "De $x vers $z\n";
        hanoi($n-1, $y, $x, $z);
    }

La déclaration my ($n, $x, $y, $z) = @_; spécifie que les variables concernées sont privées. Elles seront, de ce fait, créées à chaque accès au sous-programme, c'est à dire pour chacune des récursions.

Ces variables sont donc lexicalement attachées à une récursion spécifique.

Il nous faut donc simuler cette fonction au moyen d'une pile qui sera une variable lexicale du programme principal (Principal) mais accessible à partir du sous-programme récursif (Hanoi)

  coruscant chris$ cat hanoi.pir
  .sub 'externe' :subid('Principal')
  # Declaration d'une pile commune.
    .local pmc pile
    pile = new 'ResizablePMCArray'
  # Empilage dans le LexPad  
    .lex '$pile', pile
    .local int nombre
    .local pmc STDIN
    STDIN = getstdin
    print "Combien de disques ? "
    $S0 = readline STDIN
    chopn $S0, 1
    nombre = $S0
    .local string tour1, tour2, tour3
    tour1 = "1"
    tour2 = "2"
    tour3 = "3"
    'Hanoi' (nombre,tour1,tour2,tour3)    
  .end

  .sub 'Hanoi' :outer('Principal')
    .param int nbdisques
    .param string tour1
    .param string tour2
    .param string tour3
    .local pmc pile
  # Recuperation de la pile.
    pile = find_lex '$pile'
  # Sauvegarde des variables de l'iteration courante.
    push pile, nbdisques
    push pile, tour1
    push pile, tour2
    push pile, tour3
    if nbdisques == 0 goto FIN
    nbdisques -= 1
    Hanoi (nbdisques, tour1, tour3, tour2)
    print     "De la tour "
   print     tour1
   print     " vers la tour "
   print     tour3
   print     ".\n"
   Hanoi (nbdisques, tour2, tour1, tour3)
  FIN:
  # Recuperation des variables de l'iteration precedente.
    tour3 = pop pile
    tour2 = pop pile
    tour1 = pop pile
    nbdisques = pop pile
    .return ()
  .end
  coruscant chris$ parrot hanoi.pir
  Combien de disques ? 3
  De la tour 1 vers la tour 3.
  De la tour 1 vers la tour 2.
  De la tour 3 vers la tour 2.
  De la tour 1 vers la tour 3.
  De la tour 2 vers la tour 1.
  De la tour 2 vers la tour 3.
  De la tour 1 vers la tour 3.
  coruscant chris$

Nous déclarons donc dans le programme principal (.sub 'externe' :subid('Principal')) la liste pile en tant que variable lexicale (.lex '$pile', pile). Ce qui nous permet dans le sous-programme récursif Hanoi de préciser le programme externe (.sub 'Hanoi' :outer('Principal')) et donc d'accéder à la pile (pile = find_lex '$pile') afin de sauvegarder et de récupérer les valeurs correspondant à la récursion courante.

Les coroutines

Une coroutine est semblable à un sous-programme, exception faite que l'état interne au moment du retour est mémorisé afin que l'appel suivant reprenne l'exécution à l'endroit ou elle s'était préalablement arrêtée.

Lorsqu'elles réalisent un retour normal à la routine appelante au moyen d'une instruction .return une fonction supprime la totalité de son environnement lexical.

Si, au lieu d'utiliser l'instruction .return le retour au programme principal s'effectue par l'intermédiaire de l'instruction spécifique .yield, cette destruction n'aura pas lieu et l'environnement sera conservé.

Cette instruction permet elle aussi de rendre le contrôle au flux du programme appelant tout en conservant la totalité du contexte lexicographique au moment du retour. Ainsi, lors d'un nouvel appel de la coroutine, le contrôle sera donné à l'instruction qui suit immédiatement le dernier .yield exécuté et non pas, comme c'est généralement la règle, au début du sous-programme.

L'ensemble de l'environnement lexical étant resté identique à ce qu'il était au moment ou l'instruction .yield a été exécutée, tout se passe comme si le sous-programme continuait à s'exécuter séquentiellement d'appel en appel.

Par contre, il est important de noter que les valeurs des paramètres demeurent inchangées, même si la coroutine est appelée ultérieurement avec des paramètres différents.

Définition de coroutines

La définition d'une coroutine est identique à celle d'un sous-programme ordinaire. Elle ne nécessite aucune indication particulière et la syntaxe est la copie conforme de celle d'une unité de compilation classique.

La seule chose qui va permettre de distinguer les deux représentations est l'utilisation de l'instruction .yield en lieu et place de l'instruction .return d'un sous-programme classique.

Cette instruction .yield a plusieurs rôles.

Elle permet d'identifier le code comme étant celui d'une coroutine.

Ainsi, lorsque le machine virtuelle voit apparaître une instruction .yield elle reconnaît une coroutine et crée un PMC spécifique coroutine en lieu et place d'un PMC sous-programme.

Elle provoque la génération d'une continuation.

Comme cela a été dit, la continuation permet de poursuivre ultérieurement l'exécution au point spécifié tout en conservant l'environnement lexical. C'est comme prendre une photo instantanée de l'environnement d'exécution courant.

L'instruction .yield valide le séquencement dans la coroutine et mémorise l'environnement de continuation dans l'objet coroutine pour pouvoir, ultérieurement, reprendre l'exécution à l'adresse du .yield.

Eventuellement, elle retourne une valeur.

L'instruction .yield peut ne rien renvoyer, elle peut aussi renvoyer une valeur unique ou une série de données au programme appelant.

C'est de ce point de vue, qu'elle est exactement semblable à l'instruction .return.

  coruscant chris$ cat coroutine.pir
  .sub japh
    .param string chaine
    .local int long
    .local string car
    long = length chaine
  BOUCLE:
    substr car,chaine,long,1
    print car
    long -= 1
    if long < 0 goto FIN
      .yield (long)
      branch BOUCLE
  FIN:
  .end

  .sub "main" :main
  .local string chaine
  .local int retour
  chaine = ".rekcaH lreP rehtonA tsuJ"
  ENCORE:
    retour = japh(chaine)
    unless retour < 0  goto ENCORE
    print "\n"
  .end
  coruscant chris$ parrot coroutine.pir
  Just Another Perl Hacker.
  coruscant chris$

Dans le programme ci dessus, bien que tous les appels se fassent en utilisant la chaîne de caractères de base (retour = japh(chaine)), l'environnement lexical de la coroutine est conservé d'un appel sur l'autre.

Un des premiers exemple qui vient à l'esprit lorsqu'on évoque les coroutines est le génération d'une série de valeurs pseudo aléatoires [Aléat]. L'algorithme standard démarre avec une valeur initiale, la racine, qui sert à produire la première valeur. Cette dernière sera alors utilisée pour en générer une nouvelle, et ainsi de suite. Il est donc nécessaire, lors des appels successifs, de conserver l'environnement lexical de l'appel précédent.

Un algorithme classique de génération de suite pseudo-aléatoire écrite en Perl, se présente comme suit.

  sub rand {
    $x = int(($x * 1103515245 + 12345) / 65536) % 32768;
    return $x;
  }

Qui peut être réalisé en PIR au moyen d'une coroutine :

  coruscant chris$ cat aleatoire.pir
  .sub rand
    .local num racine
    racine = 1
  GENERE:
    racine *= 1103515245
    racine += 12345
    racine /= 65536
    racine %= 32768
  # La valeur de la variable lexicale
  # racine se conserve d'un appel sur l'autre.
    .yield (racine)
  # Lors de l'appel suivant, on poursuit la boucle.
    goto GENERE
  .end
  
  .sub "Nombres Pseudo Aleatoires" :main
    .local num nb
    .local int index, aleat
    index = 10
  SUITE:
    nb = rand ()
    aleat = nb
    print aleat
    print " "
    index -= 1
    if index goto SUITE
    print "\n"
  .end
  coruscant chris$ cat aleatoire.pir
  16838 22996 7307 3007 20531 15468 29688 23554 1052 20045 
  coruscant chris$

Les définitions multiples.

On dit qu'il y a définition multiple multiple dispatch lorsqu'on est en présence de plusieurs sous-programmes portant le même nom dans une seule unité de compilation.

Ces fonctions doivent toutefois différer par leur liste de paramètres ou par leur signature.

Toutes les fonctions identifiées par un même nom sont repérées par un seul PMC appelé MultiSub. Ce PMC MultiSub n'est en définitive qu'une liste de sous-programmes. Lorsqu'il est fait référence au MultiSub, l'objet PMC parcours la liste des sous-programmes et cherche celui dont la signature est la plus voisine de celui qui est invoqué.

Définition de MultiSubs

Les PMC MultiSubs sont des sous-programmes dans lesquels on utilise l'indicateur :multi dans la déclaration. Les sous-programmes que l'on appelle généralement Multis doivent impérativement différer les uns des autres par le nombre et/ou le type des arguments qui leurs sont transmis.

On définit les MultiSubs comme suit:

  .sub 'MonMulti' :multi
    # Corps du Multi
  .end

Si deux d'entre eux ont la même signature, il en résultera soit une erreur d'analyse, soit un remplacement de la première fonction déclarée par la nouvelle.

  coruscant chris$ cat multisub.pir
  .sub prog :multi(_, Integer)
    .param pmc premier
    .param pmc second
    print "On est dans le multi Integer.  "
    print premier
    print ', '
    say second
  .end
  
  .sub prog :multi(_, Float)
    .param pmc premier
    .param pmc second
    print "On est dans le multi Float.    "
    print premier
    print ', '
    say second
  .end

  .sub prog :multi(_, String)
    .param pmc premier
    .param pmc second
    print "On est dans le multi String.   "
    print premier
    print ', '
    say second
  .end
  
  .sub main :main
    $P0 = new ['Float']
    $P0 = 2.71828183
    prog (1, $P0)
    $P1 = new ['Integer']
    $P1 = 2009
    prog ('Deux', $P1)
    $P2 = new ['String']
    $P2 = "Bonjour."
    prog (3, $P2)
  .end
  coruscant chris$ parrot multisub.pir
  On est dans le multi Float.    1, 2.71828183
  On est dans le multi Integer.  Deux, 2009
  On est dans le multi String.   3, Bonjour.
  coruscant chris$

Dans ce programme, la signature est donnée par le type du second argument. Le premier argument qui peut être n'importe quoi n'intervient pas dans la détermination. Il représente simplement une donnée supplémentaire qui est transmise.

Les Multis appartiennent à un espace de nom spécifique, car les fonctions de même nom dans deux espaces de noms différents n'entrent pas en conflit les uns avec les autres.

C'est la raison d'être des MultiSubs, seules les fonctions ayant un même nom dans un même espace de nom doivent être déclarées comme telles.

les macro instructions.

Définition.

Une macro-instruction [Macro] est un moyen de définir une opération inexistante dans le langage de base de la machine à partir des des instructions disponible.

Elle peut être considérée comme la création par le programmeur d'un nouveau code opération qui, lors de son exécution entraînera celle de plusieurs commandes de base.

Ce comportement est fondamentalement différent de celui d'un sous-programme car une macro-instruction doit être considérée comme l'association d'un texte de substitution au code qui l'identifie chaque fois que cette substitution s'avère nécessaire.

Définition de constantes.

Dans un premier temps, une macro instruction peut servir à définir des constantes qui seront accessibles dans toutes les unités de compilation du programme.

  coruscant chris$ cat constantes.pir
  .macro_const Gauss 0.834626842
  .macro_const Langage "Parrot Intermediate Representation."
  .macro_const kaprekar 6174

  .sub uc1
    print "Gauss : "
    say .Gauss
    uc2()
  .end
  .sub uc2
    print "Langage : "
    say .Langage
    uc3()
  .end
  .sub uc3
    print "Kaprekar : "
    say .kaprekar
  .end
  coruscant chris$ parrot constantes.pir
  Gauss : 0.834626842
  Langage : Parrot Intermediate Representation.
  Kaprekar : 6174
  coruscant chris$

Dans l'exemple ci dessus, l'ensemble des constantes est défini dans la macro-instruction, et elles sont rappelées dans trois unités de compilation distinctes.

Suite d'instructions.

La définition d'une macro-instruction commence avec le mot clé .macro suivi du nom qui servira de référence au moment de l'appel, puis, éventuellement, d'une liste de valeurs, elle pourra ainsi s'adapter à son environnement grâce aux paramètres syntaxiques qui lui seront transmit.

En définitive, une macro-instruction est l'association d'un texte de substitution et d'un identificateur, ce dernier étant remplacé par le texte spécifié à chacune de ses occurrences.

Une macro instructions peut déclarer des variables locales.

  coruscant chris$ cat macroinstruction.pir
  .macro SurfaceTrapeze (S, B, b, h)
    .local num var
    var = .B + .b
    var *= .h
    .S = var / 2
  .endm

  .sub main :main
    .local num surface
    .SurfaceTrapeze (surface, 10, 5, 3)
    print "La surface du premier trapeze est egale a "
    say surface
    .SurfaceTrapeze (surface, 8, 5, 3)
    print "La surface du second trapeze est egale a "
    say surface
    .end
  .end
  coruscant chris$ parrot macroinstruction.pir
  La surface du premier trapeze est egale a 22.5
  La surface du second trapeze est egale a 19.5
  coruscant chris$

Les variables locales se distinguent des paramètres transmis par l'intermédiaire de la liste d'appel par le point qui précède le nom de ces derniers.

Une application.

Voici un programme qui convertit un nombre représenté en chiffres arabes en son équivalent en numération romaine.

La translitération proposée ici reste très traditionnelle. La présentation de J. Forget [Romain] lors des journées Perl 2009 donne un très bon aperçu du problème.

  coruscant chris$ cat romains.pir
  .macro CreerHash (h, ch1, ch2)
  # Macro qui construit un hash a partir de deux chaines.
    .local int i, long
    $P1 = new 'Array'
    $P2 = new 'Array'
    $P1 = split ",", .ch1
    $P2 = split ",", .ch2
    long = $P1
    i = 0
  CREER:
    $S1 = $P1[i]
    $S2 = $P2[i]
    .h[$S2] = $S1
    i+= 1
    if i < long goto CREER
  .endm 
  
  .sub main :main
    .local pmc hash
     hash = new ['OrderedHash']
    .local string arabes, romains
    arabes = "1000,900,500,400,100,90,50,40,10,9,5,4,1" 
    romains = "M,CM,D,CD,C,XC,L,XL,X,IX,V,IV,I"
    .CreerHash (hash, arabes, romains)
    .local pmc cles
    .local string resultat
    .local int val, nombre, quotient, reste
    .local pmc STDIN
    STDIN = getstdin
    
  LECTURE:
    print "Nombre a convertir ? "
    $S0 = readline STDIN
    chopn $S0, 1
    val = $S0
    nombre = $S0
    if nombre <= 3999 goto OK
    say "Le nombre doit etre inferieur a 4000."
    goto LECTURE
    
  OK:
    resultat = ""
    cles = new 'Iterator', hash
    CALCUL:
    $S0   = shift cles
    $I0 = hash[$S0]
    quotient = nombre / $I0
    reste = nombre % $I0
    repeat $S0, $S0 , quotient
    resultat .= $S0
    nombre = reste
    i += 1
    if reste goto CALCUL
    print "Le nombre "
    print val
    print " s'ecrit "
    say resultat
     print "Nouveau calcul (o/n) ? "
    $S0 = readline STDIN
    chopn $S0, 1
    if $S0 == "o" goto LECTURE
    end
  .end
  coruscant chris$ parrot romains.pir
  Nombre a convertir ? 4500
  Le nombre doit etre inferieur a 4000.
  Nombre a convertir ? 3999
  Le nombre 3999 s'ecrit MMMCMXCIX
  Nouveau calcul (o/n) ? o
  Nombre a convertir ? 666
  Le nombre 666 s'ecrit DCLXVI
  Nouveau calcul (o/n) ? n
  coruscant chris$

la macro instruction .macro CreerHash (h, ch1, ch2) va construire un hash à partir de deux chaînes de caractères.

Les clés du hash sont les chaînes de caractères de la numération romaine, la valeur représente son équivalent en numération arabe sous la forme :

  hash{M} = 1000
  hash{CM} = 900
  hash{D} = 500
      .
      .
  hash{IV} = 4
  hash{I} = 1

Une fois construit, le hash est exploré au moyen de l'itérateur. L'algorithme consiste à effectuer des divisions par les valeurs successives et à générer les chaînes correspondantes.

Noter au passage l'instruction de multiplication de chaînes repeat Ch1, Ch2 , n qui reproduit n fois la chaîne Ch2 et la renvoie dans la chaîne Ch1.

Les étiquettes locales.

Considérons une macro instruction qui désire lire un nombre et vérifier qu'il soit pair. Nous obtenons le code suivant :

  coruscant chris$ cat macroinstruction.pir
  .macro Pair (x)
  LECTURE:
    print "Donnez moi un nombre pair : "
    $S0 = readline STDIN
    chopn $S0, 1
    .x = $S0
    $I0 = .x % 2
    unless $I0 goto FIN
    say "J'ai dit un nombre pair."
    goto LECTURE
  FIN:
  .endm
  
  .sub "Lire un nombre pair." :main
   .local pmc STDIN
    STDIN = getstdin
   .local int A
   .Pair (A)
   say A
  .end
  coruscant chris$ parrot macroinstruction.pir
  Donnez moi un nombre pair : 2
  2
  coruscant:Desktop chris$ parrot macroinstruction.pir 
  Donnez moi un nombre pair : 1
  J'ai dit un nombre pair.
  Donnez moi un nombre pair : 2
  2
  coruscant chris$

Le programme s'exécute sans le moindre problème.

Mais si maintenant nous procédons à plusieurs appels de la macro-instruction.

  .sub "Lire un nombre pair." :main
    .local pmc STDIN
    STDIN = getstdin
    .local int A, B
    .Pair (A)
    .Pair (B)
  .end

Nous obtenons le message d'erreur suivant :

  error:imcc:Label 'LECTURE' already defined
      in macro '.Pair' line 2
      included from 'test.pir' line 1

Cette erreur provient du fait que la substitution syntaxique a généré les lignes suivantes :

  .local int A, B
  LECTURE:
    print "Donnez moi un nombre pair : "
    * * *
    unless $I0 goto FIN
    * * *
    goto LECTURE
  FIN:
  LECTURE:
    print "Donnez moi un nombre pair : "
    * * *
    unless $I0 goto FIN
    * * *
    goto LECTURE
  FIN:

Ce qui correspond bien au type de l'erreur annoncée, à savoir une définition multiple d'étiquettes.

Pour éviter ce problème, on dispose d'un moyen pour déclarer des étiquettes locales au corps de la macro-instruction.

Un label local sera déclaré au moyen de la directive .label et doit commencer par le caractère $ (dollar). Sa référence se fait en faisant précéder son nom d'un point (.).

  coruscant chris$ cat macroinstruction.pir
  .macro Pair (x)
  .label $LECTURE:
    print "Donnez moi un nombre pair : "
    $S0 = readline STDIN
    chopn $S0, 1
    .x = $S0
    $I0 = .x % 2
    unless $I0 goto .$FIN
    say "J'ai dit un nombre pair."
    goto .$LECTURE
  .label $FIN:
  .endm
  
  .sub "Lire un nombre pair." :main
   .local pmc STDIN
    STDIN = getstdin
   .local int A, B
   .Pair (A)
   say A
   .Pair (B)
   say B
  .end
  coruscant chris$ parrot macroinstruction.pir
  Donnez moi un nombre pair : 2
  2
  Donnez moi un nombre pair : 1
  J'ai dit un nombre pair.
  Donnez moi un nombre pair : 6
  6
  coruscant chris$

Passage d'étiquette.

Il est aussi parfaitement possible de passer un label dans la liste d'appel d'une macro instruction.

  coruscant chris$ cat macroinstruction.pir
  .macro Etiquette (x)
    goto .x
  .endm
  
  .sub "Branchement" :main
    say "Debut du programme."
    .Etiquette (SUITE)
    say "Jamais executee."
  SUITE:
    say "Au revoir."
  .end
  coruscant chris$ parrot macroinstruction.pir
  Debut du programme.
  Au revoir.
  coruscant chris$

Les paramètres de la ligne d'appel.

Un programme PIR peut récupérer les paramètres de la ligne d'appel.

C'est par l'intermédiaire de la liste argv que ce fera cette opération. Le premier élément de argv[0] contient le nom du programme, les autres éléments la liste de paramètres.

  coruscant chris$ cat arguments.pir
  .sub main :main
    .param pmc argv
    .local int argc
    argc = elements argv
    print "Nombre d'arguments : "
    say argc
    $I0 = 0
  SUITE:
    if $I0 == argc goto FIN
    print "Argument "
    print  $I0
    print " : "
    $S0 = argv[$I0]
    say $S0
    $I0 += 1
    goto SUITE
  FIN:
   .end
  coruscant chris$ parrot arguments.pir a b c
  Nombre d'arguments : 4
  Argument 0 : arguments.pir
  Argument 1 : a
  Argument 2 : b
  Argument 3 : c
  coruscant chris$

Appel d'une fonction Unix.

Utilisation d'une chaîne de caractères.

L'instruction spawn va nous permettre de soumettre une commande au système. Dans un premier temps, la commande peut être entièrement contenue dans une chaîne de caractères.

  coruscant chris$ cat spawn.pir
  .sub "test" :main
    $S0 = "perl -e \"print 'Hello\n'\""
    $I0 = spawnw $S0
    print   "Code de retour : "
    print   $I0
    print   "\n"
    $S0 = "date"
    $I0 = spawnw $S0
    print   "\n"
  .end
  coruscant chris$ parrot spawn.pir
  Hello
  Code de retour : 0
  Dim 21 jui 2009 14:49:46 CEST
  coruscant chris$

Utilisation d'une liste de chaînes de caractères.

Dans un second temps, la commande peut être construite à partir d'une liste de chaînes de caractères.

  coruscant chris$ cat spawn.pir
  .sub "test" :main
    .local pmc commande
    commande = new "ResizablePMCArray"
    commande [0] = "perl"
    commande [1] = "-e"
    commande [2] = "print \"Bonjour a tous.\n\""
    $I0 = spawnw commande
    print   "Code de retour : "
    shr $I1, $I0, 8
    print   $I1
    print   "\n"
  .end
  coruscant chris$ parrot spawn.pir
  Bonjour a tous.
  Code de retour : 0
  coruscant chris$ cat commande.pir
  .sub "test" :main
    .local pmc commande
    commande = new "ResizablePMCArray"
  # Definition de la commande.
    commande [0] = "ls"
    say "Commande : ls"
    $I0 = spawnw commande
    say "Commande : ls -l"
  # Ajout de l'extension -l.
    commande [1] = "-l"
    $I0 = spawnw commande
    say "Commande : ls -l /bin"
  # Ajout du repertoire a lister.
    commande [2] = "/bin"
    $I0 = spawnw commande
  .end
  coruscant chris$ parrot commande.pir
  Commande : ls
  Ajax			Pr.pir
  CFP.pdf		Pr.pl
  * * *
  Photos		wile_coyote16.gif
  Radios		www3
  Commande : ls -l
  total 24952
  drwxr-xr-x   10 chris  chris      340  8 mar 15:30 Ajax
  -rw-r--r--@   1 chris  chris   155297 13 mai 15:39 CFP.pdf
  * * *
  -rw-r--r--    1 chris  chris    20177  8 oct  2006 wile_coyote16.gif
  -rw-r--r--@   1 chris  chris      248 28 jul  2008 www3
  Commande : ls -l /bin
  total 18832
  -rwxr-xr-x  1 root  wheel  1244928  5 mar  2008 bash
  -r-xr-xr-x  1 root  wheel    43296  6 mar  2008 cat
  * * *
  -rwxr-xr-x  2 root  wheel   982000 24 sep  2007 zsh
  -rwxr-xr-x  2 root  wheel   982000 24 sep  2007 zsh-4.3.4
  coruscant chris$

Chaque fois qu'un nouvel élément est ajouté à la liste représentative de la commande, il est pris en compte par l'instruction spawn.

Conclusion.

Nous avons maintenant une bonne connaissance de PIR et de son utilisation. Nous avons pu constater l'étendue de ses possibilités en programmation classique. Le prochain chapitre nous permettra d'aller un peu plus loin et de découvrir que ce langage peut aussi ouvrir les portes de la programmation orientée objet.

Références

[Hanoï] http://articles.mongueurs.net/magazines/perles/perles-hanoi.html

[Fermeture] Les Fermetures. http://fr.wikipedia.org/wiki/Fermeture_(informatique)

[Aléat] http://fr.wikipedia.org/wiki/Générateur_de_nombres_pseudo-aléatoires

[Pascal] http://pagesperso-orange.fr/therese.eveilleau/pages/jeux_mat/textes/pascal2.htm

[Macro] http://fr.wikipedia.org/wiki/Macro-définition

[Romain] Les journées Perl 2009. Présentation de Jean Forget. http://fpw2009.ubicast.eu/videos/free/62/

[Langages] http://www.parrot.org/languages

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]