[couverture de Linux Magazine 97]

La machine Parrot, première partie

Article publié dans Linux Magazine 97, septembre 2007.

Copyright © 2007 Christian Aperghis-Tramoni

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

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.

[logo Parrot à la Terminator] One bytecode to rule them all

Le langage assembleur

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.

Genèse de Parrot

La machine virtuelle Parrot

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.

[couverture du faux livre] La couverture du livre que n'ont jamais
écrit Larry Wall et Guido von Rossum.

Le poisson d'Avril

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

Premiers pas en Parrot

[couverture du livre] Le livre de référence.

Documentation.

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.

Installation

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

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.

Principe de fonctionnement

Le système Parrot est structuré en plusieurs éléments fondamentaux destinés chacun à une tâche bien spécifique.

[diagramme de fonctionnement de Parrot]

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 machine virtuelle

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 :

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.

Les piles

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.

Les débuts en programmation Parrot

Le premier programme

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.

Syntaxe de l'assembleur

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 :

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

La représentation des données

Les constantes numériques

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.

Les chaînes de caractère

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.

Les registres

Référence à un registre

Un registre sera référencé par son type et par son numéro. Le type est toujours une lettre majuscule :

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$

Particularité des registres

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.

Manipulation des données

Travail sur les valeurs numériques

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$

Travail sur les chaînes de caractères

À 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$

Suppression de caractères

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$

Indexation d'une chaîne

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$

Conversion de caractères

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$

Changement de type

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$

Les Parrot Magic Cookies

Qu'est ce qu'un PMC ?

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.

Quels sont les PMC disponibles ?

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$

Manipulation des registres PMC

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$

Les entrées sorties

Lecture d'information

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$

Sortie d'information

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.

Conclusion

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.

Références

[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

[JWS] http://www.jwcs.net/~jonathan/perl6/parrot-win32.zip

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]