Article publié dans Linux Magazine 97, septembre 2007.
Copyright © 2007 Christian Aperghis-Tramoni
Ce premier article sur Parrot a pour but de de décrire la machine virtuelle qui servira de support au développement du projet Perl 6. Après quelques rappels sur les notions de base nécessaires pour appréhender les réalisations en langage d'assemblage, nous étudierons plus en détail les caractéristiques propres à cette plate-forme et à sa programmation.
Le langage d'assemblage est le seul langage de programmation lisible par un homme et aussi proche que possible du langage machine. Le code étant exclusivement binaire, il est impossible à un être humain de l'approcher en l'état. Ce sera donc par l'intermédiaire de mnémoniques et de symboles spécifiques qu'il sera rendu abordable. La particularité est que, contrairement à ce qui se passe dans un langage évolué, il existe une correspondance biunivoque entre les lignes du programme source et les instructions de la machine. Un programme spécifique, l'assembleur, transformera le code source en son équivalent binaire. Afin d'en faciliter l'utilisation, il autorise la gestion d'adresses symboliques, la manipulation de modules et la définition de macros.
La structure syntaxique du langage d'assemblage est intimement liée à l'architecture du support, ainsi, contrairement aux langages évolués, il n'en existe pas d'universel multi-plateforme et une connaissance approfondie de la topologie de la machine est fondamentale pour le maîtriser et bien en comprendre l'utilisation. De cette constatation est né le principe de la machine virtuelle. Au début des années 1970, Niklaus Wirth de l'École Polytechnique de Zurich et initiateur de Pascal a défini pour son nouveau langage un interpréteur logiciel capable de s'exécuter sur différents systèmes, la P-machine [Pascal]. Cette même idée a été reprise chez Sun dans les années 1990 pour le développement de Java.
Parrot est une machine virtuelle développée par la communauté des
programmeurs Perl. Elle servira, entre autres, de support au futur
langage Perl6. Contrairement à la Java machine qui est construite
autour d'une structure de pile, Parrot est une machine à base de
registres. Cette conception influe particulièrement sur la programmation
des expressions arithmétiques en les rendant plus lisibles. Dans une machine à pile,
ce sont les éléments supérieurs qui représentent
les opérateurs implicites de toute opération. Il est
donc nécessaire de convertir une expression arithmétique en notation
polonaise post-fixée avant d'effectuer un calcul [Lukasiewicz]. Méthode
utilisée dans les années 1970 sur les anciennes calculatrices
Hewlett-Packard [HP 35].
Dans ces conditions, (A * X * X ) + (B * X) + C
doit être transformé
en AXX**BX*C++
pour pouvoir être évaluée, alors que sur une
machine registre il suffit de spécifier explicitement les sources et
la destination.
Les développeurs ont fait en sorte que la machine virtuelle soit aussi proche que possible d'une machine réelle afin de pouvoir mettre en œuvre tout ce que la technique apporte en terme d'optimisation de code généré. En fait, à l'origine, ce projet était une avrilologie.
Le premier Avril 2001, Simon Cozens annonce sur le site de O'Reilly le livre Programming Parrot [Parrot]. Cet ouvrage présente la Programmation Parrot comme étant la référence ultime des nouveaux langages de programmation dynamique. Parrot, un langage qui, in fine, fusionnera les forces vives des jumeaux de l'Open Source Perl et Python. En unissant la flexibilité de Perl avec la simplicité d'exploitation de Python, Parrot deviendra le premier langage de développement du vingt et unième siècle.
C'est l'annonce officielle que tous les utilisateurs de Perl et Python attendaient, le point culminant d'un an de collaboration entre Larry Wall et Guido van Rossum, les développeurs de Perl et de Python. À l'appui, la jaquette du livre qu'ils ont écrit en commun et l'interview exclusive de Larry et Guido qui évoquent cette excitante perspective.
Extrait :
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!
C'est ainsi que le poisson d'avril est en fin de compte devenu réalité sous forme de perroquet[Monty Python].
Il n'existe qu'un livre sur ce sujet, il est disponible chez O'Reilly et a été écrit en 2003 par Allison Randal, Dan Sugalski et Léopold Tötsch. Il détaille plusieurs aspects du projet, et, bien que ne reflettant pas tout à fait son état de développement actuel, c'est un bon ouvrage pour commencer à aborder la question.
Le site de référence du projet est <http://www.parrotcode.org/>. On peut y trouver la documentation en ligne et télécharger la dernière version Unix de la machine virtuelle http://svn.perl.org/snapshots/parrot/parrot-latest.tar.gz. L'installation par elle même ne pose aucun problème majeur.
Télécharger le fichier parrot-latest.tar.gz et décomprimer la distribution dans le répertoire parrot/.
Configurer l'installation : perl Configure.pl
Lancer la compilation : make
Tester le résultat : make test
Installer l'application : make install
Une version Windows précompilée est disponible sur le site de Jonathan Worthington [JWS].
Il ne faut jamais oublier que Parrot évoluant continuellement, certaines fonctions peuvent se transformer. Pour se maintenir informé des changements, il est bon de consulter régulièrement le site et la documentation en ligne http://www.parrotcode.org/docs/. Tous les exemples de cet article ont été testés sur un système Mac OS X.
coruscant:~/Langages/asmparrot chris$ parrot -V This is parrot version 0.4.10-devel built for ppc-darwin. Copyright (C) 2001-2007, The Perl Foundation.
Le système Parrot est structuré en plusieurs éléments fondamentaux destinés chacun à une tâche bien spécifique.
Tout commence avec un programme source qui sera transmis à l'analyseur afin d'en vérifier la syntaxe, il sera par la suite converti en une structure que le compilateur pourra manipuler avec plus de facilité.
À la sortie de l'analyseur, le programme source aura été transformé en
un Arbre Syntaxique Abstrait (AST, Abstract Syntax Tree). C'est une
configuration un peu sommaire constituée d'une suite de jetons.
Un jeton (token) est un terme générique pour désigner un terminal
qui sera
ultérieurement pris en compte par l'analyseur syntaxique.
Par exemple, une expression arithmétique de type (B * X) + C
derait
représentée sous la forme (Terme opérateur Terme) opérateur Terme
.
Au cours de cette étape sont aussi construites les tables permettant
d'associer les adresses réelles et les adresses symboliques.
Le source ainsi transformé sera pris en compte par le compilateur, et le byte code, qui pourra être directement transmis à la machine pour être exécuté, sera généré.
Pour terminer, l'interpréteur exécutera le byte code et fournira les résultats attendus.
Parrot propose aussi de créer un fichier à partir du byte code et de le stocker sur disque sous forme figée. Il peut ainsi être soumis à l'interpréteur sans repasser par les étapes d'analyse et de compilation. Cette option permettra la création et le catalogage de bibliothèques pré-compilées que l'on pourra lier au code principal grâce à l'optimiseur.
La structure de la machine virtuelle est extrêmement simple. On ne distingue que quatre types de données, les entiers, les flottants, les chaînes de caractères et les PMC. À chaque type, sera associé un jeu de 32 registres. On disposera donc de :
32 registres IV
(entiers) (I0
.. I31
)
32 registres NV
(flottants) (N0
.. N31
)
32 registres STRING
(chaînes de caractères) (S0
.. S31
)
32 registres PMC
(Parrot Magic Cookie) (P0
.. P31
)
Si les trois premieres appellations ne présentent aucune difficulté pour comprendre à quel type de donnée ils font référence, leur nom parlant par lui-même, le dernier mérite quelques explications supplémentaires sur lesquelles nous reviendrons ultérieurement. PMC signifie Parrot Magic Cookies.
La machine dispose de sept piles distinctes.
Chacun des quatre jeux de registres possède sa propre pile qui va permettre de sauvegarder et restaurer aussi rapidement que possible leur contenu. Dans ce cas, les opérations ne portent pas sur un registre particulier mais sur la famille concernée afin d'empiler ou de dépiler en une seule instruction les 32 registres. L'utilité première de ce mécanisme est de procéder à une sauvegarde très rapide lorsque cette opération s'avère nécessaire, lors des appels de fonction par exemple.
Une pile spécifique est dédiée à la sauvegarde et à la restauration des entiers utilisés intensivement par les expressions régulières.
Une pile garde la trace de toutes les informations de contrôle des gestionnaires d'exceptions.
En fin de compte, on dispose d'une pile banalisée qui va permettre le stockage des données individuelles. Elle sera utilisée pour répondre aux opérations nécessaires à un travail de programmation standard car elle permet d'empiler ou de dépiler individuellement un registre, quel qu'il soit. Il est cependant important de noter que les informations sont typées, ce qui interdit, par exemple, d'empiler une valeur numérique pour le dépiler ultérieurement dans un registre chaîne.
Pour le réaliser, nous devons disposer d'un éditeur de texte et, selon une tradition désormais bien établie, le premier programme que nous allons écrire nous affichera le classique message de bienvenue « Bonjour. ». Une fois écrit, le source sera stocké dans un fichier dont l'extension doit impérativement être .pasm (Parrot assembly).
Pour procéder à son exécution, il suffit d'ouvrir une fenêtre terminal, de se positionner dans le dossier qui contient le programme à exécuter et de le soumettre à l'interpréteur.
coruscant:~/Langages/asmparrot chris$ cat hello.pasm print "Bonjour.\n" end coruscant:~/Langages/asmparrot chris$ parrot hello.pasm Bonjour. coruscant:~/Langages/asmparrot chris$
Ce que nous venons d'écrire est, volontairement, quelque peu provocateur. Cela ne ressemble que de très loin à ce que l'on a l'habitude de voir lorsque l'on pratique l'assembleur. C'est l'avantage de Parrot, mettre à notre disposition un langage qui a toutes les caractéristiques d'un véritable assembleur sans en avoir les inconvénients. Reprenons le même programme mais, cette fois, en utilisant un registre pour mémoriser la chaîne de caractères que nous désirons imprimer.
coruscant:~/Langages/asmparrot chris$ cat hello.pasm set S1, "Bonjour." print S1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot hello.pasm Bonjour. coruscant:~/Langages/asmparrot chris$
L'instruction set
permet de stocker la valeur spécifiée dans le registre
cible. Dans notre exemple, la donnée à manipuler étant une chaîne de
caractères, elle doit être mémorisée dans l'un des 32 registres string
,
au hasard, S1
.
Ceci nous amène à définir la syntaxe générale de l'assembleur. Elle est simple et se résume en quelques principes.
Une instruction tient sur une ligne dont le format est constant.
[Etiquette] Code Opération Destinations, Source_1, ..., Source_n
Si l'opération retourne un résultat, c'est le toujours premier argument qui représente la destination et, dans certains cas, il peut simultanément être une source et la destination.
Les arguments sont indifféremment des registres ou des valeurs explicites, mais seules les sources peuvent être représentées par des constantes.
L'étiquette permet d'identifier une ligne de code à laquelle d'autres instructions pourront faire référence (rupture de séquence, branchement à un sous-programme).
Les caractères utilisables pour définir une étiquette sont :
les lettres majuscules et minuscules
les chiffres
le blanc souligné.
L'identification d'une ligne se fait au moyen d'un nom d'étiquette
suivi d'un deux points (:
).
La référence à une étiquette se fait par le nom de cette dernière, sans les deux points. Traditionnellement, et pour des raisons de clarté, on représente les étiquettes par des lettres majuscules.
Toute ligne qui commence par un dièse (#
) sera considérée comme
un commentaire et sera ignorée au moment de l'assemblage.
Il en est de même des marqueurs POD (Plain Old Documentation).
Une constante entière précédée d'un signe (+
) ou (-
) sera considérée
comme une valeur décimale. Les constantes binaires sont précédées de 0b
ou 0B
alors que les constantes hexadécimales sont précédées de 0x
ou 0X
. Leur destination doit être un registre entier (I
) .
set I1, 42 # Constante entière positive. set I2, -68 # Constante entière négative. set I3, 0x2A # Constante hexadécimale. set I4, 0b1001 # Constante binaire. set I5, -0B1101 # Constante binaire négative.
Les constantes flottantes sont elles aussi signées, c'est à dire qu'elles
peuvent être précédées d'un signe (+
) ou (-
).
La notation scientifique permet une représentation sous forme de mantisse
et d'exposant. L'exposant est précédé de la lettre e
ou E
. Son signe
est optionnel. Leur destination doit être un registre flottant (N
).
set N2, 1E6 # Constante en notation scientifique. set N3, -1.5E-2 # Constante en notation scientifique.
La syntaxe des chaînes de caractères est identique à celle utilisée dans Perl.
Une chaîne de caractères peut être encadrée par des doubles quotes ("
)
pour activer le mécanisme de substitution ou par des simples quotes ('
)
pour le désactiver. Leur destination doit être un registre chaîne (S
).
set S1, "Chaine.\n" # Le mot : Chaine suivi d'un retour chariot. set S2, "\\" # un antislash. set S3, 'L\'etau' # La chaine : L'etau set S4, 'a\n' # La lettre a, suivie d'un antislash puis d'un n.
Un registre sera référencé par son type et par son numéro. Le type est toujours une lettre majuscule :
I
pour un registre entier
N
pour un registre flottant
S
pour un registre chaîne de caractères
P
pour un registre PMC.
Le numéro est un nombre compris entre 0 et 31.
coruscant:~/Langages/asmparrot chris$ cat regs.pasm # Stockage des caractères "Bonjour." dans le registre chaîne 10. set S10, "Bonjour." # Stockage de la valeur entière 2005 dans le registre entier 17. set I17, 2005 # Stockage de la valeur réelle 3.14159 dans le registre flottant 20. set N20, 3.14159 # Impression diverses valeurs précédemment mémorisées. print S10 print "\n" print I17 print "\n" print N20 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot regs.pasm Bonjour. 2005 3.14159 coruscant:~/Langages/asmparrot chris$
Une instruction spécifique exchange
permet d'intervertir la valeur de
deux registres. Les registres doivent impérativement être de même type.
coruscant:~/Langages/asmparrot chris$ cat regs.pasm set I10, 10 set I20, 20 print "Avant exchange \n" print "Le registre 10 contient : " print I10 print "\n" print "Et le registre 20 contient : " print I20 print "\n" exchange I10, I20 print "Apres exchange \n" print "Le registre 10 contient : " print I10 print "\n" print "Et le registre 20 contient : " print I20 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot regs.pasm Avant exchange Le registre 10 contient : 10 Et le registre 20 contient : 20 Apres exchange Le registre 10 contient : 20 Et le registre 20 contient : 10 coruscant:~/Langages/asmparrot chris$
Les registres N
et I
contiennent une valeur alors que les registres
S
et P
contiennent un pointeur. On comprend aisément que la recopie
d'un registre N
ou I
porte sur la valeur qu'il avait préalablement
mémorisée.
set I0, 33 # Mettre la valeur 33 dans le registre I0 set I1, I0 # Transférer le contenu du registre I0 dans le registre I1.
Dans un premier temps la valeur 33 est stockée dans le registre entier
I0
, puis dans un second temps elle est transférée du registre
I0
dans le registre I1
. A terme, les deux contiendront la valeur 33.
Les registres chaînes (S
) travaillent sur une référence. La recopie d'un
registre dans un autre duplique la référence, et la nouvelle assignation
au registre crée un nouveau pointeur vers une nouvelle constante, l'ancienne
référence restant inchangée. En définitive, tout se passe donc comme si le
transfert agissait sur une valeur et non sur une adresse.
coruscant:~/Langages/asmparrot chris$ cat ptrs.pasm set S0, "Chaine initiale\n" set S1, S0 set S0, "Nouvelle affectation\n" print "Contenu du registre S0 : " print S0 print "Contenu du registre S1 : " print S1 end coruscant:~/Langages/asmparrot chris$ parrot ptrs.pasm Contenu du registre S0 : Nouvelle affectation Contenu du registre S1 : Chaine initiale coruscant:~/Langages/asmparrot chris$
Nous verrons plus loin que la même opération sur les registres PMC produit un résultat fondamentalement différent.
Voici un programme simple mettant en évidence quelques unes des opérations arithmétiques et des fonctions de base disponibles. Cette liste est, bien entendu, non exhaustive.
coruscant:~/Langages/asmparrot chris$ cat arith.pasm set N1, 50 set N2, 3 add N0, N1, N2 print "La somme de " print N1 print " et de " print N2 print " est egale a :" print N0 print "\n" mul N0, N1, N2 print "Le produit de " print N1 print " et de " print N2 print " est egal a :" print N0 print "\n" div N0, N1, N2 print "Le quotient de " print N1 print " par " print N2 print " est egal a :" print N0 print "\n" set N15, 99 inc N15 print "Incrementer " print N15 print " donne :" print N15 print "\n" set N12, -5.28547 abs N10, N12 print "La valeur absolue de " print N12 print " est egale a :" print N10 print "\n" set I20, 10 fact I22, I20 print "La factorielle de " print I20 print " est egale a :" print I22 print "\n" set N27, 3.14159 sin N8, N27 print "Le sinus de " print N27 print " est egal a :" print N8 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot arith.pasm La somme de 50.000000 et de 3.000000 est egale a :53.000000 Le produit de 50.000000 et de 3.000000 est egal a :150.000000 Le quotient de 50.000000 par 3.000000 est egal a :16.666667 Incrementer 100.000000 donne :100.000000 La valeur absolue de -5.285470 est egale a :5.285470 La factorielle de 10 est egale a :3628800 Le sinus de 3.141590 est egal a :0.000003 coruscant:~/Langages/asmparrot chris$
À l'instar de Perl, la machine Parrot propose de nombreux outils pour
permettre la manipulation des chaînes de caractère. Dans la majorité des
cas, les opérations concernées génèrent de nouvelles chaînes dans le
registre destination. Il est toutefois possible, dans certaines conditions,
de faire porter la modification sur la source elle-même.
Par exemple, l'instruction de concaténation concat
peut avoir deux
ou trois paramètres. Les arguments source peuvent indifféremment être des
registres string
ou des chaînes explicites. La variante à trois arguments
(Si
, Sj
, Sk
) ajoute la chaîne Sk
à la fin de la chaîne Sj
et stocke le résultat dans Si
. Dans la variante à deux arguments,
c'est le premier qui joue à la fois le rôle de source et de destination.
coruscant:~/Langages/asmparrot chris$ cat conc.pasm set S1, "Bonjour" set S2, " la Compagnie." concat S0, S1, S2 # Concaténer S1 avec S2, résultat dans S0. print S0 print "\n" concat S1, S2 # Concaténer S1 avec S2, résultat dans S1. print S1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot conc.pasm Bonjour la Compagnie. Bonjour la Compagnie. coruscant:~/Langages/asmparrot chris$
L'instruction substr
va permettre d'extraire une sous-chaîne.
Dans sa version la plus simple, elle comporte 4 arguments :
substr Sa, Sb, Ic, Id
Un registre string
(Sb
) contient une chaîne de référence.
Les valeurs numériques sont stockées dans des registres entiers (I
).
Ce sont l'indice du premier caractère de la sous-chaîne à extraire (Ic
),
et sa longueur (Id
).
Le résultat de l'opération est rangé dans un registre string
(Sa
).
coruscant:~/Langages/asmparrot chris$ cat sst.pasm set S1, "0123456789" set I1, 2 set I2, 5 substr S10, S1, I1, I2 print S10 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot sst.pasm 23456 coruscant:~/Langages/asmparrot chris$
Comme dans toutes les instructions Parrot, on peut indifféremment utiliser comme sources des références à des registres ou des valeurs explicites.
substr S10, S1, 3, 5 substr S10, "012345", 3, I3
Si l'indice du premier caractère est une valeur négative, le décompte se fera
à partir de la fin de la chaîne, le dernier caractère ayant la position -1.
L'instruction substr
, comme la fonction Perl de même nom, peut avoir
plusieurs comportements distincts.
Une forme plus développée comporte 5 arguments, dans cette configuration
le cinquième argument représente la valeur alternative par laquelle sera
remplacée la sous-chaîne qui vient d'être sélectionnée.
La modification porte sur le second argument et la chaîne qui a été supprimée
se retrouve dans le registre de destination. La valeur de remplacement peut
être vide. Dans ce cas, la sous-chaîne concernée est tout simplement supprimée.
coruscant:~/Langages/asmparrot chris$ cat sst1.pasm set S0, "abcdef" # Remplacer les deux caractères "cd" par la chaîne ---- substr S1, S0, 2, 2, "----" print S1 print "\n" print S0 print "\n" # supprimer la chaîne "----" substr S1, S0, 2, 4, "" print S1 print "\n" print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot sst1.pasm cd ab----ef ---- abef coruscant:~/Langages/asmparrot chris$
La même opération peut aussi être réalisée sans procéder à la capture de la sous-chaîne. Dans ce cas, le registre destination n'apparaît pas.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S0, "abcdef" substr S0, 2, 2, "----" print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm ab----ef coruscant:~/Langages/asmparrot chris$
L'instruction length
nous permet de connaître la longueur d'une chaîne.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S2, "0123456789" length I1, S2 print "La chaine " print S2 print " comporte " print I1 print " caracteres.\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm La chaine 0123456789 comporte 10 caracteres. coruscant:~/Langages/asmparrot chris$
Comme en Perl, il existe une instruction de multiplication de chaîne, son
code est repeat
.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S0, "x0x-" set I1, 5 repeat S1, S0, I1 print S1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm x0x-x0x-x0x-x0x-x0x- coruscant:~/Langages/asmparrot chris$
L'instruction chopn
permet de retirer un certain nombre de caractères à
la fin d'une chaîne de donnée. Elle nous sera, entre autres,
très utile pour supprimer les \n
lors d'un accès à STDIN
. Dans sa forme
de base, elle comporte deux arguments, la chaîne de référence et le nombre de
caractères à retirer.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S0, "abcdef" chopn S0, 2 print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm abcd coruscant:~/Langages/asmparrot chris$
Si le nombre spécifié dans l'instruction chopn
est négatif, il n'indique
pas le nombre de caractères que l'on souhaite retirer à la fin, mais plutôt combien
on veut en conserver au début. Il est important de noter que pour ces deux
variantes, la modification porte sur la source elle-même.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S0, "abcdef" chopn S0, -3 print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm abc coruscant:~/Langages/asmparrot chris$
Une variante à trois arguments permet de conserver intacte la chaîne d'origine. Le résultat de l'opération est alors stocké dans un registre explicitement spécifié.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S0, "abcdef" # Retirer les deux derniers caractères. chopn S1, S0, 2 print S1 print "\n" # Conserver les trois premiers caractères. chopn S2, S0, -3 print S2 print "\n" # La chaîne origine n'a pas été modifiée. print S0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm abcd abc abcdef coruscant:~/Langages/asmparrot chris$
L'instruction index
a les mêmes fonctionnalités que son homonyme
en Perl, elle permet de repérer l'emplacement dans une chaîne de référence
d'une sous-chaîne donnée.
Si cette dernière n'apparaît pas, la valeur retournée sera -1.
coruscant:~/Langages/asmparrot chris$ cat ind.pasm set S0, "abcdef" index I0, S0, "cd" print I0 print "\n" index I1, S0, "gh" print I1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot ind.pasm 2 -1 coruscant:~/Langages/asmparrot chris$
On dispose de deux opérations qui convertissent une valeur numérique en
caractère et inversement.
L'instruction chr
qui a comme registre destination un registre S
et
comme source un registre I
, transforme l'entier représentatif d'un code
ASCII en son caractère correspondant. L'instruction ord
effectue
l'opération inverse.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm chr S0, 65 # 65 est le code ASCII décimal de "A" print S0 print "\n" ord I0, "x" # Le code ASCII de "x" est 120 décimal. print I0 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm A 120 coruscant:~/Langages/asmparrot chris$
Une variante à trois arguments de l'instruction ord
permet de désigner un
caractère particulier dans une chaîne par l'intermédiaire d'un entier.
L'emplacement est compté à partir du début en commençant à zéro si le nombre
est positif, il est compté en partant de la fin et en commençant à un si
le nombre est négatif.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm ord I0, "ABCDEF", 2 # On convertit le caractère C. print I0 print "\n" ord I1, "ABCDEF", -3 # On convertit le caractère D. print I1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm 67 68 coruscant:~/Langages/asmparrot chris$
Le simple fait de transférer une valeur d'un registre vers un autre effectue le changement de type de celui correspondant au registre d'origine vers celui correspondant au registre de destination.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm set S1, "5" # Caractère. set S2, 10 # Caractère set N1, S1 # Transfert vers un registre flottant set N2, S2 # Transfert vers un registre flottant mul N1, N2 # Calcul en flottant print "Calcul en flottant :" print N1 print "\n" set I1, N1 # Conversion en entier print "Conversion en entier :" print I1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm Calcul en flottant :50.000000 Conversion en entier :50 coruscant:~/Langages/asmparrot chris$
Un PMC définit un type qui se comporte de manière particulière. Il va utiliser une structure particulière appelée v-table pour référencer des méthodes spécifiques. De plus, des fonctions appropriées permettent de remplacer l'implémentation de la classe de base par une séquence définie par l'utilisateur. Pour simplifier, un registre PMC contient un pointeur vers la v-table qui est elle-même une liste de pointeurs vers des fonctions dont le code réalise l'opération voulue pour le PMC concerné. Ainsi, toute instruction qui fait référence à un PMC, utilise la v-table qui lui a été associée pour accéder à la fonction appropriée. Essentiellement, les PMC héritent d'une classe de base et exécutent les opérations demandées en accord avec les caractéristiques spécifiques inhérentes aux structures concernés. Nous détaillerons tout ceci lorsque nous aborderons la programmation objet. Par exemple, pour deux langages distincts, une fonction équivalente peut occasionner deux comportements fondamentalement différents.
Pour illustrer ceci, considérons deux programmes équivalents, le premier écrit en Perl, le second en Python et comparons les résultats obtenus.
coruscant:~/Langages/perl chris$ cat ch.pl #!/usr/bin/perl $x = "ABCDEF"; $z = ++$x; print "$z\n"; coruscant:~/Langages/perl chris$ ch.pl ABCDEG coruscant:~/Langages/python chris$ cat ch.py #!/usr/bin/python x = "ABCDEF" z = ++x print z, "\n" coruscant:~/Langages/python chris$ ch.py Traceback (most recent call last): File "ess.py", line 4, in ? z = ++x; TypeError: bad operand type for unary + coruscant:~/Langages/python chris$
C'est pour tenir compte de cette singularité, que l'assembleur disposera
à terme de deux PMC .PerlString
et .PythonString
afin de calquer le
comportement des chaînes de caractères sur le comportement du langage qui
devra être compilé.
À noter par ailleurs que les PMC ne se limitent pas aux méthodes qui
exécutent des opérations spécifiques, ils sont utilisés chaque fois que
le comportement d'un objet ou d'une construction diffère en fonction de
l'environnement qui l'utilise. D'un autre côté, les PMC permettent de travailler
sur les structures de données, qui portent ici le nom d'agrégats.
Le nombre de PMC installés peut varier d'une version à l'autre. En connaître la liste peut se révéler utile. Voici un petit programme qui permet de la générer. Dans la version utilisée pour les tests, son exécution, dans la version actuelle, génère 81 lignes, correspondant aux 81 PMC existants, c'est pourquoi j'en ai volontairement tronqué les résultats.
coruscant:~/Langages/asmparrot chris$ cat listePMC.pasm set I0, 1 BOUCLE: # Test si le numéro correspond à un type de PMC valide. valid_type I1, I0 eq I1, 0, FIN # Récupération du nom du PMC correspondant au numéro. typeof S0, I0 # Impression des résultats. set S1, I0 concat S1, " ", S1 substr S2, S1, -2 print "Nom du PMC numero " print S2 print " : " print S0 print "\n" inc I0 branch BOUCLE FIN: end coruscant:~/Langages/asmparrot chris$ parrot listePMC.pasm Nom du PMC numero 1 : Null Nom du PMC numero 2 : Env Nom du PMC numero 3 : Key Nom du PMC numero 4 : Random * * * * * * Nom du PMC numero 66 : ParrotRunningThread Nom du PMC numero 67 : PCCMETHOD_Test Nom du PMC numero 68 : ResizableBooleanArray Nom du PMC numero 69 : ResizableFloatArray Nom du PMC numero 70 : ResizableIntegerArray Nom du PMC numero 71 : ResizablePMCArray Nom du PMC numero 72 : ResizableStringArray * * * Nom du PMC numero 79 : STMVar Nom du PMC numero 80 : Super Nom du PMC numero 81 : Undef coruscant:~/Langages/asmparrot chris$
Il a été précisé que les registres PMC stockent un pointeur. C'est ainsi
que le code opération new
créée une instance de la classe sollicitée.
La v-table de la classe en question permet de spécifier comment le PMC
pointé par le registre concerné doit réaliser une opération spécifique
qui le concerne. Considérons le programme ci-dessous.
coruscant:~/Langages/asmparrot chris$ cat ptrs.pasm new P1, .String set P1, "Chaine un\n" set P2, P1 set P2, "Chaine deux\n" print "Le PMC P1 pointe sur la chaine : " print P1 print "Le PMC P2 pointe sur la chaine : " print P2 end coruscant:~/Langages/asmparrot chris$ parrot ptrs.pasm Le PMC P1 pointe sur la chaine : Chaine deux Le PMC P2 pointe sur la chaine : Chaine deux coruscant:~/Langages/asmparrot chris$
Après la création de l'instance .String
dans le registre P1
, la
première instruction set
fait appel à la méthode set_string_native
(référencée en P1
) qui affecte la chaîne "Chaine un\n"
au PMC
en question. L'instruction set P2, P1
se contente de recopier le
pointeur contenu en P1
vers P2
, c'est ainsi que les deux registres
pointent sur la même structure. Dans ces conditions, l'affectation
d'une nouvelle valeur à P2 affecte le PMC référencé à la fois par P1
et
par P2
.
Si on désire réellement dupliquer le PMC, c'est à dire créer une nouvelle
instance d'un PMC donné, il est
nécessaire de le cloner. On dispose pour réaliser cette opération de
l'instruction clone P2, P1
.
Nous pouvons reprendre le programme ci-dessus en tenant compte de cette remarque.
coruscant:~/Langages/asmparrot chris$ cat ptrs.pasm new P1, .String set P1, "Chaine un\n" clone P2, P1 set P2, "Chaine deux\n" print "Le PMC P1 pointe sur la chaine : " print P1 print "Le PMC P2 pointe sur la chaine : " print P2 end coruscant:~/Langages/asmparrot chris$ parrot ptrs.pasm Le PMC P1 pointe sur la chaine : Chaine un Le PMC P2 pointe sur la chaine : Chaine deux coruscant:~/Langages/asmparrot chris$
La lecture d'information à partir du clavier se fait sous la forme d'une chaîne de caractères. On dispose de deux instructions différentes pour effectuer cette opération. La première ne fait pas appel à un PMC :
read S1, I1
Elle effectue la lecture d'une chaîne de caractères dans le registre
S1
, la longueur maximum de la chaîne est spécifiée par le nombre
indique en I1
.
coruscant:~/Langages/asmparrot chris$ cat lec.pasm getstdin P0 set I1, 5 read S1, I1 print S1 print "\n" end coruscant:~/Langages/asmparrot chris$ parrot lec.pasm 12 12 coruscant:~/Langages/asmparrot chris$ parrot lec.pasm 123456789 12345 coruscant:~/Langages/asmparrot chris$
Il existe une autre possibilité pour accéder à une information, c'est
l'instruction readline
qui attend comme argument un PMC. Si ce dernier
est getstdin
, c'est l'entrée standard qui sera concernée. L'opération
réalisée est la lecture d'une ligne dans un registre chaîne, la ligne
étant terminée par un "\n"
. Si besoin est, ce dernier peut être retiré
au moyen de l'instruction chopn
. Nous verrons ultérieurement que le PMC
peut aussi contenir la référence d'un gestionnaire de fichier.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm getstdin P0 readline S1, P0 print "Chaine lue : " print S1 end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm Bonjour Chaine lue : Bonjour coruscant:~/Langages/asmparrot chris$
L'instruction print
dont nous avons jusqu'à présent utilisée la version
simple, peut elle aussi passer par l'intermédiaire du PMC getstdout
.
coruscant:~/Langages/asmparrot chris$ cat exo.pasm getstdin P0 getstdout P1 readline S1, P0 print P1, "Chaine lue : " print P1, S1 end coruscant:~/Langages/asmparrot chris$ parrot exo.pasm Bonjour Chaine lue : Bonjour coruscant:~/Langages/asmparrot chris$
L'utilisation de PMC pour effectuer les entrées sorties se justifie pleinement lorsqu'on désire pouvoir changer facilement de gestionnaire d'entrée. Cette possibilité sera elle aussi développée dans la partie consacrée aux fichiers.
Cette approche du langage machine peut sembler quelque peu surprenante à ceux qui utilisent de manière courante un assembleur, elle a toutefois un grand mérite, celui de permettre l'écriture facile et rapide de programmes qui auraient demandé beaucoup de conception et de travail sur un support réel. La caractéristique principale est de mettre à la disposition des utilisateurs un jeu d'instruction extrêmement varié et particulièrement développé. Cet aspect du problème a été mis en évidence dans cette première présentation. Par la suite, nous en détaillerons d'autres caractéristiques et nous verrons comment gérer sans difficulté des structures plus complexes.
[Pascal] http://www.infeig.unige.ch/support/cpil/lect/mvp/
[Lukasiewicz] Jan Lukasiewicz http://www-groups.mcs.st-and.ac.uk/history/Biographies/Lukasiewicz.html
[HP 35]Calculatrices Reverse Polish Notation pour Linux http://www.linuxfocus.org/Francais/January2004/article319.shtml
[Parrot] Programming Parrot - http://www.perl.com/pub/a/2001/04/01/parrot.htm
[Monty Python] The Parrot Sketch http://video.google.com/videoplay?docid=5775099474392087542
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.