La machine Parrot : plus loin avec Parrot
Signature : | Mis en ligne le : 16/03/2008
Catégorie(s) :
  • GNU/Linux Magazine
  • | Domaine :
    Commentez creative commons

    Retrouvez cet article dans : Linux Magazine 99

    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.

     

    Les macro-instructions

    Qu’est-ce qu’une macro ?

    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 : Ensuite, chaque fois que le programme y fera référence sous la forme abs(exp), cette construction sera développée comme :

    Définition d’une macro en Parrot

    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.

     

    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 suivi 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 :

    Les paramètres

    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

    ou lorsqu’une macro doit gérer plusieurs sorties :

    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.

    Les références locales

    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 :

    L’interpréteur détecte une erreur syntaxique. En effet, l’opération de substitution a produit le résultat suivant

    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.

    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.

    Les macros prédéfinies

    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.

    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é.

    Application sur un programme

    Le programme suivant permet de convertir un nombre décimal en numération romaine [Romain].

    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.

    La programmation orientée objet

    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 contenterais de définir les diverses opérations afin que ceux qui désireraient approfondir le sujet puissent le faire sans trop de difficulté.

    Les classes

    Définition d’une classe

    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.

    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.

    Les attributs de classe

    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é.

    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.

    Définition d’une sous-classe

    L’instruction

    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 :

    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.

    va nous retourner dans le registre SO le nom de la classe correspondant au PMC P1, soit Homme.

    Les objets

    Définition d’un objet

    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 :

    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.

    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.

    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.

    Les attributs

    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.

    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.

    Dans la troisième méthode, l’instruction

    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.

    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.

     

    Une application

    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. 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. 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. À 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. À 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. Lorsque l’ensemble des morceaux du programme sont mis bout à bout, l’exécution donne le résultat suivant :

    Les méthodes

    Les méthodes prédéfinies

    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éé. Voyons un autre exemple qui va lui aussi faire référence à une méthode prédéfinie. Soient les instructions suivantes : Son exécution va produire le message d’erreur : 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 : Voyons ceci à partir d’un exemple. Nous allons déclarer deux objets composés d’un entier. 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. 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. 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. Notre programme est maintenant terminé, il ne nous reste plus qu’à l’exécuter.

    Les méthodes définies par le programmeur

    Une méthode attachée à une classe doit avoir un nom qui sera défini par l’intermédiaire d’un registre " string ". 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.

    Test de l’existence d’une méthode

    Avant l’appel d’une méthode, il est possible de tester son existence. 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 à laquelle 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.

    Quelques particularités notables

    Évaluation d’une chaîne

    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 : L’impression du contenu du registre S0 donnera le résultat suivant : 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. 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.

    Les coroutines

    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.

    Application des coroutines

    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 exemples qui vient à l’esprit est la 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 : Et réalisons-le en Parrot au moyen d’une coroutine.

    Récupération des valeurs de la ligne de commandes

    Les informations entrées sur la ligne de commandes 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.

    Suivi du déroulement

    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 commandes, et le code getline permet de connaître le numéro de la ligne courante. 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. 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.

    Gestion du temps

    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 et 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 :
    • .TM_SEC Seconde (0-60)
    • .TM_MIN Minute (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.

    Conclusion

    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. Références
    Vous souhaitez commenter cet article ?
    Brèves Flux RSS
    Édito : GNU/Linux Magazine 149
    Édito : GNU/Linux Magazine HS N°60
    Édito : Misc 61
    Édito : Linux Pratique 71
    Édito : Linux Essentiel N°25
    Communication RSS Com. RSS Presse
    Lancement de la plateforme de vente en ligne de PDF des Éditions Diamond ! Un...
    Misc N°61 – Communiqué de presse
    GNU/Linux Magazine N°149 – Communiqué de presse
    GNU/Linux Magazine HS N°60 – Communiqué de presse
    Linux Pratique N°71 – Communiqué de presse
    prochainement moteur de recherches des articles
     
    :
    :
    Jours heures minutes secondes
    En kiosque Flux RSS

    Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Misc est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau GNU/Linux Magazine HS est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Linux Essentiel est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Misc HS est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...