Catégorie : Programmation     Tags :      

    Retrouvez cet article dans : Linux Magazine 98

    Le premier article nous a permis de montrer combien il était facile de développer en assembleur Parrot. Nous allons, dans cette deuxième partie, aborder les structures de données et l’accès aux fichiers. Plusieurs programmes nous permettront de mettre en application les sujets traités.

    Les ruptures de séquence

    Les ruptures inconditionnelles

    Avant d’aborder le problème des ruptures de séquence en assembleur, je ne saurais que recommander la lecture de l’article de Philippe Bruhat et Jean Forget consacré à l’histoire de l’instruction goto [goto].
    Lorsqu’on programme en langage d’assemblage, la rupture dans le séquencement d’une suite d’instructions est inévitable. Cette affirmation n’interdit pas de tenter d’en minimiser le nombre et la portée afin de conserver au programme une certaine lisibilité.
    Les opérations de rupture de séquence, qu’elles soient conditionnelles ou inconditionnelles, font référence à une adresse symbolique, toujours représentée par une étiquette.
    Voici un exemple à ne surtout pas suivre, mais qui permet d’illustrer le fonctionnement de l’instruction de rupture inconditionnelle de séquence branch.

      coruscant:~/Langages/asmparrot chris$ cat rupt.pasm
       branch   E1
      E2:
       print    „Et puis par la.\n“
       branch   FIN
      E1:
       print    „Je passe par ici.\n“
       branch   E2
      FIN:
       print    „Et enfin, je termine.\n“
       end
      coruscant:~/Langages/asmparrot chris$ parrot rupt.pasm
      Je passe par ici.
      Et puis par la.
      Et enfin, je termine.
      coruscant:~/Langages/asmparrot chris$

    Les ruptures conditionnelles

    Il existe deux familles de rupture conditionnelle de séquence. Comme son nom l’indique, la réalisation du saut sera soumise à une condition. Dans le cas de l’instruction de code opération if, il est conditionné au test (vrai ou faux) du contenu d’un registre. Une évaluation à vrai (non undef) contraindra le programme à réaliser le saut demandé, alors qu’avec un résultat de type faux (undef), le programme continuera à s’exécuter en séquence. À noter qu’il existe aussi une instruction unless qui, au lieu de tester la condition à vrai, la teste à faux. En voici l’illustration sur un exemple :

       coruscant:~/Langages/asmparrot chris$ cat exo.pasm
       getstdin   P0
       getstdout  P1
       print      P1, «Quel est le diviseur ?\n»
       readline   S1, P0
       set        I1, S1
       print      P1, “Quel est le dividende ?\n”
       readline   S1, P0
       set        I2, S1
       mod        I3, I2, I1
       if         I3, RESTE
       print      P1, I1
       print      P1, “ est un diviseur entier de “
       print      P1, I2
       branch     FIN
      RESTE:
       print      P1, I1
       print      P1, “ divise “
       print      P1, I2
       print      P1, “ avec un reste egal a “
       print      P1, I3
      FIN:
       print      P1, “.\n”
       end
      coruscant:~/Langages/asmparrot chris$ parrot exo.pasm
      Quel est le diviseur ?
      5
      Quel est le dividende ?
      12
      5 divise 12 avec un reste egal a 2.
      coruscant:~/Langages/asmparrot chris$ parrot exo.pasm
      Quel est le diviseur ?
      5
      Quel est le dividende ?
      25
      5 est un diviseur entier de 25.
      coruscant:~/Langages/asmparrot chris$

    Lorsque le contenu doit être testé relativement à une valeur de référence, on dispose de six opérations logiques pour effectuer cette comparaison. Ce sont :

    • eq test d’égalité ;
    • ne test de différence (non égal) ;
    • lt test d’infériorité ;
    • gt test de supériorité ;
    • le test d’infériorité au sens large (<=) ;
    • ge test de supériorité au sens large (>=).

    Le programme suivant permet de calculer les n premiers éléments de la suite de Fibonacci, le nombre de valeurs à calculer sera lu sur le clavier.

      coruscant:~/Langages/asmparrot chris$ cat fibo.pasm
       getstdin   P0
       getstdout  P1
       print      P1, «Combien de valeurs doit-on calculer ?\n»
       readline   S1, P0
       print      P1, «----------\n»
       set        I1, 1
       set        I2, S1
       set        I3, 1
       set        I4, 1
      BOUCLE:
       gt         I1, I2, FIN
       set        I5, I4
       add        I4, I3, I4
       set        I3, I5
       print      P1, «Fibonnaci(«
       set        S1, I1
       concat     S0, «  «, S1
       substr     S1, S0, -2
       print      P1, S1
       print      P1, «) = «
       set        S1, I3
       concat     S0, «  «, S1
       substr     S1, S0, -2
       print      P1, S1
       print      P1, «\n»
       inc        I1
       branch     BOUCLE
      FIN:
       end
      coruscant:~/Langages/asmparrot chris$ parrot fibo.pasm
      Combien de valeurs doit-on calculer ?
      10
      ----------
      Fibonnaci( 1) =  1
      Fibonnaci( 2) =  2
      Fibonnaci( 3) =  3
      Fibonnaci( 4) =  5
      Fibonnaci( 5) =  8
      Fibonnaci( 6) = 13
      Fibonnaci( 7) = 21
      Fibonnaci( 8) = 34
      Fibonnaci( 9) = 55
      Fibonnaci(10) = 89
      coruscant:~/Langages/asmparrot chris$

    C’est l’utilisation judicieuse des opérations conditionnelles et inconditionnelles qui va permettre de réaliser toutes les boucles ou tests dont nous avons l’habitude de nous servir lorsque nous programmons dans un langage de haut niveau. La boucle for qui, en Perl, s’écrit :

        for ($i=debut; $i<fin; $i++) {
            # corps de la boucle.
        }

    Sera traduite en assembleur par la séquence d’instructions :

      set I0, debut
      FOR:
      # corps de la boucle
        inc I0
        lt I0, fin, FOR
      # suite du programme

    De la même manière, l’écriture en Perl de la boucle while :

         while (variable cond valeur) {
            # corps de la boucle.
            # modification de la variable.
        }

    se présentera en Parrot sous la forme :

      WHILE:
        set     I1, valeur_depart
        cond    I1, valeur, FIN
      # corps de la boucle
      # modification de I1
        branch  WHILE
      FIN:
        # suite du programme

    Terminons sur l’instruction conditionnelle :

         if (VAL1 == VAL2) {
            # bloc vrai.
        } else {
            # bloc faux
        }

    Dont la traduction en assembleur Parrot donne :

        set     I0, VAL1
        set     I1, VAL2
        ne      I0, I1, FAUX
      # bloc vrai
        branch  FIN_IF
      FAUX:
      # bloc faux
      FIN_IF:
      # suite du programme

    Les agrégats

    La liste statique

    C’est l’utilisation des PMC appropriés qui va permettre la manipulation des structures (listes, hash). L’ensemble des PMC disponibles est stocké dans le répertoire include/parrot/pmc.h et nous avons vu qu’on pouvait en obtenir la liste au moyen d’un programme. La documentation détaillée est disponible sur le site Parrot [pmc]. Ici, nous limiterons la présentation aux structures de base standard les plus souvent utilisées, listes statiques, listes dynamiques et hash. À terme, on devrait disposer d’une vaste palette de PMC permettant la gestion de plusieurs types d’agrégats couvrant plusieurs langages de programmation.
    Commençons par un exemple qui met en évidence l’utilisation d’un agrégat statique, une table au sens classique du terme dont la longueur doit être spécifiée avant de pouvoir l’utiliser. Ce programme va créer une structure indexée et va stocker dans chaque emplacement i de l’agrégat la somme des i premiers nombres entiers.

      coruscant:~/Langages/asmparrot chris$ cat agregat1.pasm
          getstdin  P0
          getstdout P1
      # Declaration de la table.
          new       P3, .Array
          print     P1, «Nombre d’elements a calculer ?\n»
          readline  S1, P0
          set       I2, S1
      # Declaration de la longueur de la table.
          set       P3, I2
          set       I0, 0
          set       I1, 0
      BOUCLE:
          eq        I0, I2, IMPRIME
          add       I1, I1, I0
          set       P3[I0], I1
          inc       I0
          branch    BOUCLE
      IMPRIME:
          set       I0, 0
          print     P1, “Longueur de la table : “
      # Recuperation de la longueur de la table.
          set       I3, P3
          print     P1, I2
          print     P1, “\n”
      CONT:
          eq        I0, I2, FIN
          print     P1, «Table(«
          set       S1, I0
          concat    S0, «  «, S1
          substr    S1, S0, -2
          print     P1, S1
          print     P1, «) = «
          set       S1, P3[I0]
          concat    S0, «  «, S1
          substr    S1, S0, -2
          print     P1, S1
          print     P1, «\n»
          inc       I0
          branch    CONT
      FIN:
          end
      coruscant:~/Langages/asmparrot chris$ parrot agregat1.pasm
      Nombre d’elements a calculer ?
      12
      Longueur de la table : 12
      Table( 0) =  0
      Table( 1) =  1
      Table( 2) =  3
      Table( 3) =  6
      Table( 4) = 10
      Table( 5) = 15
      Table( 6) = 21
      Table( 7) = 28
      Table( 8) = 36
      Table( 9) = 45
      Table(10) = 55
      Table(11) = 66
      coruscant:~/Langages/asmparrot chris$

    L’agrégat étant statique, ses bornes sont testées avant affectation. Une erreur dans l’indexation de la structure ainsi définie déclenchera une exception et le message d’erreur «Array index out of bounds!» sera imprimé.

    La liste dynamique

    Le même programme peut aussi être réalisé en utilisant une structure dynamique, une liste au sens Perl du terme, par exemple un agrégat de type .ResizableIntegerArray. Le programme est identique, il suffit de changer la déclaration du PMC de

      new   P3, .Array

    en

      new   P3, .ResizableIntegerArray

    Puis, si on le désire, de supprimer la ligne déclarant la longueur de la structure :

      set   P3, I2

    Cette opération n’est pas indispensable, la gestion dynamique de l’agrégat n’imposant pas son dimensionnement préalable. Il est néanmoins possible d’utiliser cette possibilité. Si, par exemple, il est nécessaire de disposer à un instant donné d’une liste vide, il suffit de lui affecter la longueur zéro.

      set  P3, 0

    Nous allons illustrer le comportement d’une telle structure, en écrivant le programme qui calcule les lignes successives du triangle de Pascal, programme particulièrement adapté à la gestion dynamique d’une liste. Rappelons très brièvement la méthode de calcul pour n lignes. Dans un dialecte classique, on réserverait une matrice n*n dans laquelle seuls un peu plus de la moitié des emplacements (n+1)/2n seraient utilisés. Un élément p[i,j] se calcule par rapport aux deux éléments de la ligne précédente p[i-1,j] + p[i-1,j-1]. Cet algorithme peut être écrit d’une manière élégante en Perl en n’utilisant qu’une seule liste dont la longueur augmentera de 1 à chaque itération dans laquelle la ligne concernée sera générée, et un doublet dans lequel seront mémorisées les valeurs servant au calcul. En Perl, cet algorithme se programme comme suit :

        $l = @t;
    
        for ($j=1; $j<=$l; $j++) {
            @tp = ($tp[1], $t[$j]);
            $t[$j] = $tp[0] + $tp[1];
        }

    La réécriture en Parrot va utiliser les deux types de structures, un PMC statique .Array pour mémoriser le doublet de valeurs et un PMC dynamique .ResizableIntegerArray pour générer les itérations successives.

       coruscant:~/Langages/asmparrot chris$ cat pascal.pasm
        # Le triangle de Pascal
           getstdin       P0
           getstdout      P1
        # Nombre de lignes a générer dans I0.
           set            I0, 10
        # P3, vecteur à deux éléments.
           new            P3, .Array
           set            P3, 2
        # P4, ligne courante du triangle.
           new            P4, .ResizableIntegerArray
           set            P4[0], «»
           set            P4[1], 1
        ITERATION:
        # Longueur courante du vecteur dans I2.
           set            I2, P4
           dec            I0
           lt             I0, 0, FIN
           inc            I1
        # Impression du vecteur.
           set            I31, 1
        CONTINUE:
        # Formatage des résultats sur 4 caractères.
           set            S1, P4[I31]
           concat         S1, «    «, S1
           substr         S1, S1, -4
           print          P1, S1
           inc            I31
           lt             I31, I2, CONTINUE
           print          P1, “\n”
        # Calculer la ligne suivante à partir de la ligne courante.
        # I31 : Indice d’exploration de vecteur.
           set            I31, 1
        CALCUL:
           set            I30, P3[1]
           set            P3[0], I30
           set            I30, P4[I31]
           set            P3[1], I30
           set            I30, P3[0]
           set            I29, P3[1]
           add            I30, I30, I29
           set            P4[I31], I30
        # Progression dans le vecteur.
           inc            I31
           le             I31, I2, CALCUL
           branch         ITERATION
        FIN:
           end
      coruscant:~/Langages/asmparrot chris$ parrot pascal.pasm
       1
       1   1
       1   2   1
       1   3   3   1
       1   4   6   4   1
       1   5  10  10   5   1
       1   6  15  20  15   6   1
       1   7  21  35  35  21   7   1
       1   8  28  56  70  56  28   8   1
       1   9  36  84 126 126  84  36   9   1
      coruscant:~/Langages/asmparrot chris$

    Ce programme peut, à volonté, être modifié par exemple pour demander combien d’itérations doivent être calculées ou, plus intéressant, n’imprimer que celles correspondant à un rang donné. De plus, le formatage de la sortie sur 4 caractères, 3 chiffres plus une espace, qui ne permet pas la présentation des résultats au-delà de 12 lignes, peut lui aussi évoluer. Une modification facile à mettre en œuvre consisterait à indiquer son numéro devant chacune des lignes en remarquant qu’il correspond au nombre d’éléments de la structure moins 1, le premier emplacement étant à Undef pour permettre de démarrer l’algorithme. La taille est récupérée dans un registre entier au moyen de l’instruction :

      set Ix, P4

    Il ne reste plus qu’à lui retrancher un et à l’imprimer.

    Le hash

    Terminons ce chapitre sur les agrégats avec une structure de type hash, au sens Perl du terme, c’est-à-dire une structure de données dans laquelle chaque valeur est accessible par l’intermédiaire d’une clé. La déclaration de la structure se fait par l’intermédiaire du PMC .Hash.

      new Px, .Hash

    L’écriture d’une valeur, explicite ou en provenance d’un registre (Rz), dans le hash défini, par le PMC Px, à l’emplacement spécifié par une clé Sy qui peut, elle aussi, être un littéral ou une référence à un registre se fait classiquement au moyen de l’opération de chargement set.

      set Px[Sy], Rz

    L’instruction exists permet de tester l’existence d’une clé d’entrée.

      exists Ix, Px[«cle»]

    Le registre entier concerné (Ix) sera positionné à vrai si la clé spécifiée existe. Dans le cas contraire, il sera positionné à faux. Cette instruction teste l’existence de l’entrée, mais elle ne teste pas la valeur qui lui est associée. Le résultat du test sera vrai lorsque l’entrée relative à la clé a été définie, même si la valeur qui lui correspond est undef. Pour vérifier que la donnée correspondante est différente de undef, on dispose de l’instruction defined.

      defined Ix, Px[«cle»]

    Le registre Ix contiendra alors faux si Px[«cle»] est undef, vrai dans le cas contraire.
    Pour pouvoir travailler efficacement sur un hash, il est indispensable de disposer d’une méthode adaptée permettant la récupération de la liste des clés d’entrée. Pour concrétiser cette opération, il est nécessaire de faire référence à un itérateur spécifique, iterator.pasm, dont le code se trouve stocké dans le répertoire /usr/local/lib/parrot/include et qui sera inclus dans le programme au moyen de l’instruction :

      .include «iterator.pasm»

    Une requête à l’itérateur permet de créer dans un PMC Px, une liste qui va contenir l’ensemble des clés d’accès au hash défini par le PMC Py.

      new   Px, .Iterator, Py

    Cet itérateur propose plusieurs méthodes d’accès, ITERATE_FROM_START, ITERATE_FROM_START_KEYS, ITERATE_GET_NEXT, ITERATE_GET_PREV, ITERATE_FROM_END, donnant le choix entre plusieurs méthodes différentes pour explorer la liste de clés ainsi construite. Dans le cas qui nous intéresse, nous choisissons la méthode .ITERATE_FROM_START qui, comme son nom l’indique, explore la liste en commençant par le début.

      set   P2, .ITERATE_FROM_START

    Reprenons l’ensemble de ces opérations dans un programme.

       coruscant:~/Langages/asmparrot chris$ cat jardin.pasm
       .include «iterator.pasm»
       getstdin   P0
       getstdout  P1
      # Déclaration du hash.
       new        P3, .Hash
       print      P1, «Etat des plantations de mon jardin.\n»
      # Lecture des informations et construction du hash.
      JARDIN:
       print      P1, «Nom de la fleur ?\n»
       readline   S5, P0
       chopn      S5, 1
       eq         S5, “”, TERMINE
       print      P1, “Nombre d’unites ?\n”
       readline   S1, P0
       chopn      S1, 1
       set        P3[S5], S1
       branch     JARDIN
      TERMINE:
       print      P1, “Statistiques du jardin.\n”
       print      P1, “-----------------------\n\n”
      # Récupération de la liste des fleurs (les clés du hash).
       new        P2, .Iterator, P3
       set        P2, .ITERATE_FROM_START
       set        I1, P2
       print      P1, “Le jardin comporte “
       print      P1, I1
       print      P1, “ essences florales differentes.\n”
       print      P1, “Ce sont :\n”
      # Impression des résultats.
      BOUCLE:
       unless     P2, FIN
       shift      S1, P2
       print      P1, S1
       print      P1, “ au nombre de “
       set        S2, P3[S1]
       print      P1, S2
       print      P1, «\n»
       branch     BOUCLE
      FIN:
       end
      coruscant:~/Langages/asmparrot chris$ parrot jardin.pasm
      Etat des plantations de mon jardin.
      Nom de la fleur ?
      Tulipes
      Nombre d’unites ?
      250
      Nom de la fleur ?
      Iris
      Nombre d’unites ?
      500
      Nom de la fleur ?
      Jonquilles
      Nombre d’unites ?
      350
      Nom de la fleur ?
      Statistiques du jardin.
      -----------------------
      Le jardin comporte 3 essences florales differentes.
      Ce sont :
      Tulipes au nombre de 250
      Iris au nombre de 500
      Jonquilles au nombre de 350
      coruscant:~/Langages/asmparrot chris$

    Clonage des agrégats

    L’instruction clone permet, comme nous l’avons vu, de cloner un agrégat, c’est-à-dire de dupliquer l’agrégat considéré en en créant une nouvelle instance.

       new P1, .ResizableIntegerArray
      # Affectation de valeurs aux éléments de P1.
       set P1[ ], ...
      # Duplication de P1.
       clone P2, P1
      # Affectation de valeurs aux éléments de P2.
       set P2[ ], ...
      # Impression des éléments de P1.
       set S1, P1[ ]
       print S1
      # Impression des éléments de P2.
       set S1, P2[ ]
       print S1

    Les instructions split et join

    Ces deux instructions Parrot se comportent comme les fonctions Perl du même nom.

    Découpage d’une chaîne

    split permet le découper d’une chaîne pour générer un agrégat. Dans l’état actuel du développement, l’implémentation n’autorise qu’une chaîne de caractères comme critère pour déterminer l’emplacement de la scission. Dans sa version définitive, c’est une expression régulière qui la remplacera.

       coruscant:~/Langages/asmparrot chris$ cat decoup.pasm
       new    P0, .Array
       set    P0, 2
       set    S0, «ab..cd»
       split  P0, “..”, S0        # Création de l’agregat.
       set    S0, P0[0]
       print  S0
       print  «\n»
       set    S0, P0[1]
       print  S0
       print  «\n»
       end
      coruscant:~/Langages/asmparrot chris$ parrot decoup.pasm
      ab
      cd
      coruscant:~/Langages/asmparrot chris$

    Union des éléments d’une liste

    join va réunir l’ensemble des éléments d’un agrégat en une chaîne unique, le second argument permet de spécifier quels seront les caractères qui devront être utilisés pour effectuer cette liaison.

      coruscant:~/Langages/asmparrot chris$ cat union.pasm
       new    P0, .Array
       set    P0, 3
       set    P0[0], «Zero»
       set    P0[1], “Un”
       set    P0[2], “Deux”
       join   S0, «, «, P0
       print  S0
        print  “\n”
       end
      coruscant:~/Langages/asmparrot chris$ parrot union.pasm
      Zero, Un, Deux
      coruscant:~/Langages/asmparrot chris$

    Les sous-programmes

    Branchement relatif

    Une référence destination d’un sous-programme implique la finalisation de deux actions. Concrétiser la rupture de séquence pour aller exécuter l’ensemble d’instructions représentatives de la fonction et mémoriser l’adresse courante pour pouvoir, une fois la portion de code terminée, revenir à l’emplacement d’où s’est effectué l’appel, ce retour sera effectif au moment où sera rencontrée l’instruction ret.
    La première manière d’aller exécuter un sous-programme est d’y faire référence en adressage relatif. C’est l’instruction bsr (branch to subroutine) qui permet de réaliser cette action.

      bsr   _nom

    L’adresse du sous-programme est calculée relativement à l’emplacement de l’instruction qui y fait référence.

      coruscant:~/Langages/asmparrot chris$ cat sprel.pasm
      # Branchement en relatif.
       print    «On est dans le programme principal.\n»
       print    «On se branche au sous programme.\n»
       bsr      _sp
       print    «Retour au programme principal.\n»
       end
      _sp:
       print    «On est dans le sous programme.\n»
       ret
      coruscant:~/Langages/asmparrot chris$ parrot sprel.pasm
      On est dans le programme principal.
      On se branche au sous programme.
      On est dans le sous programme.
      Retour au programme principal.
      coruscant:~/Langages/asmparrot chris$

    Branchement absolu

    Le saut à un sous-programme peut s’effectuer en faisant directement référence à son adresse. Une instruction spécifique permet de la connaître et de la sauvegarder dans un registre entier set_adr, et, dans ces conditions, c’est l’instruction jsr (jump to subroutine) qui sera utilisée pour réaliser l’opération.

      set_adr  Ix, _nom   # mémorisation de l’adresse dans un registre entier
      jsr      Ix         # saut à l’adresse contenue dans I0.

    Reprenons le programme ci dessus en effectuant un saut absolu.

      coruscant:~/Langages/asmparrot chris$ cat sabs.pasm
      # Saut en absolu.
       print    «On est dans le programme principal.\n»
       print    «On se branche au sous programme.\n»
       set_addr I1, _sp
       jsr      I1
       print    “Retour au programme principal.\n”
       end
      _sp:
       print    “---> On est dans le sous programme. <---\n”
       ret
      coruscant:~/Langages/asmparrot chris$ parrot sabs.pasm
      On est dans le programme principal.
      On se branche au sous programme.
      ---> On est dans le sous programme. <---
      Retour au programme principal.
      coruscant:~/Langages/asmparrot chris$

    Personnellement, et toujours dans le but de clarifier la présentation, j’utilise des lettres minuscules pour référencer mes sous-programmes et leur nom commence toujours par le caractère blanc souligné (_). Il est ainsi possible de les distinguer immédiatement des étiquettes correspondant à des adresses symboliques et qui sont représentées en majuscules.

    Utilisation des PMC pour gérer les sous-programmes

    Gestion dynamique de l’adresse de retour

    Une fois de plus, les capacités offertes par les PMC vont nous permettre d’aller un peu plus loin dans la construction de sous-programmes. Dans un premier temps, créer un sous-programme peut se faire en déclarant un PMC.

     .const   .Sub P0 = «_sp»

    Le PMC P0 va alors permettre de faire référence au sous-programme qui vient d’être défini par son nom.
    Dans ces conditions, l’adresse de retour sera générée dynamiquement dans le registre P1 au moment de l’appel qui doit se faire par l’intermédiaire de l’instruction invokecc.

      coruscant:~/Langages/asmparrot chris$ cat sp.pasm
       print    «On est dans le programme principal.\n»
       .const   .Sub P0 = «_sp»
       print    «On se branche au sous programme.\n»
       invokecc P0
       print    «On est de retour au programme principal.\n»
       end
    .pcc_sub _sp:
       print    “---> On est dans le sous programme. <---\n”
       returncc
      coruscant:~/Langages/asmparrot chris$ parrot sp.pasm
      On est dans le programme principal.
      On se branche au sous programme.
      ---> On est dans le sous programme. <---
      On est de retour au programme principal.
      coruscant:~/Langages/asmparrot chris$

    Les sous-programmes récursifs

    Pour autoriser la récursivité lors de la conception d’un sous-programme, il faut réaliser en Parrot ce qui est fait en Perl par l’intermédiaire des variables privées (my). Lorsqu’une variable est déclarée my, sa visibilité est réduite au bloc qui la contient. De plus, toute entrée dans le bloc en question aura pour effet d’en créer une nouvelle instance, alors que la sortie du bloc détruira la dernière instance créée. Une des solutions pour réaliser cette fonctionnalité est de se servir de la pile banalisée pour sauvegarder les variables au moment où on rentre dans le sous-programme et de les restaurer en le quittant. En nous basant sur le classique programme des Tours de Hanoi [Hanoi] dont l’écriture en Perl se présente comme suit :

        #!/usr/bin/perl
        # Les tours de Hanoi.
        print «Combien de disques ?\n»;
        $x = <STDIN>;
        hanoi($x, «a», «b», «c»);
    
        sub hanoi {
            my ($n, $x, $y, $z) = @_;
            return if $n == 0;
            hanoi($n-1, $x, $z, $y);
            print “De $x vers $z\n”;
            hanoi($n-1, $y, $x, $z);
        }

    Il est possible de réaliser, sans trop de difficulté, la même chose en Parrot en suivant le modèle défini par le programme que nous venons de voir.

      coruscant:~/Langages/asmparrot chris$ cat hanoi.pasm
      # I0 : Nombre de disques
      # S1 : Nom de la première tour (a).
      # S2 : Nom de la seconde tour (b).
      # S3 : Nom de la troisième tour (c).
       getstdin   P0
       getstdout  P1
       print      P1, «Combien de disques ?\n»
       readline   S0, P0
       set        I0, S0
       set        S1, «a»
       set        S2, «b»
       set        S3, «c»
       bsr        _hanoi
       end
      # I0, S1, S2 et S3 sont privées.
      _hanoi:
      # Sauvegarde des variables privées dans la pile banalisée.
       save      I0
       save      S1
       save      S2
       save      S3
       eq        I0, 0, OUT
       dec       I0
       exchange  S2, S3
       bsr       _hanoi
       print     P1, «De «
       print     P1, S1
       print     P1, « vers «
       print     P1, S2
       print     P1, «\n»
       exchange  S1, S3
       exchange  S2, S3
       bsr       _hanoi
      OUT:
      # Restauration des variables privees.
       restore   S3
       restore   S2 
    
       restore   S1
       restore   I0
       ret
      coruscant:~/Langages/asmparrot chris$ parrot hanoi.pasm
      Combien de disques ?
      3
      De a vers c
      De a vers b
      De c vers b
      De a vers c
      De b vers a
      De b vers c
      De a vers c
      coruscant:~/Langages/asmparrot chris$

    Au moment où on entre dans le sous-programme, les quatre instructions :

      save  I0
      save  S1
      save  S2
      save  S3

    permettent de sauvegarder dans la pile banalisée la valeur des variables concernées par la récursion r avant de lancer la récursion r + 1. Et lorsqu’on sort du sous-programme, à la fin de la récursion r + 1, les quatre instructions :

      restore S3
      restore S2
      restore S1
      restore I0

    effectuent l’opération inverse, à savoir rendre aux variables concernées la valeur qu’elles avaient lors de la récursion r.

    Le passage d’arguments

    Dans le programme que nous venons de voir, le passage d’arguments était géré par le concepteur. C’était à lui de décider par quel registre était transmise une donnée particulière destinée à la fonction. Parrot propose quatre instructions permettant de s’affranchir de cette contrainte. Le programme appelant va pouvoir au moyen de l’instruction set_args spécifier la liste des arguments à transmettre au sous-programme, lequel pourra les récupérer par get_params.

      set_args      «(desc0, desc1, .... , descn)», ARG0, ARG1, .... , ARGn
      get_params    «(desc0, desc1, .... , descn)», ARG0, ARG1, .... , ARGn

    Les descripteurs, un par valeur à transmettre, sont des entiers qui vont permettre de contrôler le type de l’argument correspondant (valeur explicite ou référence à un registre). Si la valeur est mise à 0, c’est l’assembleur qui se charge du calcul en fonction du paramètre correspondant.

      coruscant:~/Langages/asmparrot chris$ cat params.pasm
        .const   .Sub P0 = «_sp»
         print    «Appel du sous programme.\n»
         set I0, 100
      # Premier argument passe par un registre. 
    
      # Second et troisième argument explicite.
         set_args «(0,0,0)», I0, 200, «Bonjour»
         invokecc P0
         print    “Retour au programme principal.\n”
         end
      .pcc_sub _sp:
         print    “---> On est dans le sous programme. <---\n”
      # Le premier argument est récupéré dans I10.
      # Le second argument est récupéré dans I20.
      # Le troisième argument est récupéré dans S10
         get_params «(0,0,0)», I10, I20, S10
         print “Premiere valeur : “
         print I10
         print “\nSeconde valeur : “
         print I20
         print “\nTroisieme valeur : “
         print S10
         print «\n»
         returncc
      coruscant:~/Langages/asmparrot chris$ parrot params.pasm
      Appel du sous programme.
      ---> On est dans le sous programme. <---
      Premiere valeur : 100
      Seconde valeur : 200
      Troisieme valeur : Bonjour
      Retour au programme principal.
      coruscant:~/Langages/asmparrot chris$

    C’est de la même manière que la fonction retournera au programme appelant les résultats qui auront été calculés.

      set_returns   «(desc0, desc1, .... , descn)», ARG0, ARG1, .... , ARGn
      get_results   “(desc0, desc1, .... , descn)”, ARG0, ARG1, .... , ARGn

    Les modules Parrot Byte Code

    Déclaration d’un sous-programme global

    Nous avons dit qu’un sous-programme dont le nom est préfixé par la déclaration .pcc_sub est stocké dans un cache global, son adresse pourra donc être retrouvée au moyen de l’instruction find_global. Cette option alliée à l’instruction load_bytecode qui permet d’aller chercher des lignes de code sur un fichier externe, donne la possibilité de créer des modules et d’y faire appel au moment voulu.

      coruscant:~/Langages/asmparrot chris$ cat mess.pasm
      # mess.pasm
      # Sous-programme catalogue.
        .pcc_sub    _sub:
        print       “---------->  On est dans le sous programme.\n”
        returncc
      coruscant:~/Langages/asmparrot chris$ cat main.pasm
      # main.pasm
      # Programme principal qui va faire référence au sous programme
      # mess.pasm préalablement archive dans un fichier.
        _main:
        print         «***** On entre dans main.\n»
        load_bytecode «mess.pasm»
        find_global   P0, «_sub»
        invokecc      P0
        print         “De retour dans main.\n”
        end
      coruscant:~/Langages/asmparrot chris$ parrot main.pasm
      ***** On entre dans main.
      ---------->  On est dans le sous programme.
      De retour dans main.
      coruscant:~/Langages/asmparrot chris$

    Création de modules en byte code

    Nous avons vu au moment de la présentation du système qu’il était possible de stocker les programmes opérationnels sous forme de modules précomptés. C’est l’option -–o qui permet de le faire. Application à un petit programme qui permet de calculer les 13 premières lignes de l’énumération de Conway [look and say]. Tout d’abord, on crée le module. Celui-ci va être stocké dans un fichier devant impérativement comporter l’extension .pbc (Parrot byte code). Puis, le programme obtenu est soumis à l’interpréteur.

      coruscant:~/Langages/asmparrot chris$ ls
      enum.pasm
      coruscant:~/Langages/asmparrot chris$ cat enum.pasm
       getstdout  P0
       set        S0, «1»
       print      P0, S0
       print      P0, «\n»
       set        I1, 13
      CALCUL:
       set        I0, 0
       set        S4, «»
       substr     S1, S0, 0, 1
      EXPLORE:
       inc        I0
       substr     S0, S0, 1
       eq         S0, «», FINI
       substr     S2, S0, 0, 1
       eq         S1, S2, EXPLORE
       bsr        _construire
       set        S1, S2
       set        I0, 0
       branch     EXPLORE
      FINI:
       bsr       CONSTRUIRE
       print      P0, S4
       print      P0, “\n”
       set        S0, S4
       dec        I1
       ne         I1, 0, CALCUL
       end
      _construire:
       set        S3, I0
       concat     S4, S3
       concat     S4, S1
       ret
      coruscant:~/Langages/asmparrot chris$ parrot -o enum.pbc enum.pasm
      coruscant:~/Langages/asmparrot chris$ ls
      enum.pasm enum.pbc
      coruscant:~/Langages/asmparrot chris$ parrot enum.pbc
      1
      11
      21
      1211
      111221
      312211
      13112221
      1113213211
      31131211131221
      13211311123113112211
      11131221133112132113212221
      3113112221232112111312211312113211
      1321132132111213122112311311222113111221131221
      11131221131211131231121113112221121321132132211331222113112211
      coruscant:~/Langages/asmparrot chris$

    Création de sous-programmes précompilés

    Dans le même ordre d’idées, on peut utiliser cette fonctionnalité pour créer des modules externes précompilés qui seront sollicités au moment voulu.

      coruscant:~/Langages/asmparrot chris$ ls
      main.pasm mess.pasm
      coruscant:~/Langages/asmparrot chris$ cat mess.pasm
      # mess.pasm
      # Sous-programme catalogue.
        .pcc_sub    _sub:
        print       “---------->  On est dans sub.\n\n”
        returncc
      coruscant:~/Langages/asmparrot chris$ parrot –o mess.pbc mess.pasm
    
      coruscant:~/Langages/asmparrot chris$ ls
      main.pasm mess.pasm mess.pbc
      coruscant:~/Langages/asmparrot chris$ cat main.pasm
      # main.pasm
      # Programme principal qui va faire référence au sous-programme
      # mess.pasm préalablement précompilé dans un fichier.
      _main:
        print       «***** On entre dans main.\n»
      load_bytecode «mess.pbc»
      find_global   P0, «_sub»
      invokecc      P0
      print         “De retour dans main.\n”
      end
      coruscant:~/Langages/asmparrot chris$ parrot main.pasm
      ***** On entre dans main.
      ---------->  On est dans sub.
      De retour dans main.
      coruscant:~/Langages/asmparrot chris$

    Just another Perl Hacker

    Application à un programme Parrot qui affichera ce célèbre mantra. Il se compose de quatre fichiers sous-programme J.pasm, A.pasm, P.pasm, H.pasm et d’un programme principal japh.pasm.

       coruscant:~/Langages/asmparrot chris$ cat J.pasm
          .pcc_sub _sub:
          concat  S2, «Just «
          returncc
      coruscant:~/Langages/asmparrot chris$ cat A.pasm
          .pcc_sub _sub:
          concat  S2, «another «
          returncc
      coruscant:~/Langages/asmparrot chris$ cat P.pasm
          .pcc_sub _sub:
          concat  S2, «Perl «
          returncc
      coruscant:~/Langages/asmparrot chris$ cat H.pasm
          .pcc_sub _sub:
          concat  S2, «hacker.»
          returncc
      coruscant:~/Langages/asmparrot chris$ cat japh.pasm
      _main:
          set             I0, 0
      B:
          substr          S0, «JAPH», I0, 1
          concat          S1, S0, «.pasm»
          load_bytecode   S1
          find_global     P0, «_sub»
          invokecc        P0
          inc             I0
          lt              I0, 4, B
          print           S2
          end
      coruscant:~/Langages/asmparrot chris$ parrot japh.pasm
      Just another Perl hacker.
      coruscant:~/Langages/asmparrot chris$

    Les fichiers

    Ouverture d’un fichier

    La gestion des descripteurs de fichiers ressemble fortement à celle de Perl. L’ouverture d’un fichier consiste à affecter le descripteur gestionnaire par l’intermédiaire d’un PMC, puis à utiliser les fonctions d’entrée sortie qui feront référence au PMC en question.
    L’ouverture d’un fichier se réalise au moyen de l’instruction :

      open  Px, «nom.txt», «sens»

    Comme en Perl, le sens sera :

    • «<» pour un fichier ouvert en lecture seule ;
    • «>» pour un fichier ouvert en écriture seule ;
    • «+<» pour un fichier ouvert en lecture écriture ;
    • «>>» pour un fichier existant ouvert en réécriture.

    Écriture dans un fichier

    L’utilisation d’un PMC pour visualiser les informations lues va nous permettre de modifier le programme afin de créer un nouveau fichier, et ce, sans toucher le corps du programme. Il suffit de changer l’affectation du PMC de :

       getstdout P1

    en :

      open  P1, «nouveau.txt, «>»

    Pour que les informations soient dirigées sur le fichier ainsi ouvert.

      coruscant:~/Langages/asmparrot chris$ ls
      dial.txt file.pasm
      coruscant:~/Langages/asmparrot chris$ cat file.pasm
          open      P3, «dial.txt», «<»
          open      P1, «nouveau.txt», «>»
      LIRE:
          readline S0, P3
          unless    S0, FIN
          print     P1, S0
          branch    LIRE
      FIN:
          end
      coruscant:~/Langages/asmparrot chris$ parrot file.pasm
    
      coruscant:~/Langages/asmparrot chris$ ls
      dial.txt file.pasm nouveau.txt
      coruscant:~/Langages/asmparrot chris$ cat nouveau.txt
      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!
      coruscant:~/Langages/asmparrot chris$

    Assombrissement en Parrot

    Pourquoi ne pas reprendre l’idée de l’assombrissement de $A++ [$A++] pour la retravailler en Parrot ? Il suffirait d’en changer le nom de $A++ en inc I0. Le but restant toujours le même, incrémenter le registre I0 sans que cela se sache. Écrit de manière traditionnelle, le programme se présente sous une forme particulièrement évidente.

      coruscant:~/Langages/asmparrot chris$ cat inc.pasm
          getstdout   P1
          set         I1, 10
          print       P1, I1
      # Début séquence de base
          inc         I1
      # Fin séquence
          print       P1, I1
          print       P1, «\n»
          end
      coruscant:~/Langages/asmparrot chris$ parrot inc.pasm
      11
      coruscant:~/Langages/asmparrot chris$

    Il pourrait être obscurci sous la forme :

      coruscant:~/Langages/asmparrot chris$ cat inc.pasm
          getstdout   P1
          set         I1, 10
          print       P1, I1
      # Début séquence inc I1
          set         I2, 1
      CALC:
          bxor        I1, I2
          band        I3, P2, I1
          if          I3, FINI
          shl         I2, 1
          branch      CALC
      FINI:
      # Fin séquence inc I1
          print       P1, I1
          print       P1, “\n”
          end
      coruscant:~/Langages/asmparrot chris$ parrot inc.pasm
      11
      coruscant:~/Langages/asmparrot chris$

    Pour terminer

    L’assembleur n’a jamais eu l’audience qu’il méritait. Force est de reconnaître que les dialectes qui étaient proposés n’avaient rien de particulièrement attirant. Souvent, l’apprentissage de la programmation en « langage machine », comme on avait l’habitude de dire, se résumait à une longue présentation de la structure de la plate-forme, suivie d’un interminable catalogue des formats d’instructions et des modes d’adressage. Cette approche n’avait rien de vraiment passionnant et, surtout, imposait un éternel recommencement. La machine virtuelle qui a été présentée, aborde les choses sous un aspect totalement nouveau. Si l’ombre de Perl plane sur la manière qu’elle adopte pour représenter les choses, et, par là même, en simplifie l’utilisation, il est impossible de nier le fait qu’il s’agisse réellement d’un langage d’assemblage.

    Références:

    Retrouvez cet article dans : Linux Magazine 98

    Posté par admin-web (fabrice) | Signature : Christian Aperghis-Tramoni | Article paru dans Creative Commons License

    Laissez une réponse

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


    • Il y a actuellement

    • 465 articles/billets en ligne.