Le langage PIR, seconde partie
Signature : | Mis en ligne le : 03/08/2010
Catégorie(s) :
  • GNU/Linux Magazine
  • | Domaine :
    Commentez creative commons

    Retrouvez cet article dans : Linux Magazine 123

    Après une (longue) présentation des types de données de PIR, nous allons aborder une partie plus attractive dans l’apprentissage d’un langage, en l’occurrence, la programmation.

    De nombreux exemples émailleront cette présentation, tous les programmes présentés peuvent être téléchargés sur mon site : http://www.dil.univ-mrs.fr/~chris/Documents/progs02.pod

    1 Introduction

    Les types disponibles dans le langage Parrot Intermediate Representation n’ont maintenant plus aucun secret pour vous, il est donc grand temps de se pencher sur les instructions et les particularités de ce langage.

    Nous verrons à travers plusieurs exemples qu’il offre des perspectives intéressantes tant au niveau de sa syntaxe que de la manière de l’utiliser.

    2 Les entrées / sorties

    Intéressons-nous dans un premier temps aux échanges avec l’environnement.

    2.1 STDIN et STDOUT

    Nous avons déjà vu et utilisé l’instruction print qui permet d’afficher une information sur l’écran (STDOUT).

    On dispose comme en Perl 5.10 de l’instruction say qui ajoute un \n en fin de ligne.

      coruscant chris$ cat say.pir 

      .sub ‘main’ :main

        $S10 = "Linux"

        $S11 = "Magazine"

        say $S10

        say $S11

      .end

      coruscant chris$ parrot say.pir 

      Linux

      Magazine

      coruscant chris$

    Par contre, c’est par l’intermédiaire d’un PMC spécifique getstdin que le programme accédera aux informations introduites au clavier.

    L’instruction permettant de lire des informations sur une entrée se présente sous deux formes :

      chaine = read DESCRIPTEUR, nombre

    qui permet de lire un nombre donné de caractères, et

      chaine = readline DESCRIPTEUR

    qui permet de lire la totalité d’une ligne jusqu’au \n compris. coruscant chris$ cat lecture.pir .sub "Lecture" :main .local pmc STDIN STDIN = getstdin print "Quel est votre prenom ? " $S0 = readline STDIN chopn $S0, 1 print "Bonjour " print $S0 print " !\n" .end coruscant chris$ parrot lecture.pir Quel est votre prenom ? Christian Bonjour Christian ! coruscant chris$

    Comme nous en avons l’habitude en Perl, l’instruction chopn nous permet, lorsque cela s’avère nécessaire, de supprimer le caractère \n à la fin de la chaîne lue.

    2.2 Les fichiers

    L’accès à un fichier nécessite lui aussi la création d’un PMC descripteur. Il sera initialisé au moyen de la directive open à laquelle sont transmis deux paramètres. Le premier représente le nom du fichier à ouvrir sous forme d’une chaîne de caractères, le second dans quel sens sera ouvert le fichier, considérez ‘r’ pour l’ouvrir en lecture et ‘w’ pour l’ouvrir en écriture.

      coruscant chris$ cat fichier.pir

      .sub "Fichier" :main

      .local string texte

      texte = <<"FIN"

      Le nombre pi, note par la lettre grecque du meme nom,

      toujours en minuscule est le rapport constant entre

      la circonference d’un cercle et son diametre.

      Il est appele aussi constante d’Archimede.

      Des valeurs approchees de pi courantes sont

        Approximativement 3,1416

        Approximativement sqrt(10)

        Approximativement 22/7.

      FIN

      

        .local string Nom

        .local pmc STDIN

        STDIN = getstdin

        print “Quel est le nom du fichier ? “

        Nom = readline STDIN

        chopn Nom, 1

      # Ouverture du fichier en ecriture.

        $P0 = open Nom, ‘w’

      # Ecriture du fichier.

        print $P0, texte

        close $P0

     # Ouverture du fichier en lecture.

        $P0 = open Nom, ‘r’

        say “Relecture du fichier.”

        $I0 = 0

      LECTURE:

      # Lecture du fichier.

        $S0 = readline $P0

        unless $P0 goto FINI

        $I0 += 1

        print “Ligne “

        print $I0

        print " : "

        print $S0

        goto LECTURE

      FINI:

        say “Effacement du fichier.”

        $P1 = new "OS"

        $P1.”rm”(Nom)

      .end

      coruscant chris$ parrot fichier.pir 

      Quel est le nom du fichier ? NombrePi.txt

      Relecture du fichier.

      Ligne 1 :   Le nombre pi, note par la lettre grecque du meme nom,

      Ligne 2 :   toujours en minuscule est le rapport constant entre

      Ligne 3 :   la circonference d’un cercle et son diametre.

      Ligne 4 :   Il est appele aussi constante d’Archimede.

      Ligne 5 :   Des valeurs approchees de pi courantes sont

      Ligne 6 :     Approximativement 3,1416

      Ligne 7 :     Approximativement sqrt(10)

      Ligne 8 :     Approximativement 22/7.

      Effacement du fichier.

      coruscant chris$

    3 Les informations système

    Intéressons-nous pour commencer aux informations concernant notre système d’exploitation auxquelles PIR peut avoir accès. Pour cela, on dispose aussi d’une instruction sysinfo.

    Cette instruction fait référence à un fichier de macros sysinfo.pasm qui se trouve dans le répertoire parrot-1.x.0/runtime/parrot/include/

      coruscant chris$ cat systeme.pir

      .include ‘sysinfo.pasm’

      .sub main :main

        $I0 = sysinfo .SYSINFO_PARROT_INTSIZE

        print “Taille des entiers : “

        say $I0

        $I0 = sysinfo .SYSINFO_PARROT_FLOATSIZE

        print “Taille des flottants : “

        say $I0

        $I0 = sysinfo .SYSINFO_PARROT_POINTERSIZE

        print “Taille des references : “

        say $I0

        $S0 = sysinfo .SYSINFO_PARROT_OS

        print “Systeme d’exploitation : “

        say $S0

        $S0 = sysinfo .SYSINFO_PARROT_OS_VERSION

        print “Version du systeme d’exploitation : “

        say $S0

        $S0 = sysinfo .SYSINFO_CPU_ARCH

        print “Architecture du processeur : “

        say $S0

      .end

      coruscant chris$ parrot systeme.pir

      Taille des entiers : 4

      Taille des flottants : 8

      Taille des references : 4

      Systeme d’exploitation : darwin

      Version du systeme d’exploitation :

           Darwin Kernel Version 9.7.0: Tue Mar 31 22:52:17 PDT 2009;

           root:xnu-1228.12.14~1/RELEASE_I386

      Architecture du processeur : i386

      coruscant chris$

    4 Les opérateurs arithmétiques

    Nous l’avons vu dans l’article précédent, PIR dispose des opérateurs arithmétiques nécessaires pour effectuer toutes les opérations de base exigées par la programmation. La nouveauté est que toutes les opérations peuvent être utilisées au moyen d’un opérateur d’assignement (+=, -=, *=, /=, >>=, ...).

      coruscant chris$ cat assigne.pir 

      .sub main

        .local int nombre

        .const int dix = 10

        nombre = 25

        nombre += dix

        say nombre

        $I3 = 500

        $I3 /= dix

        say $I3

        nombre = 1

        nombre <<= 4

        say nombre

      .end

      coruscant chris$ parrot assigne.pir 

      35

      50

      16

      coruscant chris$

    Nous avons déjà dit que la seule contrainte est de ne pouvoir effectuer qu’une seule opération à la fois. Toute expression arithmétique doit donc être décomposée en autant de calculs élémentaires que nécessaire.

    Par contre, PIR est aussi parfaitement outillé en ce qui concerne les instructions capables de réaliser des fonctions d’un certain niveau de complexité (factorielle, exponentiation, ...).

      coruscant chris$ cat facto.pir 

      .sub _main

        .local int nombre, factorielle

        .local pmc STDIN

        STDIN = getstdin

        print “Quel est le nombre ? "

        $S0 = readline STDIN

        chopn $S0, 1

        nombre = $S0

        factorielle = fact nombre

        print “La factorielle de “

        print nombre

        print “ est egale a : “

        say factorielle

      .end

      coruscant chris$ parrot facto.pir

      Quel est le nombre ? 14

      La factorielle de 14 est egale a : 1278945280

      coruscant chris$

    5 Les labels

    5.1 Généralités

    PIR, nous l’avons dit, ne dispose pas d’instructions évoluées de type boucle. L’instruction goto honnie par des générations de programmeurs, est de ce fait incontournable, et comme dans tout langage autorisant l’utilisation du goto, chaque instruction peut être étiquetée.

    Les étiquettes sont représentatives d’adresses symboliques, elles seront utilisées comme destination dans les ruptures de séquence conditionnelles ou inconditionnelles.

    Dès qu’il est question de cette instruction tant décriée, je recommande de consulter l’article paru dans Linux Magazine n° 72 [goto].

    En PIR, une étiquette peut se présenter sous deux formes :

      LABEL:

      _LABEL:

    Les caractères disponibles sont ceux autorisés pour les noms de variables, à savoir les lettres majuscules ou minuscules, les chiffres et le blanc souligné. Toutefois, pour des raisons de lisibilité, on recommande toujours d’identifier les étiquettes par des lettres majuscules et de les présenter sur une ligne vide.

        Instruction ...

        Instruction ...

      ETIQUETTE:

        Instruction ...

        Instruction ...

    Une étiquette servant d’adresse de destination chaque fois qu’une rupture de séquence conditionnelle ou inconditionnelle apparaît, cette représentation permet de mettre en évidence le début du bloc d’instructions susceptible d’être référencé lors d’un branchement. Ainsi, le programme gagne en clarté et en lisibilité.

    5.2 Règles d’utilisation

    Les règles qui régissent les étiquettes sont les suivantes :

    ● les étiquettes peuvent être locales ou globales ;

    ● un nom d’étiquette locale doit impérativement commencer par une lettre ;

    ● un nom d’étiquette globale commence toujours par un blanc souligné (_) ;

    ● un nom d’étiquette doit être unique dans son unité de compilation (étiquette locale) ;

    ● un label local ne sera accessible que dans l’unité de compilation dans laquelle il a été défini.

    Cette dernière remarque nous donne donc la possibilité d’utiliser des noms de label locaux identiques dans des unités de compilation distinctes.

    6 Les instructions de contrôle

    Ces instructions vont permettre de rompre de manière conditionnelle ou inconditionnelle le déroulement séquentiel du programme.

    PIR devant rester assez proche de la machine virtuelle et des instructions de base, nous avons déjà remarqué qu’il ne dispose pas de structures complexes (for, while ou until).

    6.1 La rupture de séquence inconditionnelle goto

    C’est l’instruction de base. Elle permet de dérouter le programme de manière arbitraire vers une adresse donnée repérée par son label.

      coruscant chris$ cat goto.pir 

      .sub "Application goto" :main

        goto L1

      L2:

        print "Second affichage.\n"

        end

      L1:

        print "Premier affichage. \n"

        goto L2

      .end

      coruscant chris$ parrot goto.pir 

      Premier affichage. 

      Second affichage.

      coruscant chris$

    Cette instruction est incontournable, mais il ne faut pas en abuser et son utilisation doit respecter les règles de la programmation. Vu sous cet angle, le programme que nous venons d’écrire n’aurait jamais dû exister.

    6.2 La rupture de séquence conditionnelle

    Comme en PASM elle ne peut être réalisée qu’en utilisant les instructions if ou unless et leur comportement est exactement le même qu’en Perl ou PASM.

    Ces instructions (if ou unless) seront contrôlées par le résultat de l’évaluation d’une expression booléenne qui peut prendre plusieurs formes.

    Première forme : le test d’une variable. Dans ce cas, le résultat sera considéré comme faux si la variable contient la valeur undef vraie dans le cas contraire.

      .sub condition

        .local int x

        * * *

        x = ligne

        if x goto NU

          $S0 = “x est undef.\n”

          goto FIN

      NU:

        $S0 = “x n’est pas undef.\n”

      FIN:

        print S0

        * * *

      .end

    C’est ce type de test qui va nous permettre, lors de l’accès en lecture à un fichier, d’en détecter la fin ([Ctrl]+[D]).

    Seconde forme : l’évaluation d’une expression booléenne au moyen des opérateurs de comparaison <, <=, ==, !=, >, >=.

      .sub condition

        .local int x

        * * *

        if x < 10 goto INF

        $S0 .= “x est superieur ou egal a 10.\n”

        goto FIN

      INF:

        $S0 .= “x est inferieur a 10.\n”

      FIN:

        print $S0

        * * *

      .end

    Troisième forme : l’évaluation d’un objet PMC.

    Nous avons déjà évoqué le fait qu’un PMC est représentatif d’une classe, il peut donc avoir été déclaré mais ne pas avoir été instancié.

      .sub condition

        .local pmc chaine

        # chaine = new ‘String’

        # chaine = "Salut a tous."

        if null chaine goto NE

          print "Le pmc existe.\n"

          goto FIN

      NE:

        print "Le pmc n’existe pas.\n"

      FIN:

      .end

    Dans ce dernier exemple, le fait de laisser les deux lignes commentées permet de déclarer le PMC mais pas de l’instancier. Il n’a de ce fait pas d’existence et son test donnera un résultat faux. Pour que le résultat du test soit vrai, il suffit de décommenter les deux lignes d’instanciation.

    7 La réalisation des boucles

    7.1 Les boucles non bornées while et until

    Elles doivent être construites au moyen de :

    if pour le until ;

    unless pour le while.

    Si nous considérons le programme Perl construit autour d’une boucle while :

      $x = 0;

      while ($x <= 5) {

        print “Valeur : $x.\n”;

        $x += 1

      }

      print “Fin de la boucle.\n”;

    Le programme effectuant la même opération en PIR sera le suivant :

      coruscant chris$ cat while.pir

      .sub "while" :main

        .local int x

        x = 0

        $S0 = "Valeur : "

      DEBUT:

          unless x < 5 goto FIN

          print $S0

          print x

          print “.\n”

          x += 1

          goto DEBUT

      FIN:

        print “Fin de la boucle. \n"

      .end

      coruscant chris$ parrot while.pir

      Valeur : 0.

      Valeur : 1.

      Valeur : 2.

      Valeur : 3.

      Valeur : 4.

      Fin de la boucle. 

      coruscant chris$ 

    Ecrivons un programme identique, mais utilisant cette fois une boucle until :

      $x = 0;

      until ($x == 5) {

        print “Valeur : $x.\n”;

        $x += 1

      }

      print “Fin de la boucle\n”;

    Sa traduction en PIR sera :

      coruscant chris$ cat until.pir

      .sub "until" :main

        .local int x

        x = 0

        $S0 = "Valeur : " 

     DEBUT:

        if x == 5 goto FIN

          print $S0

          print x

          print ".\n"

          x += 1

          goto DEBUT

      FIN:

      print “Fin de la boucle. \n"

      .end

      coruscant chris$ parrot until.pir

      Valeur : 0.

      Valeur : 1.

      Valeur : 2.

      Valeur : 3.

      Valeur : 4.

      Fin de la boucle. 

      coruscant chris$

    7.2 La boucle bornée for

    Prenons une nouvelle fois comme référence un programme Perl :

      for ($i=0; $i<5; $i++) {

        print “Valeur : $i.\n”;

      }

      print “Fin de la boucle\n”;

    Il sera écrit en PIR :

      coruscant chris$ cat for.pir

      .sub "until" :main

        .local int x

        x = 0

      DEBUT:

        unless x < 5 goto FIN

          print "Valeur : "

          print x

          print ".\n"

          x += 1

          goto DEBUT

      FIN:

        print "Fin de la boucle. \n"

      .end

      coruscant chris$ parrot for.pir

      Valeur : 0.

      Valeur : 1.

      Valeur : 2.

      Valeur : 3.

      Valeur : 4.

      Fin de la boucle. 

      coruscant chris$ 

    Ce qui est démontré dans ces quelques exemples, c’est qu’il est parfaitement possible de limiter l’utilisation des ruptures de séquence au strict minimum nécessaire.

    7.3 Macros de haut niveau

    Pour ceux qui le souhaitent, il existe dans le répertoire parrot-1.x.0/runtime/parrot/include un fichier qui s’appelle hllmacros.pir.

    Ce fichier met à la disposition des usagers un ensemble de macros permettant d’émuler un certain nombre d’instructions de haut niveau.

      .macro IfElse(conditional, true, false)

      .macro While(conditional, code)

      .macro DoWhile(code, conditional)

      .macro For(start, conditional, cont, code)

      .macro Foreach(name, array, code)

    Elles sont accompagnées de nombreux exemples et leur utilisation facilite la programmation pour les usagers qui le souhaitent.

    8 Retour sur les unités de compilation

    Nous utilisons fréquemment le terme " Unité de compilation " et nous l’avons déjà défini comme étant un morceau de code qui représente une entité de programmation.

    Dans certains cas de figure, il peut être utilisé pour désigner la totalité du fichier source, mais dans la majorité des cas, il ne désignera qu’un groupe de lignes représentatives d’un ensemble compact d’instructions.

    Toutefois, cette organisation devra respecter certaines contraintes sur lesquelles nous allons revenir en détail.

    Pour commencer, prenons le programme suivant qui va calculer et imprimer la factorielle d’un nombre. Il a été écrit dans une unique unité de compilation et sans appel de sous-programme.

      coruscant chris$ cat factorielle.pir 

      .sub "facto" :main

        .local string chaine 

        .local int nombre 

        .local int factorielle

        .local pmc STDIN

        print “Quel est le nombre : “

        STDIN = getstdin

        chaine = readline STDIN

        nombre = chaine

        factorielle = 1

        print “La factorielle de “

        print nombre

      BOUCLE:

        factorielle *= nombre

        nombre -= 1

       if nombre goto BOUCLE

       print “ est egale a “

        print factorielle

       print “\n”

      .end

      coruscant chris$ parrot factorielle.pir 

      Quel est le nombre : 10

      La factorielle de 10 est egale a 3628800

      coruscant chris$

    Une première évolution évidente nous conduit à la création d’un sous-programme tout en conservant une unique unité de compilation.

    Cette modification nous conduit au code suivant :

      coruscant chris$ cat factorielle.pir 

      .sub "Factorielle" :main

        .local string chaine 

        .local int nombre 

        .local int factorielle

        .local int compteur

        .local pmc STDIN

        print "Quel est le nombre : "

        STDIN = getstdin

        chaine = readline STDIN

        nombre = chaine

      # Appel du sous-programme.

        bsr FACTORIELLE

        print “La factorielle de “

        print nombre

        print “ est egale a “

        print factorielle

        print “\n”

        end

      # sous-programme.

      FACTORIELLE:

        factorielle = 1

        compteur = nombre

      BOUCLE:

        factorielle *= compteur

        compteur -= 1

        if compteur goto BOUCLE

        ret

      .end

      coruscant chris$  parrot factorielle.pir

      Quel est le nombre : 5

      La factorielle de 5 est egale a 120

      coruscant chris$

    Dans ces lignes de code, l’ensemble d’instructions, qui commence à l’étiquette FACTORIELLE: et qui se termine par l’instruction ret, représente du code réutilisable en tant que fonction.

    Ce type d’approche va néanmoins poser un certain nombre de problèmes.

    Tout d’abord en termes d’interface entre le programme appelant et le sous-programme. Ici, il n’y a qu’un argument à transmettre au sous-programme FACTORIELLE. Ce passage se fait par nom en utilisant la variable (nombre) de l’unité de compilation. Ceci implique que l’appelant doit savoir quel est le nom et quel est le type du paramètre que va récupérer la fonction.

    Le même problème se pose pour la valeur de retour qui se fait par l’intermédiaire de la variable factorielle.

    Le fait que le module principal et le sous-programme doivent se partager la même unité de compilation n’est donc pas une bonne solution. Les deux blocs d’instructions seront analysés et traités comme un unique morceau de code, et devront se partager un environnement commun, en particulier deux PMC sur lesquels nous reviendrons ultérieurement, LexInfo et LexPad.

    La bonne méthode, celle que nous allons présenter maintenant, consiste à déclarer deux sous-sections représentatives de deux unités de compilation. La première qui va regrouper les instructions du sous-programme, la seconde celles du module principal

    9 Les sous-programmes

    Tout programmeur sait qu’il n’est pas envisageable d’écrire une quelconque application sans avoir la possibilité de définir des sous-programmes.

    Dans la majorité des applications, on dispose de lignes de code stockées dans des bibliothèques de fonctions et de modules réutilisables dans de multiples endroits. Le sous-programme représente la base incontournable dans la notion de code réutilisable.

    Cette fonctionnalité est constamment utilisée lorsqu’on écrit du code en PIR. En fait, comme nous l’avons déjà constaté, le langage est entièrement basé sur cette présentation car tout code PIR est un sous-programme qui est déclaré et ne peut exister qu’en tant que tel.

    Il a été dit à plusieurs reprises que le programme de plus haut niveau, le programme principal, est lui-même un sous-programme qu’on a coutume de référencer :main par la suite. D’autres sous-programmes sont créés et appelés pour réaliser l’ensemble des opérations nécessaires.

    C’est aussi l’utilisation de sous-programmes, au sens PIR du terme, qui nous permettra d’écrire des morceaux de code pour déclarer des objets et y attacher des méthodes.

    Nous allons maintenant présenter dans le détail la manière de réaliser et de déclarer les sous-programmes, de quelle façon s’opère la transmission de la liste de paramètres, et enfin, de savoir comment les utiliser au mieux pour développer des applications complexes.

    Nous avons vu que la machine virtuelle Parrot est, au final, destinée à supporter de multiples langages [Langages], chacun d’eux disposant de ses propres conventions et sa propre syntaxe pour gérer la définition et l’appel de ses fonctions.

    Le but de PIR n’étant pas d’être lui-même un langage de haut niveau, il se doit de proposer les outils de base afin que chaque langage qui sera implémenté par son intermédiaire puisse les utiliser pour réaliser ses propres fonctionnalités.

    C’est pour cette raison que la syntaxe de PIR, pour l’utilisation des sous-programmes, est d’une grande simplicité.

    9.1 Les conventions d’appel

    Les PPC (Parrot Calling Conventions) [PPC] décrivent en détail comment la machine virtuelle doit faire référence à un sous-programme, doit gérer la rupture de séquence puis récupérer l’adresse de retour. Elles définissent aussi comment sera transmise la liste des paramètres et de quelle manière seront récupérés les résultats. Elles sont écrites en partie en langage C et en partie en PASM (Parrot Assembly).

    Les détails de fonctionnement d’un sous-programme ou d’une fonction restent cachés à la grande majorité des programmeurs, car ces derniers n’ont généralement pas besoin de les connaître. PIR dispose de multiples constructions pour procéder à cette dissimulation.

    Le principe des Parrot Calling Conventions est basé sur la CPS (Continuation Passing Style) pour transférer le contrôle à un sous-programme et gérer la pile des adresses de retour.

    Si, comme nous venons de le dire, la grande majorité des utilisateurs peuvent totalement ignorer les détails du mécanisme qui gère ces actions, il n’en est pas de même pour tous ceux qui souhaitent en exploiter toutes les capacités et qui, de ce fait, doivent en connaître le fonctionnement détaillé.

    9.2 Appels de sous-programmes

    Il ne faut pas se le cacher, la gestion d’un sous-programme dissimule une grande complexité.

    Au niveau de la structure de base de la machine virtuelle, cela va se traduire par l’instanciation d’un PMC sous-programme. Il est ensuite nécessaire de créer un PMC de continuation pour gérer l’adresse de retour à la fin du sous-programme, puis transmettre la liste d’arguments, résoudre l’adresse symbolique représentée par le nom du sous-programme en question et, en fin de compte, renvoyer les résultats et les mémoriser aux emplacements qui auront été spécifiés au moment de l’appel (variables ou registres).

    Tout ce travail étant destiné à la gestion d’une seule et unique instruction, l’appel à la fonction ne prend pas en compte la complexité de l’exécution du sous-programme lui-même. Il est donc évident que l’instruction d’appel à un sous-programme apparaîtra incroyablement simple en comparaison de la difficulté du travail sous-jacent.

    À la base, l’appel d’un sous-programme en PIR est très proche, bien que moins flexible, de ce qu’on a l’habitude de voir dans n’importe quel langage de haut niveau.

    Nous avons aussi vu dans nos précédents exemples que la syntaxe de PIR est particulièrement prolixe. Cet état de choses présente certains avantages.

    Ainsi, l’exemple suivant montre comment extraire la référence à un sous-programme nom de la table des symboles globaux pour la stocker dans un PMC $P1 afin d’y faire référence.

      find_global $P1, "nom"

        .begin_call

        .arg Valeur1

        .arg Valeur2

        .call $P1

        .result $I0

      .end_call

    L’ensemble des instructions que l’on trouve entre les deux directives .begin_call et .end_call se comporte comme un bloc. La directive .arg positionne et transmet les arguments de la liste d’appel, et en fin de compte, l’instruction .call fait référence au PMC $P1 préalablement positionné. Enfin, l’instruction .result nous indique que le résultat renvoyé par le sous-programme sera stocké dans le registre $I0.

    Nous utiliserons ultérieurement la fonctionnalité que nous venons de décrire.

    9.3 Déclaration de sous-programmes

    La définition de l’instruction d’appel d’un sous-programme n’est qu’une partie du problème. Il est aussi important de savoir déclarer le sous-programme et en écrire les lignes de code.

    Nous l’avons déjà vu à de nombreuses reprises, c’est la directive .sub qui permet de déclarer un sous-programme, en fait, une unité de compilation, et la directive .end qui en indique la fin.

     .sub "Main" :main

        * * *

      .end

    La description et le typage des paramètres se font au moyen de la directive .param. Cette dernière, outre le fait qu’elle définit les paramètres, crée aussi pour chacun d’eux une variable locale.

      .param int c

    Enfin, la directive .return indique que le sous-programme se termine et, optionnellement, positionne la valeur qui sera retournée en fin de calcul.

      .return (valeur)

    Si nous reprenons l’exemple de la factorielle en appliquant ce qui vient d’être dit, le sous-programme FACTORIELLE devient une unité de compilation au même titre que le programme principal main. Dans ces conditions, PIR résoudra le nom des diverses unités de compilation de la même manière que sont traitées les étiquettes dans un programme.

      coruscant chris$ cat factorielle.pir 

      # sous-programme.

      .sub factoriel

        .param int nombre

        .local int factorielle

        factorielle = 1

      BOUCLE:

        if nombre == 1 goto FIN

          factorielle *= nombre

          nombre -= 1

          branch BOUCLE

      FIN:

         .return (factorielle)

      .end

      .sub "main" :main

        $P0 = getstdin

        .local int nb

        .local int resultat

        print “Donnez moi une valeur entiere : “

        $S0 = readline $P0

        nb = $S0

        resultat =  factoriel(nb)

        print “La factorielle de “

        print nb

        print “ est egale a “

        print resultat

        print ".\n"

        end

      .end

      coruscant chris$ parrot factorielle.pir 

      Donnez moi une valeur entiere : 6

      La factorielle de 6 est egale a 720.

      coruscant chris$

    Analysons les lignes que nous venons d’écrire. On commence par définir une unité de compilation factorielle, qui sera aussi un sous-programme, au moyen de la directive .sub que nous connaissons. Ce sous-programme va récupérer comme paramètre une valeur entière qui lui sera transmise par l’intermédiaire de la variable locale nb. Comme nous l’avons dit, c’est la directive .param qui fait savoir au sous-programme qu’il y a un paramètre à récupérer, que ce paramètre est une valeur entière (int) et qu’elle sera mémorisée dans la variable locale nombre. Nous aurons par ailleurs besoin d’une variable locale entière factorielle qui sera initialisée à 1 et qui nous servira à effectuer le calcul.

    La boucle permet de manière très classique d’effectuer le produit des valeurs successives de nb à 1 au moyen de la variable compteur.

    À la fin, on retourne la valeur qui a été calculée et mémorisée dans la variable factorielle au moyen de l’instruction .return (factorielle).

    Le programme principal se contente de définir une valeur entière nb qui sera lue et contiendra la valeur dont nous désirons calculer la factorielle. Elle sera passée comme paramètre au sous-programme qui vient d’être défini.

    La valeur de retour récupérée dans la variable locale resultat sera alors affichée.

    9.4 Passage d’une liste de paramètres

    Pour illustrer ceci, revoici un classique de la programmation, l’énumération de Conway.

    Ce programme va nous permettre de mettre en évidence, outre le passage de la liste de paramètres, plusieurs caractéristiques que nous avons étudiéés jusqu’à présent.

      coruscant chris$ cat conway.pir

      .sub "Enumeration_Conway" :main

        .local string ch_ref, ch_res, car_ref, car_comp

        .local int occurence, nb_lignes

        nb_lignes = 11

        print “1\n”

        ch_ref = "1"

      CALCUL:

        occurence = 0

        ch_res = ""

        substr car_ref,ch_ref,0,1

      EXPLORE:

        occurence += 1

        substr ch_ref,ch_ref,1

        unless ch_ref goto FINI

        substr car_comp,ch_ref,0,1

        if car_ref == car_comp goto EXPLORE

        ch_res = CONSTRUIRE(occurence, car_ref, ch_res)

        car_ref = car_comp

        occurence = 0

        goto EXPLORE

      FINI:

        ch_res = CONSTRUIRE(occurence, car_ref, ch_res)

        print ch_res

        print "\n"

        ch_ref = ch_res

        nb_lignes -= 1

        if nb_lignes goto CALCUL

      .end

      .sub CONSTRUIRE

        .param int occurence

        .param string car_ref

        .param string ch_res

        .local string c

        c = occurence

        c .= car_ref

        ch_res .= c

        .return (ch_res)

      .end

      coruscant chris$ parrot conway.pir

      1

      11

      21

      1211

      111221

      312211

      13112221

      1113213211

      31131211131221

      13211311123113112211

      11131221133112132113212221

      3113112221232112111312211312113211

      coruscant chris$

    Ici, le sous-programme CONSTRUIRE récupère une liste de trois valeurs représentatives de trois chaînes de caractères et retourne un résultat, lui aussi sous forme d’une chaîne de caractères.

    9.5 Les paramètres nommés

    Dans la méthode de transmission que nous venons de voir, il y a plusieurs paramètres, et ils doivent être transmis dans un ordre strict.

    Les paramètres sont alors appelés positionnels et c’est la correspondance entre leur ordre dans la liste d’appel et l’ordre dans lequel ils sont déclarés en tant que paramètres dans le sous-programme qui permet d’effectuer le passage des valeurs.

    Il existe une autre manière de transmettre les paramètres à un sous-programme, on appelle cette méthode " les paramètres nommés ". Ici, l’ordre est quelconque et c’est le nom qui va permettre d’effectuer l’affectation de la bonne valeur au bon paramètre.

      coruscant chris$ cat parametres.pir

      .sub ident

        .param string prenom :named ("prenom")

        .param string nom :named ("nom")

        .param string mail :named ("mail")

        print “Votre prenom est : “

        print prenom

        print “.\n”

        print “Votre nom est : “

        print nom

        print “.\n”

        print “Votre mail est : “

        print mail

        print “.\n”     

      .end

      .sub "main" :main

        .local string n

        .local string p

        .local string m

        n = "Aperghis"

        p = “Christian”

        m = “chris@aperghis.fr”

        ident(“mail” => m, “nom” => n, “prenom” => p)

      .end

      coruscant chris$ parrot parametres.pir

      Votre prenom est : Christian.

      Votre nom est : Aperghis.

      Votre mail est : chris@aperghis.fr.

      coruscant chris$

    La liste d’appel du sous-programme indique que la valeur d’appel contenue dans la variable locale m sera récupérée dans le corps du sous-programme par la variable locale mail, et le raisonnement est identique pour les autres valeurs.

    C’est dans le corps du sous-programme que la variable mail est déclarée comme étant une chaîne de caractères passée par l’intermédiaire d’un paramètre nommé (:named).

    L’intérêt des arguments nommés est de s’affranchir, dans le cas de listes de paramètres un peu trop longues, d’éventuelles erreurs dues à une inversion accidentelle dans l’ordre des valeurs de la liste d’appel.

    9.6 Les paramètres optionnels

    Certains paramètres peuvent ne pas être présents lors de l’appel. On parle dans ce cas de paramètres optionnels.

    Dans les langages qui proposent cette facilité, le principe est de pouvoir transmettre une valeur si le paramètre est présent dans la liste d’appel, ou de prendre une valeur par défaut dans le cas contraire.

    En fait, PIR ne dispose pas de cette caractéristique en tant que telle, mais il propose une solution pour faire en sorte que certains paramètres puissent ne pas se présenter.

    C’est un indicateur spécifique attaché au paramètre qui va nous permettre de savoir si le paramètre optionnel en question s’est vu affecter une valeur par l’intermédiaire de la liste d’appel.

    En définitive, un paramètre déclaré comme étant optionnel peut être vu comme contenant deux informations distinctes, la première étant la valeur éventuellement transmise, la seconde représentant l’indicateur qui va permettre de savoir si, effectivement, une valeur lui a été affectée.

      .param string parametre :optional

      .param int present :opt_flag

    La directive :optional permet d’indiquer que le paramètre correspondant représente une valeur optionnelle et que, de ce fait, va lui être attaché un indicateur present défini par la directive :opt_flag.

    C’est son contenu qui permet de savoir de manière effective si une valeur a été transmise au paramètre par l’intermédiaire de la liste d’appel (1) ou si aucune valeur n’a été spécifiée (0).

    Le test de cet indicateur permet alors, si nécessaire, d’affecter une valeur par défaut au paramètre en question, ou de prendre toute décision en fonction du calcul à effectuer.

      coruscant chris$ cat optionnel.pir

      .sub valeur

        .param num valeur :optional

        .param int defaut :opt_flag

        print "Indicateur : "

        print defaut

        print ".\n"

        if defaut==0 goto DEFAUT

        print “Parametre transmis : “

        print valeur

        print “.\n”

        .return()

      DEFAUT:

        valeur = 0

        print “Aucun parametre transmis, on donne la valeur par defaut.\n”

      .end

      .sub "main" :main

        .local num n

        print “Appel avec une valeur effective (3,14159265).\n”

        n = 3.14159265

        valeur(n)

        print “Appel sans parametre.\n”

        valeur()   

      .end

      coruscant chris$ parrot optionnel.pir

      Appel avec une valeur effective (3,14159265).

      Indicateur : 1.

      Parametre transmis : 3.14159265.

      Appel sans parametre.

      Indicateur : 0.

      Aucun parametre transmis, on donne la valeur par defaut.

      coruscant chris$

    Il est à noter que les paramètres optionnels peuvent indifféremment être des paramètres positionnels ou des paramètres nommés. Toutefois, lorsqu’on les utilise avec des paramètres nommés, ils doivent impérativement apparaître à la fin de la liste, après les paramètres positionnels. De plus, la directive :opt_flag doit nécessairement se trouver immédiatement après la directive :optional.

    Première erreur :

      .sub ‘parametres’

        .param int valeur_optionnelle :optional

        .param int indicateur :opt_flag

        .param pmc valeur <- Cette ligne est fausse.

    On aurait dû écrire :

      .sub ‘parametres’

        .param pmc valeur

        .param int valeur_optionnelle :optional

        .param int indicateur :opt_flag

    Seconde erreur :

      .sub ‘parametres’

        .param int indicateur :opt_flag

        .param int valeur_optionnelle :optional <- Faux.

    On aurait dû écrire :

      .sub ‘parametres’

        .param int valeur_optionnelle :optional

        .param int indicateur :opt_flag

    Troisième erreur :

      .sub ‘parametres’

        .param int valeur_optionnelle :optional

        .param pmc valeur <- Faux.

        .param int indicateur :opt_flag 

    On aurait dû écrire :

      .sub ‘parametres’

        .param pmc valeur

        .param int valeur_optionnelle :optional

        .param int indicateur :opt_flag

    Et il est aussi possible de mélanger des paramètres optionnels et des paramètres nommés.

    Nous allons illustrer ceci avec l’exemple d’un sous-programme qui calcule la racine n-ième d’un nombre, et ce, quel que soit n.

    Le sous-programme récupère deux paramètres nommés, la Valeur et la Racine, la particularité étant que le second paramètre peut être absent. Si c’est la cas, la valeur par défaut sera 2 et on procédera au calcul de la racine carrée.

      coruscant chris$ cat optionnel.pir

      .sub racine

        .param num N :named ("Valeur")

        .param num Exp :named ("Racine") :optional

       .param int ind :opt_flag

        .local num X0, X1

        .local int I

        if ind == 1 goto RACINE

      # Parametre optionnel absent, on prend 2 par defaut.

        Exp = 2

      RACINE:

       X0 = N

        I = 0

      BOUCLE:

        I += 1

        $N2 = Exp - 1.0

        $N1 = X0 * $N2

        $N2 = X0 ** $N2

        $N2 = N / $N2

        X1 = $N1 + $N2

        X1 /= Exp

        $N2 = X0 - X1

        if $N2 > 0 goto OK

        $N2 = - $N2

      OK:  

        if $N2 < 0.00000000000001 goto FIN

        X0 = X1

        goto BOUCLE

      FIN:

        .return (I, X1)

      .end

      .sub “Racine nieme d’un nombre” :main

        .local num e, pi, Rac

        .local int i

        e = 2.71828183

        pi = 3.14159265

        (i, Rac) = racine (“Valeur” => pi, “Racine” => e)

        print “Racine e-ieme de pi = “

        say Rac

        print "Trouvee en "

        print i

        say " iterations."

        (i, Rac) = racine (“Valeur” => 10)

        print “Racine carree de 10 = “

        say Rac

        print "Trouvee en "

        print i

        say " iterations."

      .end

      coruscant chris$ parrot optionnel.pir

      Racine e-ieme de pi = 1.52367105385469

      Trouvee en 7 iterations.

      Racine carree de 10 = 3.16227766016838

      Trouvee en 7 iterations.

      coruscant chris$

    9.7 Les fonctions récursives

    N’abandonnons pas les bonnes habitudes. La tradition veut que pour illustrer la notion de récursivité, on prenne comme exemple la fonction factorielle :

      return (n > 1 ? n * _fact(n - 1) : 1)

    Pour changer un peu, au lieu de se contenter de calculer une simple factorielle, le programme proposé va calculer les factorielles des n premiers nombres entiers.

      coruscant chris$ cat facto.pir

      .sub _factoriel

        .param int valeur

        .local int factorielle

        if valeur > 1 goto RECURSION

          factorielle = 1

          goto RETOUR

      RECURSION:

        $I0 = valeur - 1

        factorielle = _factoriel($I0)

        factorielle *= valeur

      RETOUR:

          .return (factorielle)

      .end

      .sub _main :main

        .local int facto, nombre

        print “Calcul des factorielles des cinq premiers nombres entiers.\n”

        nombre = 0

      BOUCLE:

        facto = _factoriel(nombre)

        print “La factorielle de “

        print nombre

        print “ est egale a “

        print facto

        print ".\n"

        inc nombre

        if nombre <= 5 goto BOUCLE

      .end

      coruscant chris$ parrot facto.pir

      Calcul des factorielles des cinq premiers nombres entiers.

      La factorielle de 0 est egale a 1.

      La factorielle de 1 est egale a 1.

      La factorielle de 2 est egale a 2.

      La factorielle de 3 est egale a 6.

      La factorielle de 4 est egale a 24.

      La factorielle de 5 est egale a 120.

      coruscant chris$

    10 Les continuations

    Une continuation peut être considérée comme une photographie instantanée, une sorte d’image figée de l’état courant de l’exécution de la machine virtuelle.

    Une fois qu’une continuation aura été définie, elle peut être invoquée pour retourner à l’emplacement du programme où elle a été créée.

    C’est une étape dans le déroulement séquentiel du programme qui permet au développeur de transférer le contrôle du programme à une adresse précédemment enregistrée.

    En fait, ce concept n’est pas vraiment une nouveauté. Des langages comme Lisp ou Scheme proposent ce type d’outils depuis longtemps [Continuation].

    Toutefois, il faut noter qu’en dépit de son intérêt, cette facilité n’a pas vraiment été utilisée de manière optimale, quel que soit le langage considéré.

    Le but affiché par la machine virtuelle Parrot et par le langage Parrot Intermediate Representation est de modifier profondément cette tendance.

    Sur cette plate-forme, toutes les manipulations du contrôle de flux, y compris les appels de méthodes, de sous-programmes ou de coroutines, sont réalisées au moyen du mécanisme de continuation.

    Si ce mécanisme est généralement caché aux développeurs qui se contentent de réaliser des applications, il est disponible pour tous ceux qui souhaitent en utiliser toute la puissance et la flexibilité dans la gestion de leurs sous-programmes.

    On appellera CPS (Continuation Passing Style) l’ensemble des contrôles de flux utilisant le mécanisme de continuation.

    Cette technique permet à la machine virtuelle de proposer tout un ensemble de fonctionnalités telles l’optimisation de l’appel en queue (Tail Calls) ou les sous-programmes lexicaux.

    11 Les tail calls

    Il existe des cas de figure dans lesquels une routine sera mise en place simplement pour faire appel à un autre sous-programme [TailCall1], le but étant en définitive de retourner le résultat du second appel.

    On appelle cette technique tail call [TailCall2] et elle représente une occasion à ne pas manquer pour optimiser le code.

    Voici un exemple Perl :

      coruscant chris$ cat TC.pl 

      sub plus_deux {

        my ($valeur) = @_;

       $valeur = plus_un($valeur);

        return plus_un($valeur);

      }

      sub plus_un {

        my ($val) = @_;

        return (++$val)

      }

      $A = 10;

      print “ Resultat final : “, plus_deux($A), “\n”;

      coruscant chris$ perl TC.pl 

      Resultat final : 12

      coruscant chris$

    Si nous regardons cet exemple de manière attentive, nous constatons que le sous-programme plus_deux fait deux appels successifs au sous-programme plus_un, le second appel étant simplement utilisé comme valeur de retour. Jamais une valeur renvoyée par le sous-programme plus_un n’est mémorisée à un quelconque emplacement mémoire spécifique dans le sous-programme plus_deux.

    Ce type de situation peut facilement être optimisé en utilisant le même emplacement mémoire pour récupérer la valeur renvoyée. C’est ainsi que les deux appels réutiliseront un espace commun qui va aussi servir pour renvoyer la valeur de retour au lieu d’en créer un nouveau à chaque appel.

    En PIR, ceci pourrait se présenter comme suit :

      coruscant chris$ cat tailcall.pir

      .sub plus_un

        .param int val

        val = val + 1

        .return (val)

      .end

      .sub plus_deux

        .param int valeur

        valeur = plus_un (valeur)

        valeur = plus_un (valeur)

        .return (valeur)

      .end

      .sub "main" :main

        .local num n

        n = 10

        n = plus_deux(n)

        print “Valeur finale : “

        say n

      .end

      coruscant chris$ parrot tailcall.pir

      Valeur finale : 12

      coruscant chris$

    En fait, il existe en PIR une directive .tailcall qui permet de réaliser cette opération de manière plus efficace que la directive .return.

      coruscant chris$ cat tailcall.pir

      .sub plus_un

        .param int val

        val = val + 1

        .return (val)

      .end

      .sub plus_deux

        .param int valeur

        valeur = plus_un (valeur)

        .tailcall plus_un (valeur)

      .end

      .sub "main" :main

        .local num n

        n = 10

        n = plus_deux (n)

        print “Valeur finale : “

        say n

      .end

      coruscant chris$ parrot tailcall.pir

      Valeur finale : 12

      coruscant chris$

    C’est cette directive qui permet d’optimiser le processus en réutilisant la continuation de la fonction père pour effectuer l’appel.

    11.1 Création et utilisation des continuations

    Dans la majorité des cas, les continuations sont utilisées de manière implicite dans le flot de contrôle de multiples opérations de la machine virtuelle. C’est ce que nous avons vu jusqu’à présent.

    Toutefois, le programmeur peut les gérer de manière explicite lorsqu’il le désire. Dans ce cas, une continuation sera un PMC tout à fait ordinaire et, de ce fait, déclaré comme tel au moyen du constructeur new.

      $P0 = new ‘Continuation’

    Lors de sa création, cette continuation possède un état indéfini, le fait d’y faire référence immédiatement après sa création se soldera par la génération d’une exception.

    Pour positionner la continuation dans le but de l’exécuter, il est nécessaire de lui assigner une étiquette au moyen de la directive set_addr.

     $P0 = new ‘Continuation’

      set_addr $P0, LABEL

     Voyons ce mécanisme sur un exemple simple.

      coruscant chris$ cat continuation.pir

      .sub Produit

        .param int a

        .param int b

        .local int s

        s = a + b

        .begin_return

          .set_return s

        .end_return

      .end

      .sub "main" :main

        .const “Sub” $P0 = “Produit”

        $P1 = new ‘Continuation’

        set_addr $P1, RETOUR

        .local int x

        .local int y

        x = 10

        y = 25

        .begin_call

          .set_arg x

          .set_arg y

        .call $P0, $P1

      RETOUR:

        .local int r

        .get_result r

        .end_call

        print “Valeur de retour : “

        say r

      .end

      coruscant chris$ parrot continuation.pir

      Valeur de retour : 35

      coruscant chris$

    La ligne .call $P0, $P1 indique que l’on désire exécuter le sous-programme Produit référencé par l’intermédiaire de $P0 et que l’adresse où doit se continuer le programme après l’exécution du sous-programme est celle indiquée par le contenu de $P1.

    12 Les outils de mise au point

    12.1 Suivre le déroulement du programme

    Deux instructions permettent de disposer d’informations sur le déroulement du programme. La première, getfile, permet de récupérer dans une variable string le nom du fichier représentatif du programme sans avoir à acquérir la totalité de la ligne de commande. La seconde, getline, permet de connaître le numéro de la ligne courante.

      coruscant chris$ cat suivi.pir

     .sub "Main" :main

        .local int x, Ligne

        .local string Nom

        x = 0

        Nom = getfile

        print "Nom du programme : "

        say Nom

      DEBUT:

        unless x < 3 goto FIN

          print “Valeur : “

          print x

          print “.\n”

          Ligne = getline

          print “  On est sur la ligne : “

          say Ligne

          x += 1

          goto DEBUT

      FIN:

        Ligne = getline

        print “  Maintenant on est sur la ligne : “

        say Ligne

       print “Fin de la boucle. \n”

      .end

     coruscant chris$ parrot suivi.pir

      Nom du programme : test.pir

      Valeur : 0.

        On est sur la ligne : 13

      Valeur : 1.

        On est sur la ligne : 13

      Valeur : 2.

        On est sur la ligne : 13

        Maintenant on est sur la ligne : 19

      Fin de la boucle.

      coruscant chris$

    Ces indications ne permettent pas vraiment une mise au point du programme, tout au plus, elles donnent des indications sur son déroulement.

    12.2 Trace du programme

    Si on désire un suivi beaucoup plus précis de l’exécution du programme, on dispose d’une opération spécifique trace Booleen. L’instruction trace 1 permet d’activer le mécanisme de tracé du programme alors que l’instruction trace 0 y met fin.

    Lorsqu’il est activé, ce suivi donne nombre d’indications qui permettent de connaître avec beaucoup de précision l’instruction qui est en cours d’exécution et l’état des variables à ce moment.

      coruscant chris$ cat trace.pir

     .sub "until" :main

        .local int x

        x = 0

        trace 1

     DEBUT:

          unless x < 2 goto FIN

          print “Valeur : “

          say x

          x += 1

          goto DEBUT

      FIN:

        trace 0

        print “Fin de la boucle. \n"

      .end

      coruscant chris$ parrot trace.pir

           5 le 2, I0, 13              I0=0 

           9 print "Valeur : "

      Valeur :     11 say I0           I0=0

      0

          13 add I0, 1                 I0=0 

          16 branch -11

           5 le 2, I0, 13              I0=1 

           9 print "Valeur : "

     Valeur :     11 say I0           I0=1

      1

          13 add I0, 1                 I0=1

          16 branch -11

           5 le 2, I0, 13              I0=2 

          18 trace 0

      Fin de la boucle. 

      coruscant chris$

    On voit bien que la rencontre de l’instruction trace 1 active l’affichage de toutes les instructions qui s’exécutent et la valeur de la donnée qui est concernée. Si, de plus, l’instruction est une rupture de séquence conditionnelle, l’adresse du label est elle aussi précisée.

    L’instruction unless x < 2 goto FIN apparaît sous la forme le 2, I0, 13 indiquant que si la valeur 2 est strictement inférieure à $I0, on continue à l’adresse 13, représentative du label FIN. La valeur du registre apparaît elle aussi, ce qui permet de suivre l’exécution de l’instruction.

    13 Une petite distraction

    On dispose d’un PMC Timer qui permet de procéder à un décompte du temps. Les macros décrites dans le fichier timer.pasm seront utilisées pour positionner les diverses valeurs. Les deux valeurs principales sont .PARROT_TIMER_SEC, qui donne le nombre de secondes à décompter, et .PARROT_TIMER_HANDLER, qui spécifie quel est le sous-programme à appeler lorsque le décompte de temps arrive à zéro.

      coruscant chris$ cat chronometre.pir

      .include ‘timer.pasm’    # Constantes

      .sub Termine

        print "\n"

        say "Fin du decompte."

        exit 0

      .end

      .sub main :main

        $P0 = new ‘Timer’

        $P1 = get_global ‘Termine’

      # sous-programme à appeler en fin de decompte.

        $P0[.PARROT_TIMER_HANDLER] = $P1

      # Decompte de 5 secondes.

        $P0[.PARROT_TIMER_SEC]     = 5      

      # Lancement du decompte.

        $P0[.PARROT_TIMER_RUNNING] = 1      

        $I0 = 0

      BOUCLE:

      # Decompte des secondes.

        print $I0

        print "  "

        $I0 += 1

        sleep 1

        goto BOUCLE

        .end

      coruscant chris$ parrot chronometre.pir

      0  1  2  3  4  5

      Fin du decompte

      coruscant chris$

    Ce type de code peut servir à temporiser une action pour éviter que le programme se bloque sur un quelconque événement.

    14 Pour conclure provisoirement

    Nous avons exploré dans ce second volet un certain nombre des particularités de PIR. On peut se rendre compte que ce type de programmation ne se rattache à rien de vraiment défini.

    De l’assembleur PASM, il a conservé la rusticité et la manière de programmer.

    Mais, certaines de ses tournures syntaxiques sont proches de celles que l’on peut trouver dans les langages de plus haut niveau.

    Contrairement à l’assembleur de base, il nous propose un ensemble d’abstractions qui vont permettre à un utilisateur de ne pas avoir à se préoccuper de l’architecture de la machine sur laquelle il programme, voire à l’ignorer totalement, écrivant cependant rapidement un code extrêmement optimisé pour la plate-forme considérée.

    La plupart des éléments qui, au niveau de l’assembleur, présentent une quelconque difficulté sont cachés par l’ensemble des directives proposées par PIR.

    En définitive, PIR est d’un usage plus facile, tout en permettant de conserver toutes les fonctionnalités d’un langage d’assemblage.

    Références

    [goto] BRUHAT (Philippe) et FORGET (Jean), " goto Perl ", GNU/Linux Magazine France, n°72, mai 2005, http://articles.mongueurs.net/magazines/linuxmag72.html

    [Langages] Site officiel de la machine virtuelle Parrot, http://www.parrot.org/languages

    [PPC] PDD 3: Calling Conventions, http://www.parrotcode.org/docs/pdd/pdd03_calling_conventions.html

    [Continuation] Continuation dans les langages de programmation, http://fr.wikipedia.org/wiki/Continuation

    [TailCalls1] Squawks of the Parrot, http://www.sidhe.org/~dan/blog/archives/000211.html

    [TailCalls2] Tail Call, http://en.wikipedia.org/wiki/Tail_call

    Auteur : Christian Aperghis-Tramoni

    Retrouvez cet article dans : Linux Magazine 123

    Vous souhaitez commenter cet article ?
    Brèves Flux RSS
    Édito : Linux Pratique N°77
    Édito : GNU/Linux Magazine N°160
    Édito : GNU/Linux Magazine Hors-Série N°66
    Édito : MISC Hors-Série N°7
    Édito : Linux Essentiel N°31
    Communication RSS Com. RSS Presse
    Linux Essentiel N°31 – Communiqué de presse
    GNU/Linux Magazine N°159 – Communiqué de presse
    Linux Magazine, Partenaire de Symfony Live Paris
    Linux Pratique, Partenaire des Rencontres du Libre
    Misc, Partenaire de Insomni’Hack
    Rechercher un article dans notre base documentaire :
    En kiosque Flux RSS

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

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

    Lire la suite...

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

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

    Lire la suite...

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

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

    Lire la suite...

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

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

    Lire la suite...

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

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

    Lire la suite...

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

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

    Lire la suite...

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

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

    Lire la suite...