Catégorie : Programmation     Tags :      0 Commentaire

    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 :

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

    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.

     

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

      # 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

    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

      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.

    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 :

      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.

    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.

      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$

    Application sur un programme

    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.

    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.

          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$

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

      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.

    Définition d’une sous-classe

    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.

    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 :

          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.

    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.

          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.

     

    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.

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

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

       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 à partir d’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$

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

      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$

    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.

     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$

    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 :

      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.

    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.

      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$

    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 :

      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$

    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.

       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$

    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.

       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.

    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 :

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

      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$

    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

    Posté par (La rédaction) | Signature : Christian Aperghis-Tramoni | Article paru dans Creative Commons License

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.