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.