Article publié dans Linux Magazine 99, novembre 2007.
Copyright © 2007 Christian Aperghis-Tramoni
Lors des deux premiers articles, nous avons détaillé le fonctionnement des instructions de base de la machine virtuelle Parrot. Nous allons maintenant nous intéresser davantage aux particularités du langage d'assemblage et découvrir que l'on peut faire de la programmation orientée objet en assembleur.
Une macro-instruction [Macro] est un moyen de définir des opérations non répertoriées par le langage de base de la machine à partir du jeu d'instructions réellement disponible. Plus spécifiquement, elle peut être vue comme la création de toutes pièces par le programmeur d'un nouveau code opération dont l'exécution entraînera celle de plusieurs commandes de base. Le comportement est toutefois fondamentalement différent de celui d'un sous-programme, une macro-instruction doit être considérée comme l'association d'un texte de substitution au code qui l'identifie, et ceci chaque fois que nécessaire. De plus, une macro-instruction pourra s'adapter à son environnement au moyen des paramètres syntaxiques qui lui seront transmis.
Il faut par ailleurs noter que cette technique de programmation n'est pas
l'apanage de l'assembleur. En langage C, define
permet d'introduire
une macro-définition. Si on désire créer une macro-instruction permettant
de déterminer la valeur absolue d'un nombre, il sera nécessaire de la
déclarer au préalable :
#define abs(x) ((x) < 0 ? - (x) : (x))
Ensuite, chaque fois que le programme y fera référence sous la forme
abs(exp)
cette construction sera développée comme :
((exp) < 0 ? - (exp) : (exp))
La définition d'une macro instruction commence par le mot clé .macro
suivi
du nom qui lui servira de référence, puis, éventuellement, d'une liste de
paramètres.
Commençons par définir une opération simple. À l'image de l'instruction
readline
qui lit une chaîne de caractères terminée par un \n
, nous
allons créer une macro-instruction printline
qui affiche une valeur
suivie d'un \n
.
chris$ cat macro.pasm .macro printline (A) print .A print "\n" .endm # Affichage d'un entier. set I1, 12513 .printline (I1) # Affichage d'un réel. set N1, 3.14159 .printline (N1) # Affichage d'une chaîne. set S1, "Bonjour tout le monde." .printline (S1) end chris$ parrot macro.pasm 12513 3.141590 Bonjour tout le monde.. chris$
Cette séquence amène quelques commentaires.
La première ligne nous permet de définir le nom et la liste d'appel de
notre macro-instruction.
Cette définition débute par la directive .macro
suivie du nom par
lequel elle sera référencée, ici printline
.
Le paramètre qui lui est passé A
représente la valeur que l'on désire
afficher. Comme partout dans Parrot, ce peut être une référence à un
registre ou une valeur explicite.
Après cette ligne, il est nécessaire de définir le corps de la macro-instruction,
ici il se compose de deux lignes, l'affichage de la donnée qui lui a été
transmise suivie d'un \n
.
La séquence doit se terminer par la directive .endm
.
Dans le programme, on y fait référence par l'intermédiaire de son nom
précédé d'un point : .printline
, suivi du paramètre.
Si on examinait un peu plus en détail ce qui a été fait, on constaterait qu'un prétraitement syntaxique aurait substitué à chaque appel les deux lignes qu'il représente. Le programme final soumis au système est, en définitive, le suivant :
# Impression d'un entier. set I1, 12513 print I1 print "\n" # Impression d'un réel. set N1, 3.14159 print N1 print "\n" # Impression d'une chaîne. set S1, "Bonjour tout le monde." print S1 print "\n" end
Un paramètre peut représenter pratiquement n'importe quel objet, un registre
(I
, N
, S
ou P
), un label ou une constante.
Par exemple, pour accéder à un fichier.
chris$ cat macro.pasm .macro lire (PMC) LIRE: readline S0, .PMC unless S0, FIN print P1, S0 branch LIRE FIN: .endm getstdout P1 open P3, "dial.txt", "<" .lire (P3) end chris$ parrot macro.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! chris$
Ou, lorsqu'une macro doit gérer plusieurs sorties.
chris$ cat macro.pasm .macro printline (PMC, A) print .PMC, .A print "\n" .endm .macro quot (DIVID, DIVIS, BON, MAUVAIS) eq .DIVIS, 0, .MAUVAIS div I2, .DIVID, .DIVIS branch .BON .endm getstdin P0 getstdout P1 readline S1, P0 set I0, S1 readline S1, P0 set I1, S1 .quot (I0, I1, OK, PASOK) OK: print P1, "Le quotient de " print P1, I0 print P1, " par " print P1, I1 print P1, " est egal a " .printline (P1, I2) branch SUITE PASOK: .printline (P1, "Erreur, division par zero.") SUITE: end coruscant:~/Langages/asmparrot chris$ parrot macro.pasm 25 5 Le quotient de 25 par 5 est egal a 5 chris $parrot macro.pasm 25 0 Erreur, division par zero. chris$
Explication. La macro-instruction .quot
comporte quatre paramètres, le
dividende, le diviseur, l'adresse où aller si tout se passe bien et l'adresse
concernée en cas d'erreur.
Lors de la première exécution, le diviseur étant différent de zéro, il n'y
a aucun problème, l'opération est réalisable, la sortie se fait vers l'adresse
transmise par l'intermédiaire du paramètre BON
soit OK
. La seconde fois
que le programme s'exécute, le diviseur est à zéro, cette impossibilité est
détectée par le test et la sortie se fait vers l'adresse PASOK
transmise
via le paramètre MAUVAIS
.
Lorsque nous avons écrit la macro lire
, nous avons dû déclarer deux
adresses LIRE:
et FIN:
. Ce qui ne nous avait posé aucun problème
lors de l'exécution. Si nous reprenons cette macro dans le programme
suivant :
chris$ cat macro.pasm .macro lire (PMC) LIRE: readline S0, .PMC unless S0, FIN print P1, S0 branch LIRE FIN: .endm getstdout P1 open P3, "dial1.txt", "<" .lire (P3) open P4, "dial2.txt", "<" .lire (P4) end chris$ parrot macro.pasm error:imcc:Label 'LIRE' already defined in macro '.lire' line 2 included from 'st.pasm' line 1 chris$
L'interpréteur détecte une erreur syntaxique. En effet, l'opération de substitution a produit le résultat suivant :
getstdout P1 open P3, "dial1.txt", "<" LIRE: readline S0, P3 unless S0, FIN print P1, S0 branch LIRE FIN: open P4, "dial2.txt", "<" LIRE: readline S0, P4 unless S0, FIN print P1, S0 branch LIRE FIN: end
Qui met bien en évidence la duplication des labels ainsi que nous le précise le message d'erreur.
Pour parer à ce problème, chaque fois qu'une macro qui contient une référence
est susceptible d'être développée plusieurs fois, il est nécessaire de déclarer
tous ses labels comme étant locaux au moyen de la directive .local
.
D'une manière générale, comme la notion même de macro-instruction sous-entend de
multiples utilisations, il est bon de prendre l'habitude de le faire de manière
systématique.
chris$ cat macro.pasm .macro lire (PMC) .local $LIRE: readline S0, .PMC unless S0, .$FIN print P1, S0 branch .$LIRE .local $FIN: close .PMC .endm getstdout P1 print P1, "** Fichier 1 **\n" open P3, "dial1.txt", "<" .lire (P3) print P1, "** Fichier 2 **\n" open P4, "dial2.txt", "<" .lire (P4) end chris$ parrot macro.pasm ** Fichier 1 ** 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/. ** Fichier 2 ** 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! chris$
Il est maintenant possible de faire référence à cette macro autant de fois que nécessaire sans que la moindre erreur soit générée.
Parrot prédéfinit un certain nombre de macros, nous en avons utilisé
quelques-unes, par exemple include
dont le paramètre est une chaîne de
caractères va permettre d'insérer dans le programme le contenu littéral du
fichier spécifié.
Une macro est aussi disponible pour déclarer des constantes. Elle est de
la forme : .constant nom valeur
. Cette directive ne générera pas de
ligne de code mais uniquement une équivalence entre le nom et la valeur.
Chaque fois que le nom en question apparaîtra dans le source du programme,
on lui substituera la valeur qu'il représente.
chris$ cat cons.pasm .macro printline (PMC, A) print .PMC, .A print "\n" .endm .constant Registre_Entier1 I1 .constant Registre_Reel1 N1 .constant Registre_Chaine1 S1 .constant Registre_PMC1 P1 .constant Dix 10 .constant Cent 100 .constant Hello "Bonjour tout le monde\n" set .Registre_Entier1, .Dix set .Registre_Reel1, .Cent set .Registre_Chaine1, .Hello getstdout .Registre_PMC1 .printline (.Registre_PMC1, .Registre_Entier1) .printline (.Registre_PMC1, .Registre_Reel1) .printline (.Registre_PMC1, .Registre_Chaine1) end chris$ parrot cons.pasm 10 100.000000 Bonjour tout le monde chris$
Ainsi, rien n'empêche de créer un fichier qui va contenir toutes les
déclarations de constantes possibles et imaginables qui sera inséré dans
le programme au moyen de la directive .include
et qui permettra de faire
référence aux diverses valeurs par le nom qui leur aura été affecté.
chris$ cat constantes.txt .constant Zero 0 .constant Euler 0.57721 .constant Un 1 .constant Apery 1.20205 .constant Racine_Deux 1.41421 .constant Mills 1.30637 .constant Nombre_d_or 1.61803 .constant Niven 1.70521 .constant Racine_Trois 1.73205 .constant Feigenbaum 2.50290 .constant Sierpinski 2.58498 .constant e 2.71828 .constant pi 3.14159 chris$ cat cons.pasm .macro printline (PMC, A) print .PMC, .A print "\n" .endm .include "constantes.txt" getstdout P1 print P1, "Racine de deux : " .printline (P1, .Racine_Deux) print P1, "Racine de trois : " .printline (P1, .Racine_Trois) print P1, "Valeur de e : " .printline (P1, .e) print P1, "Valeur de pi : " .printline (P1, .pi) end chris$ parrot cons.pasm Racine de deux : 1.414210 Racine de trois : 1.732050 Valeur de e : 2.718280 Valeur de pi : 3.141590 chris$
Le programme suivant permet de convertir un nombre décimal en numération romaine [Romain].
chris$ cat romains.pasm # Declaration des macros. .macro set_liste (PMC, CHAINE) new .PMC, .ResizablePMCArray split .PMC, ",", .CHAINE .endm .macro print_line (LIGNE) print P1, .LIGNE print P1, "\n" .endm .macro lire (REGISTRE) .local $LIRE: .print_line ("Nombre de depart?") readline S0, P0 set .REGISTRE, S0 lt I0, 4000, .$SORTIE .print_line ("Le nombre doit etre inferieur a 4000.") branch .$LIRE .local $SORTIE: .endm .macro convertir (A, R) set I1, 0 # Indice d'exploration des listes. .local $CALCUL: set I2, P3[I1] # Récupération diviseur. div I3, .A, I2 # Occurrences du chiffre romain. set S2, P4[I1] # Récupération de chiffre romain. repeat S2, S2, I3 concat .R, S2 # Construction du nombre romain. mod .A, .A, I2 # Reste de la division. inc I1 if .A, .$CALCUL .endm .macro question (OUI) .print_line ("Un autre calcul (o/n) ?") readline S0, P0 eq S0, "o\n", .OUI .endm # Déclaration et initialisation liste chiffres arabes. .set_liste (P3, "1000,900,500,400,100,90,50,40,10,9,5,4,1") # Déclaration et initialisation liste chiffres romains .set_liste (P4, "M,CM,D,CD,C,XC,L,XL,X,IX,V,IV,I") getstdin P0 # Entrée standard. getstdout P1 # Sortie standard. LEC: .lire (I0) CONV: set S1, "" print P1, "Le nombre " print P1, I0 print " s'ecrit : " .convertir (I0, S1) .print_line (S1) .question (LEC) end chris$ parrot romains.pasm Nombre de depart? 666 Le nombre 666 s'ecrit : DCLXVI Un autre calcul (o/n) ? o Nombre de depart? 3333 Le nombre 3333 s'ecrit : MMMCCCXXXIII Un autre calcul (o/n) ? n chris$
La première macro, set_liste
permet de déclarer et d'initialiser les
listes utilisées. Elle prend comme paramètre un registre PMC et une chaîne
représentative de valeurs d'initialisation séparées par des virgules.
La liste sera déclarée par l'intermédiaire du PMC et initialisée avec
les valeurs correspondantes.
La seconde macro print_line
a déjà été vue.
La troisième macro lire
lit la donnée à convertir et vérifie qu'elle
est inférieure à 4000. En effet, la numération romaine n'écrit pas au-delà
de cette valeur.
La quatrième macro convertir
effectue la conversion du nombre, la valeur
d'origine est contenue dans le registre entier passé par l'intermédiaire
du premier paramètre et le résultat est récupéré dans le registre chaîne
passé par l'intermédiaire du second argument.
Enfin, la dernière macro question
demande si on souhaite convertir une
nouvelle valeur ou terminer le programme.
En fin de compte, le programme se résume à quelques instructions et des appels de macro.
Il peut sembler surprenant de penser que l'on puisse écrire des applications orientées objet [poo] en assembleur. En fait, il existe dans Parrot des fonctions permettant ce type d'approche. La longueur des programmes ne me permettra pas d'écrire comme je l'ai fait jusqu'à présent des ensembles complets. Je me contenterai de définir les diverses opérations afin que ceux qui désireraient approfondir le sujet puissent le faire sans trop de difficulté.
C'est bien entendu par l'intermédiaire d'un PMC que sera déclarée une
classe. La directive newclass
permet de le faire et d'en affecter
la référence à un PMC. Il faut garder en mémoire le fait que les PMC
gèrent des références vers des v-tables pour comprendre les subtilités
de la programmation objet en Parrot.
newclass P0, "Humain"
Un nouvel objet référencé Humain
est créé. Le PMC P0
pointe alors vers
une v-table qui va en contenir les diverses caractéristiques. En définitive,
la création d'une classe revient à créer une nouvelle entrée dans la liste
des PMC
chris$ parrot romains.pasm print "Avant la creation de l'objet.\n" bsr LISTEPMC newclass P10, "Humain" print "\n---------------------------\n\n" print "Apres la creation de l'objet.\n" bsr LISTEPMC end LISTEPMC: set I0, 1 BOUCLE: # Le numéro correspond à un type de PMC valide. valid_type I1, I0 eq I1, 0, FIN # Récupération du nom du PMC. typeof S0, I0 # Edition 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: ret chris$ Avant la creation de l'objet. Nom du PMC numero 1 : Null Nom du PMC numero 2 : Env * * * * * Nom du PMC numero 80 : Super Nom du PMC numero 81 : Undef --------------------------- Apres la creation de l'objet. Nom du PMC numero 1 : Null Nom du PMC numero 2 : Env * * * * * Nom du PMC numero 80 : Super Nom du PMC numero 81 : Undef Nom du PMC numero 82 : ParrotClass chris$
La classe qui vient d'être créée est vierge de toute caractéristique. Il est possible de définir des attributs qui lui seront rattachés et desquels hériteront tous ses descendants (sous-classes ou objets).
Un attribut est défini par son nom et par la classe à laquelle il est rattaché.
addattribute P0, "Prenom" addattribute P0, "Nom" addattribute P0, "Age"
Nous dirons par exemple qu'un être humain, quel qu'il soit, comporte trois caractéristiques, son prénom, son nom et son âge.
Les trois instructions que nous venons d'écrire spécifient cet état de chose.
Les attributs Prenom
, Nom
et Age
sont attachés à la classe Humain
et tout ce qui sera défini à partir d'elle héritera de ces caractéristiques.
L'instruction
subclass Pi, Pj, "Nom_Classe"
Permet de définir une sous-classe appelée Nom_Classe
rattachée à
celle dont la référence se trouve dans le PMC Pj
et qui sera
elle-même définie par le PMC Pi
.
Dans notre exemple, nous pouvons procéder aux déclarations :
subclass P1, P0, "Homme" subclass P2, P0, "Femme" subclass P3, P0, "Enfant"
Qui nous permettent de créer dans la classe principale Humain
(P0
) les trois
sous-classes Homme
, Femme
et Enfant
définies respectivement par
les trois PMC P1
, P2
et P3
et qui hériteront par là même des
attributs Prenom
, Nom
et Age
de la classe Humain
. Il est
possible de connaître le nom d'une classe au moyen de l'instruction
classname
.
classname SO, P1
Va nous retourner dans le registre SO
le nom de la classe correspondant au
PMC P1
soit Homme
.
Pour instancier un objet dans une classe donnée, il faut, au préalable, déterminer quel est le numéro qui a été affecté à la classe en question lors de sa déclaration. Le code opération pour réaliser cette fonction est
find_type Ix, "Nom_de_la_classe"
L'entier correspondant à la référence de la classe se trouve dans le registre spécifié, c'est lui qui va nous permettre d'instancier un objet.
new Px, Ix
Si nous reprenons l'exemple que nous avons décrit jusqu'à présent, nous
pouvons créer un objet Homme
au moyen de la séquence.
find_type I0, "Homme" new P10, I0
La référence de l'objet qui vient d'être créé se trouve dans le PMC P10
.
Nous verrons aussi plus loin que l'opération new
a aussi pour effet de
vérifier l'existence d'une méthode spécifique __init
et de l'exécuter
si c'est le cas.
L'objet vient d'hériter des caractéristiques de la classe dans laquelle il a
été créé, nous pouvons donc maintenant leur affecter des valeurs. C'est
l'instruction setattribute
qui réalise cette opération.
setattribute Px, A, Pz
Elle a pour effet de positionner l'attribut A
de l'objet Px
à la
valeur définie par Pz
. L'attribut concerné peut être spécifié de
plusieurs manières distinctes.
Par son nom s'il n'y a pas d'ambiguïté : "Prenom"
Par sa hiérarchie "Humain\00Prenom"
Par son numéro d'ordre dans la déclaration Ix
Voyons les différentes manières de procéder pour affecter un prénom à
l'homme qui vient d'être créé et dont le descripteur est défini par P10
.
new P5, .String set P5, "Christian" setattribute P10, "Prenom", P5 new P5, .String set P5, "Christian" setattribute P10, "Humain\00Prenom", P5 new P5, .String set P5, "Christian" classoffset I1, P10, "Humain" setattribute P10, I1, P5
Dans la troisième méthode l'instruction
classoffset I1, P10, "Humain"
Va permettre de récupérer le numéro d'ordre du premier attribut que la
classe Humain
a transmis en héritage à l'objet qui a été créé et dont
le descripteur se trouve dans le PMC P10
. Si on utilise cette procédure,
il faut penser à incrémenter le numéro d'ordre, I1
dans notre cas,
avant l'affectation de l'attribut suivant. Nous pouvons voir maintenant
comment récupérer les valeurs qui viennent d'être positionnées. C'est
l'instruction getattribute
qui va nous le permettre.
getattribute Px, Py, A
Qui a pour effet de récupérer la valeur de l'attribut A
de l'objet Py
dans Pz
. Les manières de spécifier l'attribut sont identiques à celle
de l'instruction setattribute
.
Le programme suivant reprend les divers points qui ont été vus. Il permet de créer plusieurs objets et d'en stocker les définitions dans une liste de PMC (.ResizablePMCArray) en vue d'une exploitation ultérieure.
Les divers morceaux le composant seront détaillés les uns après les autres. Il suffira de les réunir pour obtenir le programme complet.
Tout d'abord, les macros. La première affiche trois données sur la même ligne, la seconde permet de lire la valeur requise dans la destination souhaitée.
.macro print_line (A1, A2, A3) print P1, .A1 print P1, .A2 print P1, .A3 .endm .macro lecture (DESTINATION) readline .DESTINATION, P0 chopn .DESTINATION, 1 .endm
On déclare maintenant les PMC. C'est dans une liste de PMC référencée par P4, que seront mémorisés les divers objets au fur et à mesure de leur création.
getstdin P0 getstdout P1 new P4, .ResizablePMCArray
Comme vu précédemment, on déclare la classe de base Humain
et ses
attributs, puis la sous-classe Homme
qui lui est rattachée et qui va
hériter de ses attributs.
# Déclaration de la classe Humain newclass P10, "Humain" # Attributs de l'humain. addattribute P10, "Prenom" addattribute P10, "Nom" addattribute P10, "Age" # Déclaration de la sous-classe Homme subclass P11, P10, "Homme"
À partir de maintenant, le programme entre dans un mode conversationnel. Un nouvel objet possédant ses propres attributs sera créé chaque fois que nécessaire et sa référence sera conservée dans la liste créée pour la circonstance.
# Récupération du type de "Homme" find_type I0, "Homme" # Lecture informations. print P1, "Prenom,Nom,Age ou ligne vide pour terminer\n" ENTREE: .lecture (S1) unless S1, EDITION new P2, .ResizableStringArray # Découpage de la chaîne lue en ses composantes. split P2, ",", S1 # Nouvel objet de la sous-classe "Homme" new P20, I0 # PMC intermédiaire pour affecter les attributs. new P30, .String set P30, P2[0] setattribute P20, "Humain\x00Prenom", P30 set P30, P2[1] setattribute P20, "Humain\x00Nom", P30 set P30, P2[2] setattribute P20, "Humain\x00Age", P30 push P4, P20 branch ENTREE
À la fin des créations, il ne reste plus qu'à explorer la liste pour récupérer l'une après l'autre les références mémorisées afin de les éditer.
EDITION: set I0, P4 .print_line ("Nombre de creations : ", I0, "\n") set I1, 0 SUIVANT: eq I1, I0, FINI .print_line ("Homme numero ", I1, " : ") set P25, P4[I1] classname S1, P11 getattribute P9, P25, "Humain\x00Prenom" .print_line ("Prenom : ", P9, " - ") getattribute P9, P25, "Humain\x00Nom" .print_line ("Nom : ", P9, " - ") getattribute P9, P25, "Humain\x00Age" .print_line ("Age : ", P9, "\n") inc I1 branch SUIVANT FINI: end
Lorsque l'ensemble des morceaux du programme sont mis bout à bout, l'exécution donne le résultat suivant.
chris$ parrot homme.pasm Prenom,Nom,Age ou ligne vide pour terminer Christian,Aperghis-Tramoni,59 Sebastien,Aperghis-Tramoni,29 Nombre de creations : 2 Homme numero 0 : Prenom : Christian - Nom : Aperghis-Tramoni - Age : 59 Homme numero 1 : Prenom : Sebastien - Nom : Aperghis-Tramoni - Age : 29 chris$
Tous les objets héritent d'un ensemble de fonctions prédéfinies par le
PMC ParrotObject
. Les noms de ces méthodes commencent systématiquement
par un double blanc souligné (__
). Alors qu'une méthode définie par l'usager
doit être appelée de manière explicite, celles qui sont prédéfinies dans
la v-table sont implicitement exécutées en fonction du contexte.
Ainsi que nous l'avons précédemment évoqué, la création d'un
nouvel objet entraîne automatiquement l'activation de la méthode __init
si cette dernière a été définie pour la classe considérée. On peut récupérer
les paramètres qui sont automatiquement transmis au moyen de l'instruction
get_params
. Dans le cas d'une création la liste ne comporte qu'une
valeur, le PMC correspondant à l'objet qui vient d'être créé.
chris$ cat init.pasm # Déclaration de la classe Humain newclass P10, "Humain" # Attributs de l'humain. addattribute P10, "Prenom" # Déclaration de la sous-classe Homme subclass P11, P10, "Homme" # Recuperation du type de "Homme" find_type I0, "Homme" # Nouvel objet de la sous-classe "Homme" new P20, I0 end .namespace ["Humain"] .pcc_sub __init: get_params "(0)", P31 classname S0, P31 print "Un nouvel objet de type " print S0 print " vient de se creer.\n" returncc chris$ parrot init.pasm Un nouvel objet de type Homme vient de se creer. chris$
Voyons un autre exemple qui va lui aussi faire référence à une méthode prédéfinie. Soient les instructions suivantes
newclass P10, "Arithmetique" addattribute P10, "Entier" subclass P11, P10, "Valeur" find_type I0, "Valeur" # Objet de la sous-classe "Valeur" new P20, I0 set P20, 30 end
Son exécution va produire le message d'erreur :
Can't find method '__set_integer_native' for object 'Valeur' current instr.: '(null)' pc 16 (prog.pasm:7)
Nous faisant savoir que l'instruction numéro 7 a fait appel à la méthode
__set_integer_native
que nous avons omis de définir. Ainsi donc, pour
plusieurs instructions qui feront référence à un objet, il sera indispensable
de définir la méthode qui leur est attachée. Afin qu'elle soit appelée au
moment voulu.
set Px, Iy : __set_integer_native get Ix, Py : __get_integer add Px, Py, Pz : __add inc Px : __increment print Px : __get_string
Voyons ceci sur un exemple. Nous allons déclarer deux objets composés d'un entier.
newclass P1, "Nombre" addattribute P1, "Entier" find_type I1, "Nombre" # Création d'un premier objet nombre. new P3, I1 # Affectation d'une valeur à l'objet. set P3, 300 # Création d'un second objet nombre. new P4, I1 # Affectation d'une valeur à l'objet. set P4, 400 get_results "(0)", P5 # Addition des deux objets. add P5, P4, P3 # Affichage des résultats. print "Resultat de l'addition : " print P5 print "\n" end
On désire au moment où ils sont créés leur affecter comme attribut un
entier. Dans l'espace de nom Nombre
nous allons donc déclarer la
méthode __init
.
.namespace ["Nombre"] .pcc_sub __init: get_params "(0)", P31 new P10, .Integer setattribute P31, "Entier", P10 returncc
Comme il s'agit d'objets, le code opération set
va faire à son tour
appel à une méthode prédéfinie. Dans ce cas, il s'agit d'affecter une
valeur entière à l'attribut d'un objet. La méthode qui est requise pour
cette opération est __set_integer_native
. L'espace de nom Nombre
s'enrichit d'une nouvelle méthode.
.pcc_sub __set_integer_native: get_params "(0,0)", P31, I31 new P30, .Integer set P30, I31 returncc
L'opération que nous désirons effectuer est l'addition des
valeurs entières correspondant aux attributs des deux objets qui viennent
d'être créés. Le résultat de cette opération sera récupéré dans un PMC
de type .Integer
qui, pour terminer, sera affiché.
La méthode requise pour effectuer cette opération est __add
. Rajoutons-la
à son tour dans notre espace de nom.
.pcc_sub __add: get_params "(0,0,0)", P25, P24, P23 getattribute P15, P25, "Entier" getattribute P14, P24, "Entier" new P13, .Integer add P13, P14, P15 set_returns "(0)", P13 returncc
Notre programme est maintenant terminé, il ne nous reste plus qu'à l'exécuter.
chris$ parrot add.pasm Resultat de l'addition : 700 chris$
Une méthode attachée à une classe doit avoir un nom qui sera défini par l'intermédiaire d'un registre « string ».
set S0, "methode" *** callmethodcc P0, S0 *** .namespace ["Objet"] .pcc_sub methode: *** returncc
Une fois déclarée, la méthode devra être rattachée à l'espace concerné
au moyen de la fonction .mamespace
et son contenu défini.
chris$ cat obj.pasm # Declaration de la classe Humain newclass P0, "Humain" # Declaration de la methode Identite set S0, "Identite" # Appel de la methode Identite callmethodcc P0, S0 end # Definition de la methode .namespace ["Humain"] .pcc_sub Identite: print "Bonjour, je suis un etre humain.\n" returncc chris$ parrot obj.pasm Bonjour, je suis un etre humain. chris$
Avant l'appel d'une méthode, il est possible de tester son existence.
can Ix, Py, "Nom"
Va tester si la méthode Nom
a été déclarée dans l'espace de nom défini
par le PMC Py
. La réponse retransmise par l'intermédiaire du registre
Ix
sera undef
si elle n'existe pas, non undef
dans le cas contraire.
L'exemple suivant va déclarer une classe Chris
auquel est attachée la
méthode Bonjour
.
Avant de l'appeler, on vérifiera qu'elle est bien présente. La même opération
sera effectuée sur la méthode Salut
qui, elle, ne correspond à rien.
chris$ cat obj.pasm .macro existe (A, B, OUI, NON) print "La methode " print .A unless .B, .$FAUX print " existe, on l'appelle.\n" branch .OUI .local $FAUX: print " n'existe pas, on passe a la suite.\n" branch .NON .endm newclass P2, "Chris" set S0, "Bonjour" # Test de l'existence de la methode Bonjour. set S1, "Bonjour" can I0, P2, S1 .existe (S1, I0, E1, SUITE) E1: callmethodcc P2, S0 SUITE: # Test de l'existence de la methode Salut. set S1, "Salut" can I0, P2, S1 .existe (S1, I0, E2, FIN) E2: callmethodcc P2, S0 FIN: end # Definition de la methode .namespace ["Chris"] .pcc_sub Bonjour: print " Chris vous souhaite le bonjour.\n" returncc chris$ parrot obj.pasm La methode Bonjour existe, on l'appelle. Chris vous souhaite le bonjour. La methode Salut n'existe pas, on passe a la suite. chris$
Le langage d'assemblage nous permet, comme cela est possible en Perl, d'évaluer une chaîne de caractères, contenue dans un registre, comme un programme.
Prenons par exemple l'instruction suivante :
set S0, "set S1, 10\nprint \"Valeur de S1 : \"\nprint S1\nprint \"\\n\"\nreturncc\n"
L'impression du contenu du registre S0
donnera le résultat suivant :
set S1, 10 print "Valeur de S1 : " print S1 print "\n" returncc
Soit un sous programme Parrot parfaitement constitué. Cette chaîne peut donc être soumise à un évaluateur qui la considérera comme une suite d'instructions à exécuter.
chris$ cat evaluer.pasm print " Debut du programme.\n" compreg P1, "PASM" set S0, "set S1, 10\nprint \"Valeur de S1 : \"\nprint S1\nprint \"\\n\"\nreturncc\n" set_args "(0)", S0 get_results "(0)", P0 print " Evaluation du programme contenu dans S0.\n" invokecc P1 invokecc P0 print " Retour au programme principal.\n" end chris$ parrot evaluer.pasm Debut du programme. Evaluation du programme contenu dans S0. Valeur de S1 : 10 Retour au programme principal. chris$
Le registre S0 contient la donnée représentative du programme qui doit
être évalué. L'instruction compreg P1, "PASM"
indique que le langage
est de l'assembleur Parrot et devra être traité en conséquence. Le registre
S0
sera transmis en tant que paramètre au programme d'assemblage.
C'est le rôle de l'instruction set_args "(0)", S0
et le résultat,
soit le module exécutable, sera récupéré dans un PMC (P0
), c'est ce
que dit l'instruction get_results "(0)", P0
il ne nous reste plus
qu'à appeler le programme d'assemblage dont la référence se trouve dans
le PMC P1
au moyen de l'instruction invokecc P1
puis de faire appel
au résultat que nous récupérons dans P0
comme s'il s'agissait d'un
sous-programme invokecc P0
. C'est la raison pour laquelle le morceau
de code soumis à l'évaluation doit se terminer par l'instruction de retour
returncc
.
Une coroutine est un sous-programme dont l'exécution peut être suspendue
et qui, au moment de l'appel suivant reprendra son exécution au point où
elle aura été interrompue. Tout sous-programme qui contient le code
opération yield
sera considéré comme une coroutine, et le point de
reprise sera justement l'emplacement de l'instruction en question.
chris$ cat evaluer.pasm .include "interpinfo.pasm" .pcc_sub _main: .const .Sub P0 = "_japh" new P2, .String set P2, "" store_global "car", P2 find_global P10, "car" IMP: invokecc P0 unless P10, FIN print P10 branch IMP FIN: end .pcc_sub _japh: set S1, "Just Another Perl Hacker.\n" set I1, 0 SUITE: find_global P10, "car" substr S10, S1, I1, 1 set P10, S10 inc I1 yield branch SUITE chris$ parrot evaluer.pasm Just Another Perl Hacker. chris$
Le mécanisme se révèle être d'une grande utilité lorsqu'une fonction doit mémoriser une valeur au fur et à mesure de ses appels successifs.
Un des premiers exemple qui vient à l'esprit est le génération d'une série de valeurs pseudo-aléatoires [Aléat]. L'algorithme standard part avec une valeur initiale (la racine) qui sert pour produire la première valeur. Cette dernière sera alors utilisée pour en générer une nouvelle, et ainsi de suite.
Prenons un algorithme classique de génération de suite pseudo-aléatoire, qui, en Perl, se présente comme suit.
sub rand { $x = int(($x * 1103515245 + 12345) / 65536) % 32768; return $x; }
Et réalisons-le en Parrot au moyen d'une coroutine.
chris$ cat rand.pasm .const .Sub P1 = "_rand" set I1, 10 AUTRE: get_results "(0)", I0 invokecc P1 print I0 print " " dec I1 if I1 , AUTRE print "\n" end .pcc_sub _rand: new P0, .Float set P0, 1 store_global "racine", P0 find_global P0, "racine" NOUVEAU: mul P0, 1103515245 add P0, 12345 div P0, 65536 mod P0, 32768 set I31, P0 set_returns "(0)", I31 yield branch NOUVEAU chris$ parrot rand.pasm 16838 22996 7307 3007 20531 15468 29688 23554 1052 20045 chris$
Les informations entrées sur la ligne de commande sont les premiers
résultats (get_params
) que peut récupérer un programme Parrot. C'est un PMC de type
.ResizableStringArray
qui leur servira de réceptacle.
chris$ cat cmd.pasm # Récupération des paramètres de la ligne de commande. # P0 est de type .ResizableStringArray. get_params "(0)", P0 set I1, P0 print I1 print " parametres : " set I0, 0 AUTRE: set S0, P0[I0] print S0 print " - " inc I0 lt I0, I1 , AUTRE print "\n" end chris$ parrot cmd.pasm Bonjour tout le monde. 5 parametres : cmd.pasm - Bonjour - tout - le - monde. - chris$
On dispose d'instructions spécifiques permettant de suivre pas à pas le
déroulement du programme. Le code opération getfile
permet de récupérer
dans un registre string
le nom du fichier représentatif du programme
sans avoir à procéder à l'acquisition de la totalité de la ligne de
commande, et le code getline
permet de connaître le numéro de la
ligne courante.
chris$ cat info.pasm .macro set_liste (PMC, CHAINE) new .PMC, .ResizablePMCArray split .PMC, ",", .CHAINE .endm .macro info (FICHIER, LIGNE) print "Fichier : " print .FICHIER print ", Ligne : " print .LIGNE print "\n" .endm getfile S0 .set_liste (P3, "1,2,3,4,5") set I1, P3 getline I30 .info (S0, I30) set I0, 0 SUITE: set S1, P3[I0] print S1 print "\n" inc I0 getline I30 .info (S0, I30) lt I0, I1 , SUITE end chris$ parrot info.pasm Fichier : info.pasm, Ligne : 17 1 Fichier : info.pasm, Ligne : 25 2 Fichier : info.pasm, Ligne : 25 3 Fichier : info.pasm, Ligne : 25 4 Fichier : info.pasm, Ligne : 25 5 Fichier : info.pasm, Ligne : 25 chris$
Dans le même ordre d'idée, il est possible de procéder au tracé des
instructions. L'instruction trace 1
permet d'activer le mécanisme de
tracé du programme alors que l'instruction trace 0
y met fin.
chris$ cat trace.pasm .macro set_liste (PMC, CHAINE) new .PMC, .ResizablePMCArray split .PMC, ",", .CHAINE .endm .set_liste (P3, "1,2,3,4,5") trace 1 set I1, P3 set I0, 0 trace 0 SUITE: set S1, P3[I0] print S1 print "\n" trace 1 inc I0 trace 0 lt I0, I1, SUITE end chris$ parrot trace.pasm 9 set I1, P3 I1=-888 P3=ResizableStringArray=PMC(0xf9bf7c) 12 set I0, 0 I0=-888 15 trace 0 1 27 inc I0 I0=0 29 trace 0 2 27 inc I0 I0=1 29 trace 0 3 27 inc I0 I0=2 29 trace 0 4 27 inc I0 I0=3 29 trace 0 5 27 inc I0 I0=4 29 trace 0 chris$
Pour chaque instruction tracée, on voit apparaître le numéro de la ligne considérée, l'instruction correspondante et, si cette dernière est une affectation, le registre de destination et sa valeur avant l'exécution de l'opération demandée. C'est ainsi que lors de la première exécution des lignes 9 et 12, les contenus sont égaux à -888, valeur qui correspond à un registre qui n'a reçu aucune affectation.
Comme en Perl, l'instruction sleep
permet de mettre le programme en
attente pendant le nombre de secondes spécifié. Par ailleurs, un ensemble
d'instructions permettent de récupérer le temps en nombre de secondes
time
de le décoder en heure locale decodelocaltime
dans un PMC.
Le fichier tm.pasm
est un fichier de constantes symboliques qui va
permettre d'accéder aux divers éléments du PMC ainsi créé pour en extraire
les données. Son contenu est le suivant :
.constant TM_SEC 0 .constant TM_MIN 1 .constant TM_HOUR 2 .constant TM_MDAY 3 .constant TM_MON 4 .constant TM_YEAR 5 .constant TM_WDAY 6 .constant TM_YDAY 7 .constant TM_ISDST 8
.TM_SEC Secondes (0-60)
.TM_MIN Minutes (0-59)
.TM_HOUR Heure (0-23)
.TM_MDAY Jour dans le mois (1-31)
.TM_Mon Mois dans l'année (1-12)
.TM_YEAR Année réelle, 2007 est bien 2007, pas 107
.TM_WDAY Jour dans la semaine (0-6), 0 représentant le dimanche.
.TM_YDAY Jour dans l'année (0-365)
.TM_ISDST Heure d'été (Vrai ou Faux)
Le programme suivant est construit autour d'une coroutine, les cinq appels successifs permettent d'initialiser les données pour le premier, puis d'afficher une ligne pour chacun des quatre suivants.
chris$ cat time.pasm .macro set_liste (PMC, CHAINE) new .PMC, .ResizablePMCArray split .PMC, ",", .CHAINE .endm .macro print2 (V1, V2) print .V1 print .V2 .endm .const .Sub P1 = "_date" # Initialisations données de la coroutine. invokecc P1 # Affichage du jour. invokecc P1 # IAffichage de l'heure invokecc P1 # Affichage du numéro du jour. invokecc P1 # Affichage heure d'été. invokecc P1 end .pcc_sub _date: .set_liste (P10, "Dim,Lun,Mar,Mer,Jeu,Ven,Sam") .set_liste (P11, "*,Jan,Fev,Mar,Avr,Mai,Jun,Jui,Aou,Sep,Oct,Nov,Dec") .set_liste (P12, "hiver,ete") time I0 decodelocaltime P2, I0 yield .include "tm.pasm" .print2 ("Nous sommes le", " ") set I1, P2[.TM_WDAY] set S1, P10[I1] .print2 (S1, " ") set I1, P2[.TM_MDAY] .print2 (I1, " ") set I1, P2[.TM_MON] set S1, P11[I1] .print2 (S1, " ") set I1, P2[.TM_YEAR] .print2 (I1, "\n") yield .print2 ("Il est", " ") set I1, P2[.TM_HOUR] .print2 (I1, " heures ") set I1, P2[.TM_MIN] .print2 (I1, " minutes ") set I1, P2[.TM_SEC] .print2 (I1, " secondes\n") yield print "Ce jour est le " set I1, P2[.TM_YDAY] .print2 (I1, "eme de l'annee\n") yield print "Nous sommes en heure d'" set I1, P2[.TM_ISDST] set S1, P12[I1] .print2 (S1, ".\n") yield chris$ parrot time.pasm Nous sommes le Mar 10 Avr 2007 Il est 11 heures 20 minutes 34 secondes Ce jour est le 99eme de l'annee Nous sommes en heure d'ete. chris$
J'ai tenté, dans cette série d'articles, de mettre en évidence le fait qu'un langage d'assemblage pouvait avoir une approche différente. La machine virtuelle Parrot servira de support à de nombreux langages, si le niveau de son assembleur devrait permettre le développement rapide de compilateurs, il permet à ceux qui le désirent de disposer d'une plate-forme performante et d'un abord agréable.
Copyright © Les Mongueurs de Perl, 2001-2020
pour le site.
Les auteurs conservent le copyright de leurs articles.