Le langage PIR, seconde partie

Article publié dans Linux Magazine 123, janvier 2010.

Copyright © 2009 Christian Aperghis-Tramoni

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

Chapeau

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

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.

Les entrées sorties.

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

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.

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éré '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$

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$

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$

Les labels.

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 Mag 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 en gagne en clarté et en lisibilité.

Règles d'utilisation.

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

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

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

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

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.

La réalisation des boucles.

Les boucles non bornées while et until.

Elles doivent être construites au moyen de :

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$

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.

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.

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 terme d'interface entre le programme appelant et le sous-programme. Ici, il n'y a 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'instruction 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

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

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, le grande majorité des utilisateurs peuvent totalement ignorer les détails du mécanisme qui gèrent ces actions, il n'en est pas de même pour tous ceux qui souhaitent en exploiter toute les capacités et qui, de ce fait, doivent en connaître le fonctionnement détaillé.

Appels de sous-programmes.

Il ne faut pas se le cacher, la gestion d'un sous-programme cache 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 le 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 sera extraire la référence à un sous-programme nom de la table des symboles globaux pour le 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.

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 fait 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 factoriel, qui sera aussi un sous-programme, au moyen de la directive que nous connaissons .sub. 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.

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

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.

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

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$

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édent enregistrée.

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

Toutefois, il faut noter que en dépit de son intérêt, cette facilité n'a pas vraiment été utilisé 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, toute manipulation du contrôle de flux y compris les appels de méthodes, de sous-programmes ou de coroutines sont réalisés 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 d'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.

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.

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 lorsque qu'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 ou doit se continuer le programme après l'exécution du sous-programme est celle indiquée par le contenu de $P1.

Les outils de mise au point.

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.

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 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 lui aussi précisé.

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.

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.

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 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 qu'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 même à 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] goto Perl, par Philippe Bruhat et Jean Forget, in 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

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