Catégorie : Programmation     Tags :      0 Commentaire

    Retrouvez cet article dans : Linux Magazine 122

    Nous entamons ici une série d’articles consacrés au langage PIR, et ce premier chapitre a pour but de le présenter. PIR signifie Parrot Intermediate Representation, qui, comme son nom l’indique, est un langage moins rustique, plus abordable et plus lisible que l’assembleur de base PASM.

    Tous les programmes présentés ici sont disponibles en téléchargement sur mon site : http://www.dil.univ-mrs.fr/~chris/Documents/progs01.pod.

    Parrot Intermediate Representation est le langage de niveau intermédiaire [Lang Int] installé de manière native sur la machine virtuelle Parrot.

    Après quelques rappels sur les notions de base nécessaires pour en comprendre la philosophie générale, nous étudierons de manière plus détaillée les caractéristiques propres à ce langage.

    Il y a quelque temps, une série d’articles sur l’assembleur Parrot [PASM01], [PASM02], [PASM03] nous avait permis de démystifier le langage de base de la machine virtuelle.

    Cet assembleur, extrêmement proche de la structure interne de la machine pouvait poser, comme tout langage de très bas niveau, un certain nombre de problèmes à ceux qui ne sont pas familiers avec la manipulation d’un jeu d’instructions élémentaire. L’obligation de devoir, en toute circonstance, respecter les contraintes imposées par l’architecture de la machine, serait-elle virtuelle, peut apparaître comme une lourde servitude.

    Fondamentalement, si Parrot Intermediate Representation peut être presque considéré comme un langage d’assemblage, il dispose de quelques caractéristiques de haut niveau qui en rendent l’utilisation plus abordable.

    PIR sera principalement utilisé pour écrire les bibliothèques, ainsi que certains des compilateurs, et c’est en PIR que seront traduits les langages de haut niveau.

    C’est ce langage que nous allons détailler. Un langage un peu plus évolué offrant de multiples possibilités tout en permettant à ceux qui le désirent de rester liés à la machine.

    Cette première partie qui traite essentiellement des données pourrait sembler quelque peu rébarbative. C’est pourtant le passage incontournable pour pouvoir aborder facilement la programmation dont nous parlerons ultérieurement.

    1

    Langage intermédiaire

    Un langage intermédiaire est généralement défini comme étant le moyen de programmer une machine virtuelle.

    Typiquement, un tel langage répond à trois critères fondamentaux :

    - Chaque instruction doit représenter exactement un code opération.

    - Il ne peut pas comporter de structures de contrôle.

    - On dispose d’un grand nombre (voire d’un nombre infini) de registres.

    Parrot Intermediate Representation (PIR) est ainsi un nouveau moyen de programmer directement la machine Parrot [Parrot]. Ce langage va, dans un certain sens, permettre de s’affranchir quelque peu de certaines des contraintes liées au contexte de la machine virtuelle tout en conservant un niveau de programmation suffisamment bas pour pouvoir tirer au mieux parti de la structure interne de la plate-forme.

    Ces deux points peuvent, a priori, paraître contradictoires, mais nous verrons qu’il n’en est rien.

    Il faut aussi noter que si à l’origine PIR devait se présenter comme une couche au-dessus de l’assembleur (PASM), ces deux représentations ont, sémantiquement, quelque peu divergé et ne sont plus dans l’état actuel du développement aussi directement liées l’une à l’autre.

    PIR peut-il être considéré comme un vrai langage ?

    Tant par la manière d’aborder les programmes que par la syntaxe de base de ses opérateurs, le langage devrait paraître familier aux programmeurs. À quelques détails près, par exemple la représentation des expressions arithmétiques, ses caractéristiques ne sont pas si éloignées de l’ancien Fortran IV des années 1950 [Back01].

    Il n’en demeure pas moins, si on le compare aux outils de programmation actuellement disponibles, un dialecte d’assez bas niveau, qui reste, par de nombreux côtés, étroitement lié aux caractéristiques intrinsèques de la machine virtuelle.

    C’est cette double spécificité qui aurait tendance à le rendre particulièrement attractif et agréable à utiliser.

    Une troisième langage, de niveau nettement supérieur, est également proposé dans la distribution [NQP]. Il s’agit de NQP (Not Quite Perl) qui, comme son nom l’indique, est un petit langage représentant un sous-ensemble de Perl 6 [Perl6] et développé à l’origine comme un outil destiné à la réalisation du compilateur Perl 6.

    Il est lui aussi intégré dans la boite à outils PCT (Parrot Compiler Toolkit).

    Par convention, les fichiers Parrot sont identifiés par leur extension. Rappelons que l’extension .pasm (parrot assembly) indique qu’il s’agit d’assembleur de base, et que l’extension .pcb (parrot byte code) identifie un fichier exécutable en byte code parrot. Dans notre cas, c’est l’extension .pir qui va permettre de spécifier que le fichier soumis à Parrot contient du code PIR.

    Dans l’avenir, plusieurs langages de haut niveau seront disponibles sur la machine Parrot. PIR est principalement destiné à la concrétisation de ces futurs langages.

    2

    Les déclarations

    2.1

    Un rapide survol des caractéristiques de PIR

    C’est dans la syntaxe de ses déclarations que PIR se distingue de la majorité des langages de bas niveau par une meilleure flexibilité. Mais, si cette caractéristique facilite son utilisation en regard à ce qui est de mise dans les assembleurs, il faut reconnaître que la manipulation des données est beaucoup plus rigide et plus proche de l’organisation interne de la machine qu’elle ne l’est dans les ensembles plus évolués de type Perl.

    En fait, PIR peut être, si on le désire, assez étroitement lié à l’agencement propre de la plate-forme Parrot, et on constate vite que, moyennant quelques changements mineurs, les instructions PIR rappellent fortement la programmation de base du système.

    Nombre d’options supplémentaires qui lui ont été ajoutées ont pour but d’en faciliter la lisibilité et d’améliorer la qualité de la programmation.

    Dans PIR, il existe un délimiteur d’instruction qui est le saut de ligne (\n). De plus, en fonction des principes des langages intermédiaires, chaque instruction qui ne peut définir qu’une unique opération doit impérativement se présenter sur une seule ligne. À noter par ailleurs que les lignes vides ne sont pas prises en compte. Elles sont considérées comme une instruction vide.

    Toutes les instructions, y compris les instructions vides, peuvent être labellisées, c’est-à-dire repérées par une référence symbolique. Ceci est fondamental pour pouvoir programmer les sauts et les branchements.

    Tout ce qui est compris entre le caractère dièse (#) et la fin de la ligne sera considéré comme un commentaire et il est possible d’utiliser les marqueurs POD pour réaliser la documentation en ligne.

    2.2

    Architecture de la machine virtuelle

    Rappelons que Parrot est organisé autour de quatre jeux de 32 registres chacun.

    - 32 registres IV (entiers) (I0 .. I31) ;

    - 32 registres NV (flottants) (N0 .. N31) ;

    - 32 registres STRING (chaînes de caractères) (S0 .. S31) ;

    - 32 registres PMC (Parrot Magic Cookie) (P0 .. P31).

    2.3

    Organisation générale de PIR

    Afin de faciliter la lecture et la réalisation des applications, PIR propose à son utilisateur quelques représentations de haut niveau. Toutefois, si on désire rester aussi proche que possible de la structure de Parrot, le programme peut accéder à chaque registre explicitement désigné. Dans ce cas, le nom de l’emplacement concerné est précédé du caractère dollar ($).

      $N10 = 3.14

      $S1 = "Bonjour\n"

    Le registre réel 10 est positionné à la valeur 3,14 et on stocke la chaîne considérée dans le registre chaîne de caractères numéro 1.

    Mais, comme nous le verrons plus loin, on peut disposer de noms symboliques pour désigner ses données. Dans ce cas, une variable nommée apparaîtra telle quelle.

      e = 2.71828183

    La variable nommée e prend la valeur 2.71828183.

    Nous détaillerons aussi dans la seconde partie comment construire des commandes moins élémentaires à partir des mots-clés et des opérateurs.

      if compteur <= limite goto LABEL

    Dans cette instruction, le séquencement se poursuit à l’adresse référencée LABEL si le contenu de la variable compteur est inférieur ou égal au contenu de la variable limite. Dans le cas contraire, la séquence normale est respectée.

    Toutes ces caractéristiques seront détaillées au fur et à mesure.

    Il est toutefois important de noter que PIR n’a pas et n’aura jamais de structures de haut niveau telles que les boucles bornées (for) ou non bornées (while, until). Lorsque leur usage s’avérera indispensable, elles devront être réalisées au moyen des instructions de base proposées par le langage if, unless et goto.

    Cet état de choses peut rendre la programmation quelque peu amphigourique. Il existe toutefois des règles d’écriture qui, si elles sont respectées, permettent de rendre ce type de codage lisible et accessible à tout le monde.

    2.4

    Déclaration des variables

    Comme sur toute plate-forme, le programmeur dispose de plusieurs emplacements pour stocker les données sur lesquelles il travaille.

    Il faut cependant noter que, au niveau de la machine virtuelle, si les registres représentent les seuls endroits où il soit possible de mémoriser une valeur destinée à être manipulée, il existe de multiples manières d’y faire référence, et PIR est là pour nous aider à le faire.

    Nous avons vu que le nom d’un registre commence systématiquement par le caractère dollar ($). Ce caractère est toujours suivi d’une lettre en majuscule qui indique sur quel type de valeur on doit travailler :

    - I pour un registre entier ;

    - N pour un registre flottant ;

    - S pour un registre chaîne de caractères ;

    - P pour un registre PMC.

    Cette lettre est obligatoirement suivie du numéro de l’emplacement concerné.

      $S25 = "Bonjour tout le monde.\n"

      $N10 = 2.71828183

      $I31 = 1984

    Mais, bien que le nombre de registres de la machine soit de 32 pour chacun des types (numérotés de 0 à 31), il est possible d’en utiliser un nombre quasiment infini. C’est ainsi que pour toutes les valeurs supérieures à 31, les positions spécifiées seront considérées comme des variables ne nécessitant pas de déclaration préalable. Pour simplifier les choses, on peut imaginer que les quatre ensembles de stockage ne sont en définitive que des vecteurs indicés par leur numéro.

      $S2555 = "Bonjour tout le monde.\n"

      $N9999 = 2.71828183

      $I2948 = 1984

    En revanche, le fait d’en multiplier le nombre va automatiquement avoir une influence sur l’efficacité du programme, en raison des problèmes liés à l’allocation et à la récupération des données.

    Si on désire optimiser les performances, il est préférable de n’utiliser que les 32 registres réels de la machine pour chacun des types disponibles. Ceci étant, il faudra garder en mémoire que le numéro de l’emplacement que l’on spécifie ne correspondra pas automatiquement au numéro réel. C’est un allocateur de mémoire qui va se charger de traduire les références utilisés dans PIR $S10, $I20 $N19 en une adresse mémoire.

    Ce mécanisme a été mis en place afin d’utiliser au mieux l’espace de stockage en récupérant les emplacements libérés au lieu d’en utiliser de nouveaux.

    En fin de compte, le programmeur n’aura à se soucier ni du nombre, ni de l’allocation des données qu’il utilise. Il est libre d’utiliser autant de ressources qu’il le désire tout en restant conscient que ses choix auront une incidence sur les performances finales de son programme.

    2.5

    Représentation des constantes

    La machine virtuelle Parrot, comme nous l’avons vu, dispose de quatre types de données de base, les nombres entiers, les nombres flottants, les chaînes de caractères, et les PMC. PIR est donc capable de manipuler les valeurs correspondantes.

    Entiers :

      $I10 = 2009       # Nombre positif.

      $I15 = -1         # Nombre négatif.

      $I20 = 0xA5       # Valeur hexadécimale.

      $I25 = 0b01010    # Valeur binaire.

    Flottants :

      $N0 = 3.14        # Nombre décimal.

      $N1 = 4           # Nombre décimal.

      $N2 = 1.2e-4      # Notation scientifique.

    Chaîne de caractères en simple ou double quote :

      $S20 = "Bonjour tout le monde"

      $S21 = â€˜Bonjour tout le monde’

    Dans une chaîne représentée entre double quotes, le mécanisme de substitution est activé

      $S30 = "Voici une chaîne\nsur deux lignes"

    alors qu’il est désactivé dans une chaîne située entre des simples quotes.

      $S0 = â€˜... \n Cette chaîne contient le caractère \ suivi du caractère n."

    Le caractère \ (antislash) permet de générer des séquences d’échappement :

    - \xhh un caractère en représentation hexadécimale à 2 chiffres ;

    - \ooo un caractère en représentation octale jusqu’à 3 chiffres ;

    - \uhhhh un caractère en représentation hexadécimale à 4 chiffres ;

    - \Uhhhhhhhh un caractère en représentation hexadécimale à 8 chiffres ;

    - \x(h.. h) un caractère en représentation hexadécimale jusqu’à 8 chiffres ;

    - \CX le caractère de contrôle X ;

    - \a, \b, \t, \n, \v, \f, \r, \e, \\, \" ont leur signification usuelle.

    Il est aussi possible de déclarer un here document à la manière de Perl.

      $S28 = <<"FIN"

      Le nombre pi, noté par la lettre grecque du même nom,

      toujours en minuscule est le rapport constant entre

      la circonférence d’un cercle et son diamètre.

      Il est appelé aussi constante d’Archimède.

      Des valeurs approchées de pi courantes sont

        Approximativement 3,1416

        Approximativement sqrt(10)

        Approximativement 22/7.

      FIN

    Attention, la chaîne de terminaison (FIN) qui se trouve sur la dernière ligne ne doit être précédée d’aucune espace.

    3

    Les opérations

    3.1

    Les opérateurs arithmétiques

    Ce sont, sans surprise, les opérateurs classiques.

        $I3 = $I0 + $I1   # Addition

        $I8 = $I0 - $I1   # Soustraction

        $I1 = $I0 * $I1   # Multiplication

        $I5 = $I0 / $I1   # Division

        $I6 = $I0 % $I1   # Modulo

    La seule différence par rapport aux langages évolués étant de ne pouvoir représenter qu’une opération par ligne, il faut donc décomposer les expressions arithmétiques en autant de calculs élémentaires que nécessaire.

    3.2

    Les opérations logiques

    Elles fonctionnent en court-circuit, c’est-à-dire que, aussitôt que le résultat peut être déterminé, l’évaluation s’arrête, sinon elle se poursuit.

      coruscant chris$ cat logique.pir 

      .sub main

        $I0 = 0 && 1      # Va retourner 0

        $I1 = and 1, 2    # Va retourner 2

        $I2 = 1 || 0      # Va retourner 1

        $I3 = or 0, 5     # Va retourner 5

        $I4 = 1 ~~ 5      # Va retourner 0

        $I5 = xor 0, 9    # Va retourner 9

        print $I0

        print " "

        print $I1

        print "\n"

        print $I2

        print " "

        print $I3

        print "\n"

        print $I4

        print " "

        print $I5

        print "\n"

      .end

      coruscant chris$ parrot logique.pir 

      0 2

      1 5

      0 9

      coruscant chris$

    Il est possible d’utiliser indifféremment les fonctions or, and, xor ou les opérateurs ||, &&, ~~.

    3.3

    Les opérations booléennes

    Ce sont les opérateurs bit à bit.

      coruscant chris$ cat booleen.pir 

      .sub main

        $I0 = 98 & 121          # 01100010 & 01111001 = 01100000

        $I1 = band 98, 121      # C’est a dire 96.

        $I2 = 98 | 121          # 01100010 | 01111001 = 01111011

        $I3 = bor 98, 121       # C’est a dire 123

        $I4 = 98 ~ 121          # 01100010 | 01111001 = 00011011

        $I5 = bxor 98, 121      # C’est a dire 27

        print $I0

        print " "

        print $I1

        print "\n"

        print $I2

        print " "

        print $I3

        print "\n"

        print $I4

        print " "

        print $I5

        print "\n"

      .end

      coruscant chris$ parrot booleen.pir 

      96 96

      123 123

      27 27

      coruscant chris$

    Il est ici aussi possible d’utiliser indifféremment les fonctions bor, band, bxor ou les opérateurs |, &, ~.

    4

    Les unités de compilation

    La notion d’" unité de compilation " en PIR rappelle fortement ce que l’on appelle " sous-programme " ou " méthode " dans les langages de plus haut niveau.

    En Parrot Intermediate Representation, tout code doit impérativement être défini dans une unité de compilation. Celle-ci commence toujours par la directive .sub et se termine par la directive .end.

    Une des caractéristiques principales de ce type de structuration est que, sauf spécification explicite sur laquelle nous reviendrons ultérieurement, toute variable ou, de manière plus surprenante, tout registre, ne sera visible que dans l’unité de compilation dans laquelle elle (il) est déclarée.

    Voyons sur quelques exemples le comportement d’un programme PIR.

    4.1

    Premier exemple


     

      coruscant chris$ cat unite.pir 

      .sub main

        print "Bonjour tout le monde.\n"

      .end

      coruscant chris$ parrot unite.pir 

      Bonjour tout le monde.

      coruscant chris$ 

    Dans ces quelques lignes de programme, nous avons défini une unité de compilation et nous l’avons référencée main. Si nous ne précisons rien de plus, ce sera toujours la première unité de compilation rencontrée dans le programme qui sera exécutée au lancement, et ce, quel que soit son nom. Ici, il n’y en a qu’une.

    4.2

    Second exemple


     

      coruscant chris$ cat unite.pir 

      .sub premiere

        print "Unite de compilation : Premiere.\n"

      .end

      .sub main

        print "Unite de compilation : Main.\n"

      .end

      coruscant chris$ parrot unite.pir 

      Unite de compilation : Premiere.

      coruscant chris$ 

    Dans ce second exemple, nous avons déclaré deux unités de compilation. La première est identifiée premiere. Elle sera exécutée lors du lancement du programme et va afficher un message. Une fois ce travail effectué, l’apparition de la directive .end va mettre fin au programme et, de ce fait, la seconde unité de compilation main sera ignorée.

    4.3

    Troisième exemple


     

      coruscant chris$ cat unite.pir 

      .sub premiere

        print "Unite de compilation : Premiere.\n"

        main()

        print "Fin de : Premiere.\n"

      .end

      .sub main

        print "Unite de compilation : Main.\n"

      .end

      coruscant chris$ parrot unite.pir 

      Unite de compilation : Premiere.

      Unite de compilation : Main.

      Fin de : Premiere.

      coruscant chris$ 

    Dans ce nouvel exemple, la première unité de compilation rencontrée est toujours celle référencée premiere. C’est donc elle qui sera exécutée lors du lancement, mais, elle fait ici appel à l’autre unité main qui sera alors considérée comme un sous-programme et exécutée à son tour. Le contrôle sera rendu à l’unité de compilation appelante à la fin de main.

    4.4

    Quatrième exemple


     

      coruscant chris$ cat unite.pir 

      .sub premiere

        print "Unite de compilation : Premiere.\n"

      .end

      .sub "main" :main

        print "Unite de compilation : Main.\n"  

      .end

      coruscant chris$ parrot unite.pir 

      Unite de compilation : Main.

      coruscant chris$ 

    Dans ces nouvelles lignes de code, l’unité de compilation main a été identifiée par une chaîne de caractères, et, bien que ce ne soit pas la première du programme, c’est elle qui prendra la main au moment du lancement. Il est important de noter que, dans ce cas de figure, c’est uniquement le fait que l’unité en question soit identifiée par une chaîne de caractères qui lui confère ce statut et en aucun cas le contenu de cette chaîne.

    Le programme aurait tout aussi bien pu se présenter comme suit :

      coruscant chris$ cat unite.pir 

      .sub premiere

        print "Unite de compilation : Premiere.\n"

      .end

      .sub "Le programme commence ici." :main

        print "Le programme commence ici.\n"  

      .end

      coruscant chris$ parrot unite.pir 

      Le programme commence ici.

      coruscant chris$ 

    On peut aussi ne pas donner de nom à l’unité de compilation que l’on désire voir s’exécuter en premier en remplaçant la chaîne par le caractère blanc souligné (_).

      .sub _ :main

        print "Le programme commence ici.\n"  

      .end

    4.5

    Terminer un programme

    Le programme se terminera dès qu’il trouve une instruction end, et ce, où qu’elle se présente.

      coruscant chris$ cat unite.pir 

      .sub premiere

        print "Unite de compilation : Premiere.\n"

      .end

      .sub "Le programme commence ici" :main

        premiere()

        print "Avant le â€˜end’ dans main.\n"

      # Directive de fin de programme.

        end

        print "Apres le â€˜end’ dans main.\n"

      .end

      coruscant chris$ parrot unite.pir 

      Unite de compilation : Premiere.

      Avant le â€˜end’ dans main.

      coruscant chris$

    Dans ce cas de figure, l’apparition de l’instruction end met fin à l’exécution du programme. Le second message de main ne sera donc jamais affiché.

    4.6

    Pour les adeptes de PASM

    Il est toujours possible à partir d’un programme écrit en PIR de générer le programme PASM correspondant. C’est au moyen de l’option -o que cette propriété est activée.

      coruscant chris$ cat assemb.pir 

      .sub "main" :main

        $S10 = "Bonjour tout le monde.\n"

        print $S10

      .end

      coruscant chris$ parrot -o assemb.pasm assemb.pir

      coruscant chris$ cat assemb.pasm

      main:

    set S0, "Bonjour tout le monde.\n"

    print S0

    set_returns 

    returncc 

      coruscant chris$ 

    À l’analyse, on constate avec surprise que le programme généré n’a pas suivi les directives qui lui avaient été données en utilisant un registre différent de celui qui lui a été indiqué. Nous comprendrons ultérieurement pourquoi.

    5

    Chaînes, encodage et jeu de caractères

    Dans la machine virtuelle, chaque chaîne est associée à un codage et à un jeu de caractères. Par défaut, le jeu de caractères est le 8-bit ASCII. Il est simple à utiliser et universellement reconnu. Il est toutefois possible d’utiliser d’autres formats.

    Dans le cas d’une constante de chaîne déclarée entre doubles quotes, un préfixe optionnel permet de préciser soit seulement le jeu de caractères, soit simultanément le jeu de caractères et l’encodage de la chaîne en question.

    Les chaînes ainsi déclarées seront alors automatiquement converties lorsque cela s’avérera nécessaire afin de préserver l’intégrité de l’information.

    5.1

    Le codage

    Il se présente sous la forme suivante :

      chaine = encodage:jeu_de_caractères:"Chaîne de caractères."

    Par exemple :

      ch1 = utf8:unicode:"Chaîne en Unicode utf8."

      ch2 = utf16:unicode:"Chaîne en Unicode utf16"

      ch3 = ascii:"Chaîne en Ascii 8 bits."

      ch4 = binary:"Chaîne binaire non formatée."

    À noter que, en ce qui concerne l’encodage binaire, la structure sera traitée comme un tampon de données brutes non formatées. En fait, ce n’est pas vraiment une chaîne en soi, car les données binaires ne peuvent pas toujours être considérées comme des caractères réellement lisibles. Ce type de codage sera principalement utilisé dans le cas de bibliothèques de fonctions qui seraient susceptibles de retourner des données binaires difficilement rattachables à un type standard connu.

    Pour que le codage utf16:unicode soit pris en compte, il est impératif que le support ICU soit activé [ICU].

    Il est aussi important de prendre en compte le fait que, pour spécifier les encodages et le jeu de caractères, le mécanisme de substitution doit avoir été activé (") dans les chaînes considérées. Si tel n’est pas le cas (‘) ni l’encodage, ni le jeu de caractères ne seront pris en compte.

    Si, dans une concaténation, deux chaînes sont regroupées, le jeu de caractères et l’encodage doivent impérativement être identiques. Dans le cas contraire, PIR procédera à l’actualisation des chaînes mises en présence en utilisant le format compatible immédiatement supérieur.

    Les chaînes ASCII seront converties en UTF-8 et UTF-8 sera converti en UTF-16.

    6

    Les fonctions sur les chaînes

    6.1

    L’instruction concat

    C’est l’instruction qui va nous permettre de concaténer deux chaînes de caractères.

      coruscant chris$ cat concatene.pir  

      .sub "main" :main

        $S10 = "Linux"

        $S11 = "Magazine"

        $S0 = concat $S10, " "

        $S0 = concat $S0, $S11

        print $S0

        print "\n"

      .end

      coruscant chris$ parrot concatene.pir 

      Linux Magazine

      coruscant chris$ 

    Si on le désire, il est aussi possible d’utiliser l’opérateur . (point).

      $S10 = $S10 . $S11

    6.2

    L’instruction substr

    L’instruction substr supporte jusqu’à quatre paramètres.

    Elle se présente sous la forme : substr ss_ch,ch_ref,debut,longueur.

      coruscant chris$ cat chaine.pir  

      .sub "main" :main

        $S0 = "Linux Magazine."

        substr $S1,$S0,0,9

        print $S1

        print "\n"

      .end

      coruscant chris$ parrot chaine.pir 

      Linux Mag

      coruscant chris$ 

    Elle se comporte exactement comme la fonction Perl. La longueur peut être omise si on désire conserver toute la fin de la chaîne, et l’index peut être négatif si on désire compter les caractères à partir de la droite.

    Si aucune destination n’est spécifiée pour la sous-chaîne, c’est-à-dire si le second paramètre est une valeur numérique, la sous-chaîne spécifiée sera remplacée par la chaîne transmise en tant que quatrième paramètre.

    6.3

    L’instruction index

    L’instruction index permet de déterminer l’indice du début d’une sous-chaîne dans une chaîne de référence, index ch_ref, ss_ch.

    Cette information peut alors être utilisée dans l’instruction substr que nous venons de voir pour procéder à une substitution de sous-chaîne.

      coruscant chris$ cat index.pir 

      .sub "main" :main

        .local string chaine

        chaine = "Il fait beau et chaud."

        $I1 = index chaine, "beau"

        print "La sous chaine commence a l’indice "

        print $I1

        print "\n"

      # On substitue "mauvais" Ã  "beau"

        substr chaine, $I1, 4, "mauvais"

        print chaine

        print "\n"

      .end

      coruscant chris$ parrot index.pir 

      La sous chaine commence a l’indice 8

      Il fait mauvais et chaud.

      coruscant chris$

    C’est la sous-chaîne qui commence en $I1 de longueur 4 (beau) qui est la cible de la substitution. La chaîne qui a été remplacée peut être récupérée si nécessaire :

      $S0 = substr chaine, $I1, 4, "mauvais"

    Dans ce cas de figure, le registre $S0 contient la chaîne "beau".

    6.4

    L’instruction length

    Appliquée à une chaîne de caractères, elle va permettre d’en récupérer la longueur.

      coruscant chris$ cat longueur.pir

      .sub "main" :main

        .local string chaine

        .local int longueur

        chaine   = "Bonjour tout le monde.\n"

        longueur = length chaine

        print "Longueur de la chaine : "

        print longueur

        $S0 = "Il fait beau.\n"

        $I1 = length $S0

        print "\nLongueur de la chaine : "

        print $I1

        print "\n"

      .end

      coruscant chris$ parrot longueur.pir 

      Longueur de la chaine : 23

      Longueur de la chaine : 14

      coruscant chris$

    6.5

    La fonction chopn

    Pour retirer un certain nombre de caractères dans une chaîne de référence, on dispose de l’instruction chopn qui se présente sous la forme générale :

      destination = chopn chaine, val

    Elle permet de retirer val caractères à la fin de la chaîne de référence.

      coruscant chris$ cat chop.pir

      .sub "main" :main

        .local string ch, dest

        ch = "0123456789"

        dest = chopn ch, 3

        print dest

       print "\n"

      .end

      coruscant chris$ parrot chop.pir

      0123456

      coruscant chris$

    Si le nombre spécifié dans l’instruction chopn est négatif, il indique le nombre de caractères à conserver en début de chaîne.

      coruscant chris$ cat chop.pir 

      .sub "main" :main

        .local string ch, dest

        ch = "0123456789"

        dest = chopn ch, -4

        print dest

        print "\n"

      .end

      coruscant chris$ parrot chop.pir

      0123

      coruscant chris$

    Si la destination n’est pas précisée, l’opération est effective sur la chaîne elle-même.

      coruscant chris$ cat chop.pir 

      .sub "main" :main

        .local string ch

        ch = "0123456789"

        chopn ch, 3

        print ch

        print "\n"

      .end

      coruscant chris$ parrot chop.pir

      0123456

      coruscant chris$

    Comme c’est le cas dans Perl, cette instruction va nous permettre, lors des accès en lecture de retirer le caractère \n à la fin de la chaîne qui vient d’être acquise.

    7

    Les variables nommées

    7.1

    Utilisation des noms symboliques

    Il n’est jamais très parlant pour un programmeur d’adresser une donnée sous la forme brute $S10. Il est beaucoup plus pratique et infiniment plus agréable de faire référence à des variables en utilisant des noms explicites indice, limite, prenom...

    Si nous avons déjà profité de cette facilité dans quelques-uns des programmes qui viennent d’être présentés, nous allons maintenant en détailler l’utilisation.

    Avant de pouvoir accéder à une variable, il est nécessaire de la déclarer, c’est la directive .local qui va permettre de le faire et de procéder au typage de la donnée qui lui sera affectée.

      .local type liste_de_variables

    Les quatre types disponibles sont :

    ? int pour déclarer une variable entière ;

    ? num pour déclarer une variable flottante ;

    ? string pour déclarer une chaîne de caractères.

    ? pmc pour déclarer une classe Parrot Parrot Magic Cookie.

    Ils correspondent aux quatre types de registres disponibles sur la machine virtuelle.

    Une fois déclarées, ces variables peuvent être référencées dans toutes les lignes de code de l’unité de compilation à laquelle elles appartiennent.

      coruscant chris$ cat variables.pir 

      .sub _ :main

      .local string bonjour

      .local num e

      .local int limite

      bonjour = "Bonjour tout le monde.\n"

      print bonjour

      e = 2.71828183

      print e

      print "\n"

      limite = 1000

      print limite

      print "\n"

      .end

      coruscant chris$ parrot variables.pir 

      Bonjour tout le monde.

      2.71828183

      1000

      coruscant chris$

    Le nom d’une variable répond aux mêmes règles que dans Perl (lettres, chiffres et blanc souligné), le premier caractère étant impérativement une lettre ou un blanc souligné.

    Il n’y a pas de limite pour le nombre de caractères pouvant constituer un nom de variable. Il ne faut cependant pas oublier que gérer des identificateurs trop longs demande un gros travail d’allocation mémoire et un certain manque d’efficacité au moment de l’analyse syntaxique.

    7.2

    L’allocation de registres

    Lorsque nous avons demandé la génération du programme PASM correspondant à un code PIR, nous avons constaté que la machine ne suivait pas les directives qui lui avaient été données.

    Nous avions fait référence à un registre chaîne de caractères ($S10) et la lecture du fichier PASM correspondant fait apparaître que c’est en définitive le registre $S0 que la machine décide d’utiliser. Cette décision arbitraire peut s’expliquer par le fait que, en théorie, le nombre de registres adressables est, comme nous l’avons vu, illimité et qu’il ne sera pas toujours possible au système d’obéir aveuglément aux directives qui lui sont données.

    En fait, PIR utilise un mécanisme d’allocation qui affecte à chaque structure déclarée un emplacement spécifique en mémoire, l’allocateur est en définitive un optimiseur qui va calculer et analyser la durée de vie de chaque élément (registre ou variable) afin de déterminer à quel moment il va être utilisé et à quel moment il ne le sera plus. La réutilisation des espaces ainsi libérés permet de diminuer les exigences en ressources.

    C’est d’ailleurs cette phase d’allocation qui réclame le plus de temps et de moyens en termes de calcul lors de la compilation du programme.

    Si on en éprouve le besoin ou la nécessité, il est parfaitement possible d’invalider ce mécanisme en demandant que les affectations de registres soient maintenues.

    Cette caractéristique peut se révéler particulièrement utile dans un sous-programme qui n’utilise qu’un nombre réduit d’espaces pour des variables locales qui ne seront utilisables que dans l’unité de compilation courante, ou bien lorsqu’un pointeur doit faire référence à une variable pour retourner une valeur. Par exemple, dans le cas d’un appel de fonction NCI Native Call Interface qui permet d’accéder à la plupart des modules écrits en C.

    C’est la directive spécifique :unique_reg qui va permettre de mettre en application cette contrainte.

      coruscant chris$ cat unique.pir 

      .sub _ :main

        .local string chaine :unique_reg

        .local int entier :unique_reg

        .local num pi :unique_reg

        .local pmc ch :unique_reg

        chaine = "Bonjour.\n"

        print chaine

        entier = 10

        print entier

        print "\n"

        pi = 3.14159

        print pi

        print "\n"

        ch = new â€˜String’

        ch = "Salut a tous.\n"

        print ch

      .end

      coruscant chris$ parrot unique.pir 

      Bonjour.

      10

      3.14159

      Salut a tous.

      coruscant chris$ 

    Il est important de noter que la directive unique_reg n’affecte en aucun cas le comportement du programme, mais se contente de modifier la manière dont les registres sont alloués et affectés aux variables concernées. Ce n’est en définitive qu’un compromis entre l’occupation mémoire et l’optimisation du temps d’exécution du sous-programme, ce dernier n’ayant plus à se préoccuper de l’allocation des ressources.

    7.3

    Les Parrot Magic Cookies

    La notion de PMC a déjà été longuement décrite dans les précédents articles sur l’assembleur Parrot. Rappelons que ce sont des registres spécifiques qui ont la capacité de représenter n’importe quel type de structure (nombre entier, nombre flottant, chaîne de caractères, objet).

    On peut dire, pour faire court, qu’un PMC permet de définir un type qui se comporte de manière spécifique et qui va utiliser un agencement caractéristique appelé " v-table " pour référencer des méthodes particulières applicables aux objets qu’il doit décrire [v-table].

    De plus, un certain nombre de fonctions appropriées permettent à l’utilisateur de remplacer l’implémentation d’une méthode de base, héritée de la définition de la classe, par une séquence alternative qu’il aura lui même écrite. Cette opération, la surcharge ou polymorphisme ad-hoc consiste à traiter certains opérateurs comme des fonctions qui peuvent être définies ou redéfinies pour de nouveaux types de données [Surcharge].

    Physiquement, un registre PMC va contenir une référence vers une v-table qui n’est elle-même rien d’autre qu’une liste de pointeurs vers des fonctions dont le code réalise l’opération voulue pour le PMC concerné.

    En fait, une v-table n’est rien d’autre qu’un descripteur d’objet qui contient la représentation les caractéristiques et les références des méthodes dynamiquement liées à l’objet en question.

    On active une méthode en accédant à l’adresse du code définissant l’action à effectuer dans la table correspondant au descriptif de l’objet considéré.

    Toute instruction qui fait référence à un PMC, utilise de ce fait la v-table qui lui a été associée au moment de sa création pour accéder à la méthode appropriée pour l’opération demandée.

    Essentiellement, les PMC héritent d’une classe de base définie par le langage et exécutent les opérations réclamées en accord avec les caractéristiques spécifiques inhérentes aux structures concernées.

    Toutes ces notions seront largement détaillées lorsque nous aborderons la programmation orientée objet.

    7.4

    Principaux types de PMC

    Il existe un nombre conséquent de PMC disponibles dans la distribution de base.

    Par la suite, nous en utiliserons principalement deux types pour déclarer des scalaires ou des structures :

    - les PMC permettant de déclarer un type scalaire,

    - String pour déclarer une chaîne de caractères,

    - Integer pour déclarer un entier,

    - Float pour déclarer un nombre en virgule flottante ;

    - les PMC permettant de déclarer une structure,

    - Array pour déclarer une liste d’éléments hétérogènes,

    - FixedBooleanArray ou ResizableBooleanArray pour déclarer une liste de booléens de longueur fixe ou variable,

    - FixedStringArray ou ResizableStringArray pour déclarer une liste de chaînes de caractères de longueur fixe ou variable,

    - FixedIntegerArray ou ResizableIntegerArray pour déclarer une liste de valeurs entières de longueur fixe ou variable,

    - FixedFloatArray ou ResizableFloatArray pour déclarer une liste de valeurs flottantes de longueur fixe ou variable,

    - FixedPMCArray ou ResizablePMCArray pour déclarer une liste de valeurs flottantes de longueur fixe ou variable.

    Nombre d’autres PMC sont disponibles pour de multiples utilisations.

    La liste complète est disponible sur le site de Parrot [PMC].

    7.5

    Utilisation des PMC

    Un PMC doit être impérativement déclaré et instancié avant toute utilisation.

      coruscant chris$ cat cookie.pir 

      .sub "PMC":main

        .local pmc chaine

        .local pmc limite

        .local pmc pi

        chaine = new â€˜String’

        limite = new â€˜Integer’

        pi = new â€˜Float’

        chaine = "Salut a tous.\n"

        print chaine

        limite = 1000

        print limite

        print "\n"

        pi = 3.14159265

        print pi

        print "\n"

      .end

      coruscant chris$ parrot cookie.pir 

      Salut a tous.

      1000

      3.14159265

      coruscant chris$ 

    Dans cet exemple, on commence par déclarer trois PMC. La directive .local nous permet de créer les variables qui, par la suite, référenceront le PMC.

    On détermine alors pour chacune des variables le type spécifique de PMC qu’elles vont représenter. C’est par l’intermédiaire du constructeur new que nous pouvons spécifier que le premier, référencé chaine va contenir une chaîne de caractères (type String), que le second, référencé limite, une valeur entière (type Integer) et le dernier, pi, un nombre flottant (type Float).

    Il est maintenant possible de les utiliser en leur affectant une valeur et en affichant leur contenu.

    Il aurait aussi été possible d’adresser directement un registre PMC sans le référencer par l’intermédiaire d’une variable. Le programme obtenu est tout aussi valide bien que moins facile à interpréter.

      coruscant chris$ cat cookie.pir 

      .sub "PMC":main

        $P0 = new â€˜String’

        $P1 = new â€˜Integer’

        $P2 = new â€˜Float’

        $P0 = "Salut a tous.\n"

        print $P0

        $P1 = 1000

        print $P1

        print "\n"

        $P2 = 3.14159265

        print $P2

        print "\n"

      .end

     coruscant chris$ parrot cookie.pir 

      Salut a tous.

      1000

      3.14159265

      coruscant chris$

    Si on désire savoir à quel type d’objet un PMC a été rattaché, on dispose de l’instruction typeof. On lui transmet la référence d’un PMC et elle retourne une chaîne de caractères indiquant à quel type est rattaché le PMC correspondant.

      coruscant chris$ cat type.pir 

      .sub "PMC":main

        $P0 = new â€˜String’

        $P1 = new â€˜Integer’

        $P2 = new â€˜Float’

        $S0 = typeof $P0

        print "Le PMC P0 est de type "

        print $S0

        print ".\n"

        $S0 = typeof $P1

        print "Le PMC P1 est de type "

        print $S0

        print ".\n"

        $S0 = typeof $P2

        print "Le PMC P2 est de type "

        print $S0

        print ".\n"

      .end

      coruscant chris$ parrot type.pir 

      Le PMC P0 est de type String.

      Le PMC P1 est de type Integer.

      Le PMC P2 est de type Float.

      coruscant chris$

    Le même programme peut être entièrement réécrit de manière beaucoup plus accessible en utilisant des noms symboliques en lieu et place des références directes aux registres.

      coruscant chris$ cat type.pir 

      .sub "PMC":main

        .local string type 

        .local pmc chaine

        .local pmc entier

        .local pmc flottant

        chaine = new â€˜String’

        entier = new â€˜Integer’

        flottant = new â€˜Float’

        

        type = typeof chaine

        print "La variable â€˜chaine’ est de type "

        print type

        print ".\n"

        type = typeof entier

        print "La variable â€˜entier’ est de type "

        print type

        print ".\n"

        type = typeof flottant

        print "La variable â€˜flottant’ est de type "

        print type

        print ".\n"

      .end

      coruscant chris$ parrot type.pir 

      La variable â€˜chaine’ est de type String.

      La variable â€˜entier’ est de type Integer.

      La variable â€˜flottant’ est de type Float.

      coruscant chris$

    Une autre possibilité est offerte par l’instruction does. Elle se présente sous la forme : does booleen, PMC, "type" ou bien booleen = does PMC, "type". On obtient en retour la valeur Vrai si le PMC est du type spécifié, Faux dans le cas contraire.

      coruscant chris$ cat does.pir

      .sub _main

        .local int bool1

        .local pmc liste

        liste = new [‘ResizableFloatArray’]

        .local pmc chaine

        chaine = new [‘String’]

        .local string type 

        .local pmc chaine

        .local pmc entier

        .local pmc flottant

        chaine = new â€˜String’

        entier = new â€˜Integer’

        flottant = new â€˜Float’

        

        does bool1, chaine, "scalar"

        print bool1

        bool1 = does  liste, "array"

        print bool1

        does bool1, chaine, "string"

        print bool1

        bool1 =  does entier, "integer"

        print bool1

        does bool1, flottant, "float"

        print bool1

      .end

      coruscant chris$ parrot does.pir

      11111

      coruscant chris$

    7.6

    Changement de type

    C’est dans ce chapitre que nous allons découvrir tout ce qui se cache sous l’opération d’affectation (=).

    Comme c’est déjà le cas dans l’assembleur, l’affectation s’accompagne d’un changement de type si cette transformation s’avère nécessaire en fonction du contexte dans lequel elle doit s’exécuter.

    C’est le type de la variable ou du registre qui sert de destination à la donnée qui va déterminer s’il est nécessaire ou non de procéder à cette conversion.

    coruscant chris$ cat conversion.pir 

      .sub "Conversion" :main

      # Stockage d’un entier.

        $I0 = 50

        print "Affichage de l’entier : "

        print $I0

        print "\n"

      # Transformation de l’entier en chaîne de caractères.

        $S0 = $I0

        print "Affichage du caractere : "

        print $S0

        print "\n"

     # Transformation de la chaîne de caractères en un flottant.

        $N0 = $S0

        print "Affichage du flottant : "

        print $N0

        print "\n"

      # Transformation du flottant en entier.

        $I0 = $N0

        print "Affichage de l’entier : "

        print $I0

        print "\n"

      .end

      coruscant chris$ parrot conversion.pir 

      Affichage de l’entier : 50

      Affichage du caractere : 50

      Affichage du flottant : 50

      Affichage de l’entier : 50

      coruscant chris$

    Bien entendu, cette transformation est aussi effective lorsqu’on utilise des variables.

      coruscant chris$ cat conversion.pir 

      .sub "Conversion" :main

        .local num e 

        .local string chaine

        .local int chiffre

      # Stockage d’une chaîne.

        chaine = "2.71828183"

        print "Affichage de la chaine : "

        print chaine

        print "\n"

      # Transformation de la chaîne de caractères en flottant.

        e = chaine

        print "Affichage du flottant : "

        print e

        print "\n"

        # Transformation de la chaîne de caractères en entier.

        chiffre = chaine

        print "Affichage de l’entier : "

        print chiffre

        print "\n"

      .end

      coruscant chris$ parrot conversion.pir 

      Affichage de la chaine : 2.71828183

      Affichage du flottant : 2.71828183

      Affichage de l’entier : 2

      coruscant chris$

    Cette transformation d’une chaîne en entier trouvera son application naturelle dès que nous désirerons lire une information sur un fichier.

    Les règles de transformation d’une chaîne de caractères en valeur numérique sont les mêmes que celles qui s’appliquent dans Perl.

      coruscant chris$ cat conversion.pir 

      .sub "Conversion" :main

        .local int entier

        .local string chaine

      # Stockage d’une chaîne.

        chaine = "50 et plus."

        print "Affichage de la chaine : "

        print chaine

        print "\n"

      # Transformation de la chaîne de caractères en entier.

        entier = chaine

        print "Affichage de l’entier : "

        print entier

        print "\n"

      .end

      coruscant chris$ parrot conversion.pir 

      Affichage de la chaine : 50 et plus.

      Affichage de l’entier : 50

      coruscant chris$

    C’est cette caractéristique, reliée à l’opération autoboxing que nous allons détailler maintenant, qui permet de procéder à tous les changements possibles entre les divers types de données.

    7.7

    L’autoboxing

    À la base, Parrot Intermediate Representation est un langage dynamique. Cette caractéristique apparaît de manière particulièrement significative dans la manière de gérer les PMC.

    Nous avons vu qu’il existe des registres typés (chaîne, entiers et flottants) et que les PMC peuvent de leur côté définir des classes capables de représenter n’importe lequel des types que nous venons d’évoquer.

    C’est ainsi que les classes de PMC disponibles chaînes, entiers et flottants peuvent être sans aucune difficulté transformées en données scalaires chaînes, entières et flottantes.

    PIR appelle autoboxing l’opération qui consiste à convertir l’information lorsqu’on le transfère d’un registre typé S, I ou N vers une classe PMC ou inversement.

      coruscant chris$ cat autobox.pir 

      .sub "Autoboxing":main

      # Déclaration et instantiation des PMC.

        .local pmc chaine

        chaine = new â€˜String’

        .local pmc entier

        entier = new â€˜Integer’

        .local pmc flottant

        flottant = new â€˜Float’

      # Déclaration des valeurs natives.

        .local string hello

        .local num pi

        .local int indice

      # Affectation des valeurs aux PMC.

        chaine = "Salut a tous.\n"

        entier = 1000

        flottant = 3.14

      # Autoboxing PMC -> valeurs natives.

        hello = chaine

        indice = entier

        pi = flottant

      # Affectation des valeurs natives.

        hello = "Comment allez vous ?\n"

        pi = 3.14159

        indice = 25

      # Autoboxing valeurs natives -> PMC.

        chaine = hello

        flottant = pi

        entier = indice

      .end

      coruscant chris$

    8

    Les constantes nommées

    La directive .const va nous permettre de déclarer un nom de constante. Elle est très semblable à la directive .local, car, comme elle, elle requiert un type et un nom.

    Une constante se voit attribuer une valeur au moment de sa déclaration, et comme pour les variables, elles ne sont visibles que dans l’unité de compilation dans laquelle elles ont été déclarées.

      coruscant chris$ cat constantes.pir 

      .sub "constantes":main

        .const string salut = "Bonjour tout le monde\n"

        .const int dix = 10

        .const num pi = 3.14159265

        print salut

        print dix

        print "\n"

        print pi

        print "\n"

      .end

      coruscant chris$ parrot constantes.pir 

      Bonjour tout le monde

      10

      3.14159265

      coruscant chris$

    Toute constante doit être déclarée avant d’être utilisée, et, comme on peut s’en douter, il est interdit de modifier une constante dans le cours du programme.

      coruscant chris$ cat constantes.pir 

      .sub "constantes":main

        .const int dix = 10

        print dix

        print "\n"

        dix = 11

      .end

      coruscant chris$ parrot constantes.pir 

      error:imcc:The opcode â€˜set_ic_ic’ (set<2>) was not found.

      Check the type and number of the arguments

            in file â€˜test.pir’ line 6

      coruscant chris$

    Il est facile de comprendre que l’unité de compilation voit apparaître une référence à une variable dix qui n’a jamais été déclarée dans une directive .local.

    Si on désire déclarer une constante globale, c’est la directive .globalconst qui doit être utilisée.

      coruscant chris$ cat constantes.pir 

      .sub "constantes":main

        .globalconst string salut = "Bonjour tout le monde\n"

        .globalconst int dix = 10

        .globalconst num pi = 3.14159265

        suite ()

      .end

      .sub suite

        print salut

        print dix

        print "\n"

        print pi

        print "\n"

      .end

      coruscant chris$ parrot constantes.pir 

      Bonjour tout le monde

      10

      3.14159265

      coruscant chris$

    9

    Conclusion provisoire

    Cette première approche du langage PIR nous aura permis de définir toutes les notions dont nous aurons besoin pour aborder les divers aspects de la programmation que nous allons détailler dans les futures présentations.

    Cette manière d’aborder la programmation pourra surprendre les familiers des langages de bas niveau, mais aussi ceux qui utilisent des langages évolués. Elle a néanmoins d’indéniables qualités : permettre l’écriture facile et rapide de programmes grâce à un jeu d’instructions extrêmement varié et particulièrement développé et mettre à la disposition des usagers un système d’allocation qui permet de se détacher quelque peu de la structure de base de la machine virtuelle.

    Comme son nom l’indique bien, PIR est un langage intermédiaire.

    Références

    - [Lang Int] La notion de langage intermédiaire, http://fr.wikipedia.org/wiki/Langage_intermédiaire

    - [PASM01] " La machine Parrot (1) ", in GNU/Linux Magazine France n°97, septembre 2007, http://articles.mongueurs.net/magazines/linuxmag97.html

    - [PASM02] " La machine Parrot (2 ) ", in GNU/Linux Magazine France n°98, octobre 2007, http://articles.mongueurs.net/magazines/linuxmag98.html

    - [PASM03] " La machine Parrot (3) ", in GNU/Linux Magazine France n°99, novembre 2007, http://articles.mongueurs.net/magazines/linuxmag99.html

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

    - [Back01] BACKUS (John), Specifications for the IBM Mathematical FORmula TRANSlating System, International Business Machines Corporation (IBM), 10 novembre 1954, http://www.computerhistory.org/collections/accession/102679231, http://www.columbia.edu/acis/history/backus.html

    - [NQP] Not Quite Perl, http://docs.parrot.org/parrot/latest/html/docs/book/ch06_nqp.pod.html

    - [Perl6] Le site officiel de Perl 6, http://www.perl6.org/

    - [ICU] International Components for Unicode, http://site.icu-project.org/

    - [v-table] Virtual method table, http://en.wikipedia.org/wiki/Virtual_table/

    - [Surcharge] Surcharge des opérateurs, http://fr.wikipedia.org/wiki/Surcharge_des_opérateurs

    - [PMC] Site officiel de la machine Parrot, http://docs.parrot.org/parrot/latest/html/pmc.html

    Retrouvez cet article dans : Linux Magazine 122

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

    Laissez une réponse

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