Copyright © 2009 Christian Aperghis-Tramoni
split et join.push et pop.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
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.
Le langage PIR dispose de deux types de listes statiques, les listes
typées et les listes non typées.
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$
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]
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.
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$
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.
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.
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].
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.
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.
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.
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.
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.
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.
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$
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é.
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.
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.
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.
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.
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.
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$
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$
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$
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$
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.
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.
[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
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.