Article publié dans Linux Magazine 98, octobre 2007.
Copyright © 2007 Christian Aperghis-Tramoni
split
et join
Le premier article nous a permis de montrer combien il était facile de développer en assembleur Parrot. Nous allons dans cette seconde partie aborder les structures de données et l'accès aux fichiers. Plusieurs programmes nous permettrons de mettre en application les sujets traités.
Avant d'aborder le problème des ruptures de séquence en assembleur,
je ne saurais que recommander la lecture de l'article de Philippe Bruhat
et Jean Forget consacré à l'histoire de l'instruction goto
[goto].
Lorsqu'on programme en langage d'assemblage, la rupture dans le séquencement d'une suite d'instructions est inévitable. Cette affirmation n'interdit pas de tenter d'en minimiser le nombre et la portée afin de conserver au programme une certaine lisibilité.
Les opérations de rupture de séquence, qu'elles soient conditionnelles ou inconditionnelles, font référence à une adresse symbolique, toujours représentée par une étiquette.
Voici un exemple à ne surtout pas suivre, mais qui permet d'illustrer le
fonctionnement de l'instruction de rupture inconditionnelle de séquence
branch
.
coruscant:~/Langages/asmparrot chris$ cat rupt.pasm branch E1 E2: print "Et puis par la.\n" branch FIN E1: print "Je passe par ici.\n" branch E2 FIN: print "Et enfin, je termine.\n" end coruscant:~/Langages/asmparrot chris$ parrot rupt.pasm Je passe par ici. Et puis par la. Et enfin, je termine. coruscant:~/Langages/asmparrot chris$
Il existe deux familles de rupture conditionnelle de séquence. Comme son
nom l'indique, la réalisation du saut sera soumise à une condition. Dans
le cas de l'instruction de code opération if
, il est conditionné au
test (vrai ou faux) du contenu d'un registre. Une évaluation
à vrai (non undef
) contraindra le programme à réaliser le saut demandé,
alors qu'avec un résultat de type faux (undef
), le programme continuera
à s'exécuter en séquence. À noter qu'il existe aussi une instruction
unless
qui, au lieu de tester la condition à vrai, la teste à faux.
En voici l'illustration sur un exemple :
coruscant:~/Langages/asmparrot chris$ cat exo.pasm getstdin P0 getstdout P1 print P1, "Quel est le diviseur ?\n" readline S1, P0 set I1, S1 print P1, "Quel est le dividende ?\n" readline S1, P0 set I2, S1 mod I3, I2, I1 if I3, RESTE print P1, I1 print P1, " est un diviseur entier de " print P1, I2 branch FIN RESTE: print P1, I1 print P1, " divise " print P1, I2 print P1, " avec un reste egal a " print P1, I3 FIN: print P1, ".\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm Quel est le diviseur ? 5 Quel est le dividende ? 12 5 divise 12 avec un reste egal a 2. coruscant:~/Langages/asmparrot chris$ parrot exo.pasm Quel est le diviseur ? 5 Quel est le dividende ? 25 5 est un diviseur entier de 25. coruscant:~/Langages/asmparrot chris$
Lorsque le contenu doit être testée relativement à une valeur de référence, on dispose de six opérations logiques pour effectuer cette comparaison. Ce dont :
eq
test d'égalité
ne
test de différence (non égal)
lt
test d'infériorité
gt
test de supériorité
le
test d'infériorité au sens large (<=
)
ge
test de supériorité au sens large (>=
).
Le programme suivant permet de calculer les n premiers éléments de la suite de Fibonacci, le nombre de valeurs à calculer sera lu sur le clavier.
coruscant:~/Langages/asmparrot chris$ cat fibo.pasm getstdin P0 getstdout P1 print P1, "Combien de valeurs doit-on calculer ?\n" readline S1, P0 print P1, "----------\n" set I1, 1 set I2, S1 set I3, 1 set I4, 1 BOUCLE: gt I1, I2, FIN set I5, I4 add I4, I3, I4 set I3, I5 print P1, "Fibonnaci(" set S1, I1 concat S0, " ", S1 substr S1, S0, -2 print P1, S1 print P1, ") = " set S1, I3 concat S0, " ", S1 substr S1, S0, -2 print P1, S1 print P1, "\n" inc I1 branch BOUCLE FIN: end coruscant:~/Langages/asmparrot chris$ parrot fibo.pasm Combien de valeurs doit-on calculer ? 10 ---------- Fibonnaci( 1) = 1 Fibonnaci( 2) = 2 Fibonnaci( 3) = 3 Fibonnaci( 4) = 5 Fibonnaci( 5) = 8 Fibonnaci( 6) = 13 Fibonnaci( 7) = 21 Fibonnaci( 8) = 34 Fibonnaci( 9) = 55 Fibonnaci(10) = 89 coruscant:~/Langages/asmparrot chris$
C'est l'utilisation judicieuse des opérations conditionnelles et
inconditionnelles qui va permettre de réaliser toutes les boucles ou tests
dont nous avons l'habitude de nous servir lorsque nous programmons dans un
langage de haut niveau. La boucle for
qui, en Perl, s'écrit :
for ($i=debut; $i<fin; $i++) { # corps de la boucle. }
Sera traduite en assembleur par la séquence d'instructions :
set I0, debut FOR: # corps de la boucle inc I0 lt I0, fin, FOR # suite du programme
De la même manière, l'écriture en Perl de la boucle while
:
while (variable cond valeur) { # corps de la boucle. # modification de la variable. }
Se présentera en Parrot sous la forme :
WHILE: set I1, valeur_depart cond I1, valeur, FIN # corps de la boucle # modification de I1 branch WHILE FIN: # suite du programme
Terminons sur l'instruction conditionnelle :
if (VAL1 == VAL2) { # bloc vrai. } else { # bloc faux }
Dont la traduction en assembleur Parrot donne :
set I0, VAL1 set I1, VAL2 ne I0, I1, FAUX # bloc vrai branch FIN_IF FAUX: # bloc faux FIN_IF: # suite du programme
C'est l'utilisation des PMC appropriés qui va permettre la manipulation des structures (listes, hash). L'ensemble des PMC disponibles sont stockés dans le répertoire include/parrot/pmc.h et nous avons vu qu'on pouvait en obtenir la liste au moyen d'un programme. La documentation détaillée est disponible sur le site Parrot [pmc]. Ici, nous limiterons la présentation aux structures de base standard les plus souvent utilisées, listes statiques, listes dynamiques et hash. À terme, on devrait disposer d'une vaste palette de PMC permettant la gestion de plusieurs types d'agrégats couvrant plusieurs langages de programmation.
Commençons par un exemple qui met en évidence l'utilisation d'un agrégat statique, une table au sens classique du terme dont la longueur doit être spécifiée avant de pouvoir l'utiliser. Ce programme va créer une structure indexée et va stocker dans chaque emplacement i de l'agrégat la somme des i premiers nombres entiers.
coruscant:~/Langages/asmparrot chris$ cat agregat1.pasm getstdin P0 getstdout P1 # Declaration de la table. new P3, .Array print P1, "Nombre d'elements a calculer ?\n" readline S1, P0 set I2, S1 # Declaration de la longueur de la table. set P3, I2 set I0, 0 set I1, 0 BOUCLE: eq I0, I2, IMPRIME add I1, I1, I0 set P3[I0], I1 inc I0 branch BOUCLE IMPRIME: set I0, 0 print P1, "Longueur de la table : " # Recuperation de la longueur de la table. set I3, P3 print P1, I2 print P1, "\n" CONT: eq I0, I2, FIN print P1, "Table(" set S1, I0 concat S0, " ", S1 substr S1, S0, -2 print P1, S1 print P1, ") = " set S1, P3[I0] concat S0, " ", S1 substr S1, S0, -2 print P1, S1 print P1, "\n" inc I0 branch CONT FIN: end coruscant:~/Langages/asmparrot chris$ parrot agregat1.pasm Nombre d'elements a calculer ? 12 Longueur de la table : 12 Table( 0) = 0 Table( 1) = 1 Table( 2) = 3 Table( 3) = 6 Table( 4) = 10 Table( 5) = 15 Table( 6) = 21 Table( 7) = 28 Table( 8) = 36 Table( 9) = 45 Table(10) = 55 Table(11) = 66 coruscant:~/Langages/asmparrot chris$
L'agrégat étant statique, ses bornes sont testées avant affectation. Une
erreur dans l'indexation de la structure ainsi définie déclenchera une
exception et le message d'erreur "Array index out of bounds!"
sera
imprimé.
Le même programme peut aussi être réalisé en utilisant une structure
dynamique, une liste au sens Perl du terme, par exemple un agrégat de type
.ResizableIntegerArray
. Le programme est identique, il suffit de changer
la déclaration du PMC de
new P3, .Array
en
new P3, .ResizableIntegerArray
Puis, si on le désire, de supprimer la ligne déclarant la longueur de la structure :
set P3, I2
Cette opération n'est pas indispensable, la gestion dynamique de l'agrégat n'imposant pas son dimensionnement préalable. Il est néanmoins possible d'utiliser cette possibilité. Si par exemple il est nécessaire de disposer à un instant donné d'une liste vide, il suffit de lui affecter la longueur zéro.
set P3, 0
Nous allons illustrer le comportement d'une telle structure, en écrivant le programme qui calcule les lignes successives du triangle de Pascal, programme particulièrement adapté à la gestion dynamique d'une liste. Rappelons très brièvement la méthode de calcul pour n lignes. Dans un dialecte classique, on réserverait une matrice n*n dans laquelle seuls un peu plus de la moitié des emplacements (n+1)/2n seraient utilisés. Un élément p[i,j] se calcule par rapport aux deux éléments de la ligne précédente p[i-1,j] + p[i-1,j-1]. Cet algorithme peut être écrit d'une manière élégante en Perl en n'utilisant une seule liste dont la longueur augmentera de 1 à chaque itération dans laquelle la ligne concernée sera générée, et un doublet dans lequel seront mémorisées les valeurs servant au calcul. En Perl, cet algorithme se programme comme suit :
$l = @t; for ($j=1; $j<=$l; $j++) { @tp = ($tp[1], $t[$j]); $t[$j] = $tp[0] + $tp[1]; }
La réécriture en Parrot va utiliser les deux types de structures, un PMC
statique .Array
pour mémoriser le doublet de valeurs et un PMC dynamique
.ResizableIntegerArray
pour générer les itérations successives.
coruscant:~/Langages/asmparrot chris$ cat pascal.pasm # Le triangle de Pascal getstdin P0 getstdout P1 # Nombre de lignes a générer dans I0. set I0, 10 # P3, vecteur à deux éléments. new P3, .Array set P3, 2 # P4, ligne courante du triangle. new P4, .ResizableIntegerArray set P4[0], "" set P4[1], 1 ITERATION: # Longueur courante du vecteur dans I2. set I2, P4 dec I0 lt I0, 0, FIN inc I1 # Impression du vecteur. set I31, 1 CONTINUE: # Formatage des résultats sur 4 caractères. set S1, P4[I31] concat S1, " ", S1 substr S1, S1, -4 print P1, S1 inc I31 lt I31, I2, CONTINUE print P1, "\n" # Calculer la ligne suivante à partir de la ligne courante. # I31 : Indice d'exploration de vecteur. set I31, 1 CALCUL: set I30, P3[1] set P3[0], I30 set I30, P4[I31] set P3[1], I30 set I30, P3[0] set I29, P3[1] add I30, I30, I29 set P4[I31], I30 # Progression dans le vecteur. inc I31 le I31, I2, CALCUL branch ITERATION FIN: end coruscant:~/Langages/asmparrot chris$ parrot pascal.pasm 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:~/Langages/asmparrot chris$
Ce programme peut, à volonté, être modifié par exemple pour demander
combien d'itérations doivent être calculées ou, plus intéressant,
n'imprimer que celle correspondant à un rang donné.
De plus, le formatage de la sortie sur 4 caractères, 3 chiffres plus une
espace, qui ne permet pas la présentation des résultats au-delà de 12
lignes, peut lui aussi évoluer. Une modification facile à mettre en oeuvre
consisterait à indiquer son numéro devant chacune des lignes en remarquant
qu'il correspond au nombre d'éléments de la structure moins 1, le premier
emplacement étant à Undef
pour permettre de démarrer l'algorithme.
La taille est récupérée dans un registre entier au moyen de l'instruction :
set Ix, P4
Il ne reste plus qu'à lui retrancher un et à l'imprimer.
Terminons ce chapitre sur les agrégats avec une structure de type hash,
au sens Perl du terme, c'est à dire une structure de donnée dans laquelle
chaque valeur est accessible par l'intermédiaire d'une clé.
La déclaration de la structure se fait par l'intermédiaire du PMC .Hash
.
new Px, .Hash
L'écriture d'une valeur, explicite ou en provenance d'un registre (Rz
),
dans le hash défini par le PMC Px
, à l'emplacement spécifié par une clé
Sy
qui peut elle aussi être un littéral ou une référence à un registre
se fait classiquement au moyen de l'opération de chargement set
.
set Px[Sy], Rz
L'instruction exists
permet de tester l'existence d'une clé d'entrée.
exists Ix, Px["cle"]
Le registre entier concerné (Ix
) sera positionné à vrai si la clé
spécifiée existe dans le cas contraire il sera positionné à faux.
Cette instruction teste l'existence de l'entrée, mais elle ne teste pas
la valeur qui lui est associée. Le résultat du test sera vrai lorsque
l'entrée relative à la clé a été définie, même si la valeur qui lui
correspond est undef
. Pour vérifier que la donnée correspondante est
différente de undef
, on dispose de l'instruction defined
.
defined Ix, Px["cle"]
Le registre Ix
contiendra alors faux si Px["cle"]
est undef
,
vrai dans le cas contraire.
Pour pouvoir travailler efficacement sur un hash, il est indispensable de disposer d'une méthode adaptée permettant la récupération de la liste des clés d'entrée. Pour concrétiser cette opération il est nécessaire de faire référence à un itérateur spécifique iterator.pasm dont le code se trouve stocké dans le répertoire /usr/local/lib/parrot/include et qui sera inclus dans le programme au moyen de l'instruction :
.include "iterator.pasm"
Une requête à l'itérateur permet de créer dans un PMC Px
, une liste
qui va contenir l'ensemble des clés d'accès au hash défini par le PMC
Py
.
new Px, .Iterator, Py
Cet itérateur propose plusieurs méthodes d'accès :
ITERATE_FROM_START
, ITERATE_FROM_START_KEYS
, ITERATE_GET_NEXT
,
ITERATE_GET_PREV
, ITERATE_FROM_END
.
Donnant le choix entre plusieurs méthodes différentes pour explorer la liste
de clés ainsi construite. Dans le cas qui nous intéresse, nous choisissons
la méthode .ITERATE_FROM_START
qui, comme son nom l'indique, explore la
liste en commençant par le début.
set P2, .ITERATE_FROM_START
Reprenons l'ensemble de ces opérations dans un programme.
coruscant:~/Langages/asmparrot chris$ cat jardin.pasm .include "iterator.pasm" getstdin P0 getstdout P1 # Déclaration du hash. new P3, .Hash print P1, "Etat des plantations de mon jardin.\n" # Lecture des informations et construction du hash. JARDIN: print P1, "Nom de la fleur ?\n" readline S5, P0 chopn S5, 1 eq S5, "", TERMINE print P1, "Nombre d'unites ?\n" readline S1, P0 chopn S1, 1 set P3[S5], S1 branch JARDIN TERMINE: print P1, "Statistiques du jardin.\n" print P1, "-----------------------\n\n" # Récupération de la liste des fleurs (les clés du hash). new P2, .Iterator, P3 set P2, .ITERATE_FROM_START set I1, P2 print P1, "Le jardin comporte " print P1, I1 print P1, " essences florales differentes.\n" print P1, "Ce sont :\n" # Impression des résultats. BOUCLE: unless P2, FIN shift S1, P2 print P1, S1 print P1, " au nombre de " set S2, P3[S1] print P1, S2 print P1, "\n" branch BOUCLE FIN: end coruscant:~/Langages/asmparrot chris$ parrot jardin.pasm Etat des plantations de mon jardin. Nom de la fleur ? Tulipes Nombre d'unites ? 250 Nom de la fleur ? Iris Nombre d'unites ? 500 Nom de la fleur ? Jonquilles Nombre d'unites ? 350 Nom de la fleur ? Statistiques du jardin. ----------------------- Le jardin comporte 3 essences florales differentes. Ce sont : Tulipes au nombre de 250 Iris au nombre de 500 Jonquilles au nombre de 350 coruscant:~/Langages/asmparrot chris$
L'instruction clone
permet comme nous l'avons vu de cloner un agrégat,
c'est-à-dire de dupliquer l'agrégat considéré en en créant une nouvelle
instance.
new P1, .ResizableIntegerArray # Affectation de valeurs aux éléments de P1. set P1[ ], ... # Duplication de P1. clone P2, P1 # Affectation de valeurs aux éléments de P2. set P2[ ], ... # Impression des éléments de P1. set S1, P1[ ] print S1 # Impression des éléments de P2. set S1, P2[ ] print S1
split
et join
Ces deux instructions Parrot se comportent comme les fonctions Perl du même nom.
split
permet le découper d'une chaîne pour générer un agrégat. Dans
l'état actuel du développement, l'implémentation n'autorise qu'une chaîne
de caractères comme critère pour déterminer l'emplacement de la scission.
Dans sa version définitive, c'est une expression régulière qui la remplacera.
coruscant:~/Langages/asmparrot chris$ cat decoup.pasm new P0, .Array set P0, 2 set S0, "ab..cd" split P0, "..", S0 # Création de l'agregat. set S0, P0[0] print S0 print "\n" set S0, P0[1] print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot decoup.pasm ab cd coruscant:~/Langages/asmparrot chris$
join
va réunir l'ensemble des éléments d'un agrégat en une chaîne
unique, le second argument permet de spécifier quel seront les caractères
qui devront être utilisés pour effectuer cette liaison.
# FIXME nbc # normalement on réserve l'usage de second à des cas # oú il n'y pas de troisième.
coruscant:~/Langages/asmparrot chris$ cat union.pasm new P0, .Array set P0, 3 set P0[0], "Zero" set P0[1], "Un" set P0[2], "Deux" join S0, ", ", P0 print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot union.pasm Zero, Un, Deux coruscant:~/Langages/asmparrot chris$
Une référence destination d'un sous-programme implique la finalisation
de deux actions. Concrétiser la rupture de séquence pour aller exécuter
l'ensemble d'instructions représentatives de la fonction et mémoriser
l'adresse courante pour pouvoir, une fois la portion de code terminé,
revenir à l'emplacement d'où s'est effectué l'appel, ce retour sera
effectif au moment où sera rencontrée l'instruction ret
.
La première manière d'aller exécuter un sous-programme est d'y faire
référence en adressage relatif. C'est l'instruction bsr
(branch to
subroutine) qui permet de réaliser cette action.
bsr _nom
L'adresse du sous-programme est calculée relativement à l'emplacement de l'instruction qui y fait référence.
coruscant:~/Langages/asmparrot chris$ cat sprel.pasm # Branchement en relatif. print "On est dans le programme principal.\n" print "On se branche au sous programme.\n" bsr _sp print "Retour au programme principal.\n" end _sp: print "On est dans le sous programme.\n" ret coruscant:~/Langages/asmparrot chris$ parrot sprel.pasm On est dans le programme principal. On se branche au sous programme. On est dans le sous programme. Retour au programme principal. coruscant:~/Langages/asmparrot chris$
Le saut à un sous-programme peut s'effectuer en faisant directement référence
à son adresse. Une instruction spécifique permet de la connaître et de la
sauvegarder dans un registre entier set_adr
, et, dans ces conditions,
c'est l'instruction jsr
(jump to subroutine) qui sera utilisée pour
réaliser l'opération.
set_adr Ix, _nom # mémorisation de l'adresse dans un registre entier jsr Ix # saut à l'adresse contenue dans I0.
Reprenons le programme ci dessus en effectuant un saut absolu.
coruscant:~/Langages/asmparrot chris$ cat sabs.pasm # Saut en absolu. print "On est dans le programme principal.\n" print "On se branche au sous programme.\n" set_addr I1, _sp jsr I1 print "Retour au programme principal.\n" end _sp: print "---> On est dans le sous programme. <---\n" ret coruscant:~/Langages/asmparrot chris$ parrot sabs.pasm On est dans le programme principal. On se branche au sous programme. ---> On est dans le sous programme. <--- Retour au programme principal. coruscant:~/Langages/asmparrot chris$
Personnellement, et toujours dans le but de clarifier la présentation,
j'utilise des lettres minuscules pour référencer mes sous-programmes et
leur nom commence toujours par le caractère blanc souligné (_
). Il est
ainsi possible de les distinguer immédiatement des étiquettes correspondant
à des adresses symboliques et qui sont représentées en majuscules.
Une fois de plus, les capacités offertes par les PMC vont nous permettre d'aller un peu plus loin dans la construction de sous-programmes. Dans un premier temps, créer un sous-programme peut se faire en déclarant un PMC.
.const .Sub P0 = "_sp"
Le PMC P0
va alors permettre de faire référence au sous-programme
qui vient d'être défini par son nom.
Dans ces conditions, l'adresse de retour sera générée dynamiquement dans
le registre P1
au moment de l'appel qui doit se faire par l'intermédiaire
de l'instruction invokecc
.
coruscant:~/Langages/asmparrot chris$ cat sp.pasm print "On est dans le programme principal.\n" .const .Sub P0 = "_sp" print "On se branche au sous programme.\n" invokecc P0 print "On est de retour au programme principal.\n" end .pcc_sub _sp: print "---> On est dans le sous programme. <---\n" returncc coruscant:~/Langages/asmparrot chris$ parrot sp.pasm On est dans le programme principal. On se branche au sous programme. ---> On est dans le sous programme. <--- On est de retour au programme principal. coruscant:~/Langages/asmparrot chris$
Pour autoriser la récursivité lors de la conception d'un sous-programme,
il faut réaliser en Parrot ce qui est fait en Perl par l'intermédiaire
des variables privées (my
). Lorsqu'une variable est déclarée my
sa
visibilité est réduite au bloc qui la contient. De plus, toute entrée
dans le bloc en question aura pour effet d'en créer une nouvelle instance
alors que la sortie du bloc détruira la dernière instance créée.
Une des solutions pour réaliser cette fonctionnalité est de se servir
de la pile banalisée pour sauvegarder les variables au moment où on
rentre dans le sous-programme et de les restaurer en le quittant.
En nous basant sur le classique programme des Tours de Hanoi [Hanoi] dont
l'écriture en Perl se présente 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); }
Il est possible de réaliser, sans trop de difficulté, la même chose en Parrot en suivant le modèle défini par le programme que nous venons de voir.
coruscant:~/Langages/asmparrot chris$ cat hanoi.pasm # I0 : Nombre de disques # S1 : Nom de la première tour (a). # S2 : Nom de la seconde tour (b). # S3 : Nom de la troisième tour (c). getstdin P0 getstdout P1 print P1, "Combien de disques ?\n" readline S0, P0 set I0, S0 set S1, "a" set S2, "b" set S3, "c" bsr _hanoi end # I0, S1, S2 et S3 sont privées. _hanoi: # Sauvegarde des variables privées dans la pile banalisée. save I0 save S1 save S2 save S3 eq I0, 0, OUT dec I0 exchange S2, S3 bsr _hanoi print P1, "De " print P1, S1 print P1, " vers " print P1, S2 print P1, "\n" exchange S1, S3 exchange S2, S3 bsr _hanoi OUT: # Restauration des variables privees. restore S3 restore S2 restore S1 restore I0 ret coruscant:~/Langages/asmparrot chris$ parrot hanoi.pasm Combien de disques ? 3 De a vers c De a vers b De c vers b De a vers c De b vers a De b vers c De a vers c coruscant:~/Langages/asmparrot chris$
Au moment où on entre dans le sous-programme, les quatre instructions :
save I0 save S1 save S2 save S3
permettent de sauvegarder dans la pile banalisée la valeur des variables concernées par la récursion r avant de lancer la récursion r + 1. Et lorsqu'on sort du sous-programme, à la fin de la récursion r + 1, les quatre instructions
restore S3 restore S2 restore S1 restore I0
effectuent l'opération inverse, à savoir rendre aux variables concernées la valeur qu'elles avaient lors de la récursion r.
Dans le programme que nous venons de voir, le passage d'arguments était géré
par le concepteur. C'était à lui à décider par quel registre était transmise
une donnée particulière destinée à la fonction. Parrot propose quatre
instructions permettant de s'affranchir de cette contrainte.
Le programme appelant va pouvoir au moyen de l'instruction set_args
spécifier
la liste des arguments à transmettre au sous-programme, lequel pourra les
récupérer par get_params
.
set_args "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn get_params "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn
Les descripteurs, un par valeur à transmettre, sont des entiers qui vont permettre de contrôler le type de l'argument correspondant (valeur explicite ou référence à un registre). Si la valeur est mise à 0, c'est l'assembleur qui se charge du calcul en fonction du paramètre correspondant.
coruscant:~/Langages/asmparrot chris$ cat params.pasm .const .Sub P0 = "_sp" print "Appel du sous programme.\n" set I0, 100 # Premier argument passe par un registre. # Second et troisième argument explicite. set_args "(0,0,0)", I0, 200, "Bonjour" invokecc P0 print "Retour au programme principal.\n" end .pcc_sub _sp: print "---> On est dans le sous programme. <---\n" # Le premier argument est récupéré dans I10. # Le second argument est récupéré dans I20. # Le troisième argument est récupéré dans S10 get_params "(0,0,0)", I10, I20, S10 print "Premiere valeur : " print I10 print "\nSeconde valeur : " print I20 print "\nTroisieme valeur : " print S10 print "\n" returncc coruscant:~/Langages/asmparrot chris$ parrot params.pasm Appel du sous programme. ---> On est dans le sous programme. <--- Premiere valeur : 100 Seconde valeur : 200 Troisieme valeur : Bonjour Retour au programme principal. coruscant:~/Langages/asmparrot chris$
C'est de la même manière que la fonction retournera au programme appelant les résultats qui auront été calculés.
set_returns "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn get_results "(desc0, desc1, .... , descn)", ARG0, ARG1, .... , ARGn
Nous avons dit qu'un sous-programme dont le nom est préfixé par la déclaration
.pcc_sub
est stocké dans un cache global, son adresse pourra donc être
retrouvée au moyen de l'instruction find_global
. Cette option alliée
à l'instruction load_bytecode
qui permet d'aller chercher des lignes de
code sur un fichier externe, donne la possibilité de créer des modules et
d'y faire appel au moment voulu.
coruscant:~/Langages/asmparrot chris$ cat mess.pasm # mess.pasm # Sous-programme catalogue. .pcc_sub _sub: print "----------> On est dans le sous programme.\n" returncc coruscant:~/Langages/asmparrot chris$ cat main.pasm # main.pasm # Programme principal qui va faire référence au sous programme # mess.pasm préalablement archive dans un fichier. _main: print "***** On entre dans main.\n" load_bytecode "mess.pasm" find_global P0, "_sub" invokecc P0 print "De retour dans main.\n" end coruscant:~/Langages/asmparrot chris$ parrot main.pasm ***** On entre dans main. ----------> On est dans le sous programme. De retour dans main. coruscant:~/Langages/asmparrot chris$
Nous avons vu au moment de la présentation du système qu'il était possible
de stocker les programmes opérationnels sous forme de modules précomptés.
C'est l'option -
o qui permet de le faire. Application à un petit programme
qui permet de calculer les 13 premières lignes de l'énumération de Conway
[look and say].
Tout d'abord on créée le module, celui-ci va être stocké dans un fichier
devant impérativement comporter l'extension .pbc
(Parrot byte code).
Puis, le programme obtenu est soumis à l'interpréteur.
# FIXME nbc # je ne suis pas sur de comprendre le "précomptés" ci-dessus ?
coruscant:~/Langages/asmparrot chris$ ls enum.pasm coruscant:~/Langages/asmparrot chris$ cat enum.pasm getstdout P0 set S0, "1" print P0, S0 print P0, "\n" set I1, 13 CALCUL: set I0, 0 set S4, "" substr S1, S0, 0, 1 EXPLORE: inc I0 substr S0, S0, 1 eq S0, "", FINI substr S2, S0, 0, 1 eq S1, S2, EXPLORE bsr _construire set S1, S2 set I0, 0 branch EXPLORE FINI: bsr CONSTRUIRE print P0, S4 print P0, "\n" set S0, S4 dec I1 ne I1, 0, CALCUL end _construire: set S3, I0 concat S4, S3 concat S4, S1 ret coruscant:~/Langages/asmparrot chris$ parrot -o enum.pbc enum.pasm coruscant:~/Langages/asmparrot chris$ ls enum.pasm enum.pbc coruscant:~/Langages/asmparrot chris$ parrot enum.pbc 1 11 21 1211 111221 312211 13112221 1113213211 31131211131221 13211311123113112211 11131221133112132113212221 3113112221232112111312211312113211 1321132132111213122112311311222113111221131221 11131221131211131231121113112221121321132132211331222113112211 coruscant:~/Langages/asmparrot chris$
Dans le même ordre d'idées, on peut utiliser cette fonctionnalité pour créer des modules externes précompilés qui seront sollicités au moment voulu.
coruscant:~/Langages/asmparrot chris$ ls main.pasm mess.pasm coruscant:~/Langages/asmparrot chris$ cat mess.pasm # mess.pasm # Sous-programme catalogue. .pcc_sub _sub: print "----------> On est dans sub.\n\n" returncc coruscant:~/Langages/asmparrot chris$ parrot o mess.pbc mess.pasm coruscant:~/Langages/asmparrot chris$ ls main.pasm mess.pasm mess.pbc coruscant:~/Langages/asmparrot chris$ cat main.pasm # main.pasm # Programme principal qui va faire référence au sous-programme # mess.pasm préalablement précompilé dans un fichier. _main: print "***** On entre dans main.\n" load_bytecode "mess.pbc" find_global P0, "_sub" invokecc P0 print "De retour dans main.\n" end coruscant:~/Langages/asmparrot chris$ parrot main.pasm ***** On entre dans main. ----------> On est dans sub. De retour dans main. coruscant:~/Langages/asmparrot chris$
Application à un programme Parrot qui affichera ce célèbre mantra. Il se compose de quatre fichiers sous-programme J.pasm, A.pasm, P.pasm, H.pasm et d'un programme principal japh.pasm.
coruscant:~/Langages/asmparrot chris$ cat J.pasm .pcc_sub _sub: concat S2, "Just " returncc coruscant:~/Langages/asmparrot chris$ cat A.pasm .pcc_sub _sub: concat S2, "another " returncc coruscant:~/Langages/asmparrot chris$ cat P.pasm .pcc_sub _sub: concat S2, "Perl " returncc coruscant:~/Langages/asmparrot chris$ cat H.pasm .pcc_sub _sub: concat S2, "hacker." returncc coruscant:~/Langages/asmparrot chris$ cat japh.pasm _main: set I0, 0 B: substr S0, "JAPH", I0, 1 concat S1, S0, ".pasm" load_bytecode S1 find_global P0, "_sub" invokecc P0 inc I0 lt I0, 4, B print S2 end coruscant:~/Langages/asmparrot chris$ parrot japh.pasm Just another Perl hacker. coruscant:~/Langages/asmparrot chris$
La gestion des descripteurs de fichiers ressemble fortement à celle de Perl. L'ouverture d'un fichier consiste à affecter le descripteur gestionnaire par l'intermédiaire d'un PMC puis à utiliser les fonctions d'entrée sortie qui feront référence au PMC en question.
L'ouverture d'un fichier se réalise au moyen de l'instruction :
open Px, "nom.txt", "sens"
Comme en Perl, le sens sera :
"<"
pour un fichier ouvert en lecture seule
">"
pour un fichier ouvert en écriture seule
"+<"
pour un fichier ouvert en lecture écriture
">>"
pour un fichier existant ouvert en réécriture.
coruscant:~/Langages/asmparrot chris$ cat file.pasm open P3, "dial.txt", "<" getstdout P1 LIRE: readline S0, P3 unless S0, FIN print P1, S0 branch LIRE FIN: end coruscant:~/Langages/asmparrot chris$ parrot file.pasm What about the name of the new language? GvR: Well, that was my idea. We went over lots of possible names: Chimera, Pylon, Perth, before finally coming up with Parrot. We had a few basic ideas: we wanted it to begin with "P"; it had to be something that wouldn't sound stupid on the end of /usr/bin/. LW: We also wanted the name of an animal, to represent the combination of the camel and the python. It also helps with the book covers... GvR: Eventually, I came up with Parrot after thinking about Monty Python's finest hour, the Parrot sketch. LW: It just sounded right - dynamic, colourful, exotic. I love it! coruscant:~/Langages/asmparrot chris$
L'utilisation d'un PMC pour visualiser les informations lues va nous permettre de modifier le programme afin de créer un nouveau fichier, et ce sans toucher le corps du programme. Il suffit de changer l'affectation du PMC de :
getstdout P1
en :
open P1, "nouveau.txt, ">"
Pour que les informations soient dirigées sur le fichier ainsi ouvert
coruscant:~/Langages/asmparrot chris$ ls dial.txt file.pasm coruscant:~/Langages/asmparrot chris$ cat file.pasm open P3, "dial.txt", "<" open P1, "nouveau.txt", ">" LIRE: readline S0, P3 unless S0, FIN print P1, S0 branch LIRE FIN: end coruscant:~/Langages/asmparrot chris$ parrot file.pasm coruscant:~/Langages/asmparrot chris$ ls dial.txt file.pasm nouveau.txt coruscant:~/Langages/asmparrot chris$ cat nouveau.txt What about the name of the new language? GvR: Well, that was my idea. We went over lots of possible names: Chimera, Pylon, Perth, before finally coming up with Parrot. We had a few basic ideas: we wanted it to begin with "P"; it had to be something that wouldn't sound stupid on the end of /usr/bin/. LW: We also wanted the name of an animal, to represent the combination of the camel and the python. It also helps with the book covers... GvR: Eventually, I came up with Parrot after thinking about Monty Python's finest hour, the Parrot sketch. LW: It just sounded right - dynamic, colourful, exotic. I love it! coruscant:~/Langages/asmparrot chris$
Pourquoi ne pas reprendre l'idée de l'assombrissement de $A++
[$A++] pour la
retravailler en Parrot ? Il suffirait d'en changer le nom de $A++
en
inc I0
. Le but restant toujours le même, incrémenter le registre I0
sans que cela se sache. Ecrit de manière traditionnelle, le programme se
présente sous une forme particulièrement évidente.
coruscant:~/Langages/asmparrot chris$ cat inc.pasm getstdout P1 set I1, 10 print P1, I1 # Début séquence de base inc I1 # Fin séquence print P1, I1 print P1, "\n" end coruscant:~/Langages/asmparrot chris$ parrot inc.pasm 11 coruscant:~/Langages/asmparrot chris$
Il pourrait être obscurci sous la forme :
coruscant:~/Langages/asmparrot chris$ cat inc.pasm getstdout P1 set I1, 10 print P1, I1 # Début séquence inc I1 set I2, 1 CALC: bxor I1, I2 band I3, P2, I1 if I3, FINI shl I2, 1 branch CALC FINI: # Fin séquence inc I1 print P1, I1 print P1, "\n" end coruscant:~/Langages/asmparrot chris$ parrot inc.pasm 11 coruscant:~/Langages/asmparrot chris$
L'assembleur n'a jamais eu l'audience qu'il méritait, force est de reconnaître que les dialectes qui étaient proposés n'avaient rien de particulièrement attirant. Souvent, l'apprentissage de la programmation en "langage machine" comme on avait l'habitude de dire se résumait à une longue présentation de la structure de la plate-forme suivie d'un interminable catalogue des formats d'instructions et des modes d'adressage. Cette approche n'avait rien de vraiment passionnant et surtout, imposait un éternel recommencement. La machine virtuelle qui a été présentée, aborde les choses sous un aspect totalement nouveau. Si l'ombre de Perl plane sur la manière qu'elle adopte pour représenter les choses, et, par là même, en simplifie l'utilisation, il est impossible de nier le fait qu'il s'agisse réellement d'un langage d'assemblage.
[goto] goto Perl, par Philippe Bruhat et Jean Forget, in GNU/Linux Magazine France n°72, mai 2005, http://articles.mongueurs.net/magazines/linuxmag72.html
[pmc] http://www.parrotcode.org/docs/pmc
[Hanoï] http://articles.mongueurs.net/magazines/perles/perles-hanoi.html
[look and say] http://fr.wikipedia.org/wiki/Suite_de_Conway
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.