Le langage PIR, seconde partie

GNU/Linux Magazine n° 123 | janvier 2010 | Christian aperghis-tramoni.
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
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, l'ensemble des programmes présentés peut être téléchargé 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 nous, 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 PMCdescripteur. 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 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 certain 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éalises 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ées, 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 ou 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