Parrot - Guide rapide

Lorsque nous introduisons notre programme en Perl conventionnel, il est d'abord compilé dans une représentation interne, ou bytecode; ce bytecode est ensuite introduit dans un sous-système presque séparé à l'intérieur de Perl pour être interprété. Il y a donc deux phases distinctes du fonctionnement de Perl:

  • Compilation en bytecode et

  • Interprétation du bytecode.

Ce n'est pas propre à Perl. D'autres langages suivant cette conception incluent Python, Ruby, Tcl et même Java.

Nous savons également qu'il existe une machine virtuelle Java (JVM) qui est un environnement d'exécution indépendant de la plate-forme qui convertit le bytecode Java en langage machine et l'exécute. Si vous comprenez ce concept, vous comprendrez Parrot.

Parrotest une machine virtuelle conçue pour compiler et exécuter efficacement le bytecode pour les langages interprétés. Parrot est la cible du compilateur final Perl 6, et est utilisé comme backend pour Pugs, ainsi que pour divers autres langages comme Tcl, Ruby, Python etc.

Parrot a été écrit en utilisant le langage le plus populaire "C".

Avant de commencer, téléchargeons une dernière copie de Parrot et installons-la sur notre machine.

Le lien de téléchargement Parrot est disponible dans Parrot CVS Snapshot . Téléchargez la dernière version de Parrot et pour l'installer suivez les étapes suivantes:

  • Décompressez et décompressez le fichier téléchargé.

  • Assurez-vous que Perl 5 est déjà installé sur votre machine.

  • Maintenant, procédez comme suit:

% cd parrot
% perl Configure.pl
Parrot Configure
Copyright (C) 2001 Yet Another Society
Since you're running this script, you obviously have
Perl 5 -- I'll be pulling some defaults from its configuration.
...
  • On vous posera ensuite une série de questions sur votre configuration locale; vous pouvez presque toujours appuyer sur retour / entrée pour chacun d'eux.

  • Enfin, on vous demandera de taper - make test_prog, et Parrot construira avec succès l'interpréteur de test.

  • Maintenant, vous devriez exécuter quelques tests; alors tapez 'make test' et vous devriez voir une lecture comme celle-ci:

perl t/harness
t/op/basic.....ok,1/2 skipped:label constants unimplemented in
assembler
t/op/string....ok, 1/4 skipped:  I'm unable to write it!
All tests successful, 2 subtests skipped.
Files=2, Tests=6,......

Au moment où vous lirez ceci, il pourrait y avoir plus de tests, et certains de ceux qui ont sauté pourraient ne pas sauter, mais assurez-vous qu'aucun d'entre eux ne devrait échouer!

Une fois que vous avez installé un exécutable parrot, vous pouvez consulter les différents types d'exemples donnés dans la section «Exemples» de Parrot . Vous pouvez également consulter le répertoire des exemples dans le référentiel parrot.

Parrot peut actuellement accepter des instructions à exécuter sous quatre formes. PIR (Parrot Intermediate Representation) est conçu pour être écrit par des personnes et généré par des compilateurs. Il cache certains détails de bas niveau, tels que la manière dont les paramètres sont transmis aux fonctions.

PASM (Parrot Assembly) est un niveau inférieur au PIR - il est toujours lisible / inscriptible par l'homme et peut être généré par un compilateur, mais l'auteur doit prendre soin des détails tels que les conventions d'appel et l'allocation des registres. PAST (Parrot Abstract Syntax Tree) permet à Parrot d'accepter une entrée de style arbre de syntaxe abstraite - utile pour ceux qui écrivent des compilateurs.

Toutes les formes d'entrée ci-dessus sont automatiquement converties dans Parrot en PBC (Parrot Bytecode). Cela ressemble beaucoup au code machine, mais compris par l'interpréteur Parrot.

Il n'est pas destiné à être lisible par l'homme ou accessible en écriture, mais contrairement aux autres formulaires, l'exécution peut démarrer immédiatement sans nécessiter de phase d'assemblage. Le bytecode Parrot est indépendant de la plateforme.

Le jeu d'instructions

Le jeu d'instructions Parrot comprend des opérateurs arithmétiques et logiques, compare et branch / jump (pour implémenter des boucles, si ... alors des constructions, etc.), trouver et stocker des variables globales et lexicales, travailler avec des classes et des objets, appeler des sous-programmes et des méthodes avec leurs paramètres, E / S, threads et plus encore.

Comme Java Virtual Machine, Parrot vous évite également de vous soucier de la désallocation de la mémoire.

  • Parrot assure le ramassage des ordures.

  • Les programmes Parrot n'ont pas besoin de libérer explicitement de la mémoire.

  • La mémoire allouée sera libérée lorsqu'elle n'est plus utilisée, c'est-à-dire qu'elle n'est plus référencée.

  • Parrot Garbage Collector s'exécute périodiquement pour s'occuper de la mémoire indésirable.

Le processeur Parrot possède quatre types de données de base:

  • IV

    Un type entier; garantie d'être suffisamment large pour contenir un pointeur.

  • NV

    Un type à virgule flottante indépendant de l'architecture.

  • STRING

    Type de chaîne abstrait et indépendant du codage.

  • PMC

    Un scalaire.

Les trois premiers types sont assez explicites; le dernier type - Parrot Magic Cookies, est un peu plus difficile à comprendre.

Que sont les PMC?

PMC signifie Parrot Magic Cookie. Les PMC représentent toute structure ou type de données complexe, y compris les types de données agrégées (tableaux, tables de hachage, etc.). Un PMC peut implémenter son propre comportement pour les opérations arithmétiques, logiques et de chaîne qui y sont effectuées, ce qui permet d'introduire un comportement spécifique au langage. Les PMC peuvent être intégrés à l'exécutable Parrot ou chargés dynamiquement quand ils sont nécessaires.

La machine virtuelle Perl 5 actuelle est une machine à pile. Il communique des valeurs entre les opérations en les gardant sur une pile. Les opérations chargent des valeurs sur la pile, font ce qu'elles doivent faire et remettent le résultat dans la pile. C'est facile à travailler, mais c'est lent.

Pour ajouter deux nombres ensemble, vous devez effectuer trois poussées de pile et deux pop de pile. Pire encore, la pile doit croître au moment de l'exécution, ce qui signifie allouer de la mémoire juste au moment où vous ne voulez pas l'allouer.

Parrot va donc briser la tradition établie des machines virtuelles, et utiliser une architecture de registre, plus proche de l'architecture d'un processeur matériel réel. Cela présente un autre avantage. Nous pouvons utiliser toute la littérature existante sur la façon d'écrire des compilateurs et des optimiseurs pour les processeurs basés sur des registres pour nos processeurs logiciels!

Parrot dispose de registres spécialisés pour chaque type: 32 registres IV, 32 registres NV, 32 registres string et 32 ​​registres PMC. Dans l'assembleur Parrot, ils sont nommés respectivement I1 ... I32, N1 ... N32, S1 ... S32, P1 ... P32.

Regardons maintenant un assembleur. Nous pouvons définir ces registres avec l'opérateur set:

set I1, 10
	set N1, 3.1415
	set S1, "Hello, Parrot"

Toutes les opérations Parrot ont le même format: le nom de l'opérateur, le registre de destination puis les opérandes.

Vous pouvez effectuer diverses opérations. Par exemple, nous pouvons imprimer le contenu d'un registre ou d'une constante:

set I1, 10
print "The contents of register I1 is: "
print I1
print "\n"

Les instructions ci-dessus aboutiront à Le contenu du registre I1 est: 10

Nous pouvons effectuer des opérations mathématiques sur les registres:

# Add the contents of I2 to the contents of I1
add I1, I1, I2
# Multiply I2 by I4 and store in I3
mul I3, I2, I4
# Increment I1 by one
inc I1
# Decrement N3 by 1.5
dec N3, 1.5

Nous pouvons même effectuer une simple manipulation de chaîne:

set S1, "fish"
set S2, "bone"
concat S1, S2       # S1 is now "fishbone"
set S3, "w"
substr S4, S1, 1, 7
concat S3, S4       # S3 is now "wishbone"
length I1, S3       # I1 is now 8

Le code devient un peu ennuyeux sans contrôle de flux; pour commencer, Parrot connaît les branches et les étiquettes. L'op de branche est équivalent au goto de Perl:

branch TERRY
JOHN:    print "fjords\n"
         branch END
MICHAEL: print " pining"
         branch GRAHAM
TERRY:   print "It's"
         branch MICHAEL
GRAHAM:  print " for the "
         branch JOHN
END:     end

Il peut également effectuer des tests simples pour voir si un registre contient une valeur vraie:

set I1, 12
         set I2, 5
         mod I3, I2, I2
         if I3, REMAIND, DIVISOR
REMAIND: print "5 divides 12 with remainder "
         print I3
         branch DONE
DIVISOR: print "5 is an integer divisor of 12"
DONE:    print "\n"
         end

Voici à quoi cela ressemblerait en Perl, à titre de comparaison:

$i1 = 12;
    $i2 = 5;
    $i3 = $i1 % $i2;
    if ($i3) {
      print "5 divides 12 with remainder ";
      print $i3;
    } else {
      print "5 is an integer divisor of 12";
    }
    print "\n";
    exit;

Opérateur de perroquet

Nous avons la gamme complète de comparateurs numériques: eq, ne, lt, gt, le et ge. Notez que vous ne pouvez pas utiliser ces opérateurs sur des arguments de types disparates; vous devrez peut-être même ajouter le suffixe _i ou _n à l'op, pour lui dire quel type d'argument vous utilisez, bien que l'assembleur doive deviner cela pour vous, au moment où vous lirez ceci.

La programmation Parrot est similaire à la programmation en langage assembleur et vous avez la possibilité de travailler à un niveau inférieur. Voici la liste d'exemples de programmation pour vous familiariser avec les différents aspects de la programmation Parrot.

Bonjour tout le monde classique!

Créez un fichier appelé hello.pir qui contient le code suivant:

.sub _main
      print "Hello world!\n"
      end
  .end

Ensuite, exécutez-le en tapant:

parrot hello.pir

Comme prévu, cela affichera le texte "Bonjour tout le monde!" sur la console, suivi d'une nouvelle ligne (en raison du \ n).

Dans cet exemple ci-dessus, «.sub _main» indique que les instructions qui suivent constituent un sous-programme nommé «_main», jusqu'à ce qu'un «.end» soit rencontré. La deuxième ligne contient l'instruction d'impression. Dans ce cas, nous appelons la variante de l'instruction qui accepte une chaîne constante. L'assembleur se charge de décider quelle variante de l'instruction à utiliser pour nous. La troisième ligne contient l'instruction «end», qui entraîne la fin de l'interpréteur.

Utilisation des registres

Nous pouvons modifier hello.pir pour stocker d'abord la chaîne Hello world! \ N dans un registre, puis utiliser ce registre avec l'instruction d'impression.

.sub _main
      set S1, "Hello world!\n"
      print S1
      end
  .end

Ici, nous avons indiqué exactement quel registre utiliser. Cependant, en remplaçant S1 par $ S1, nous pouvons déléguer le choix du registre à utiliser à Parrot. Il est également possible d'utiliser une notation = au lieu d'écrire l'instruction set.

.sub _main
      $S0 = "Hello world!\n"
      print $S0
      end
  .end

Pour rendre le PIR encore plus lisible, des registres nommés peuvent être utilisés. Ceux-ci sont ensuite mappés sur des registres numérotés réels.

.sub _main
      .local string hello
      hello = "Hello world!\n"
      print hello
      end
  .end

La directive '.local' indique que le registre nommé n'est nécessaire qu'à l'intérieur de l'unité de compilation courante (c'est-à-dire entre .sub et .end). Après '.local' est un type. Cela peut être int (pour les registres I), float (pour N registres), string (pour les registres S), pmc (pour les registres P) ou le nom d'un type PMC.

La somme des carrés

Cet exemple présente quelques instructions supplémentaires et la syntaxe PIR. Les lignes commençant par un # sont des commentaires.

.sub _main
      # State the number of squares to sum.
      .local int maxnum
      maxnum = 10

      # Some named registers we'll use. 
      # Note how we can declare many
      # registers of the same type on one line.
      .local int i, total, temp
      total = 0

      # Loop to do the sum.
      i = 1
  loop:
      temp = i * i
      total += temp
      inc i
      if i <= maxnum goto loop

      # Output result.
      print "The sum of the first "
      print maxnum
      print " squares is "
      print total
      print ".\n"
      end
  .end

PIR fournit un peu de sucre syntaxique qui lui donne un aspect plus haut niveau que l'assemblage. Par exemple:

temp = i * i

Est juste une autre façon d'écrire le plus assembleur:

mul temp, i, i

Et:

if i <= maxnum goto loop

Est le même que:

le i, maxnum, loop

Et:

total += temp

Est le même que:

add total, temp

En règle générale, chaque fois qu'une instruction Parrot modifie le contenu d'un registre, ce sera le premier registre lors de l'écriture de l'instruction sous forme d'assemblage.

Comme d'habitude dans les langages d'assemblage, les boucles et les sélections sont implémentées en termes d'instructions de branchement conditionnelles et d'étiquettes, comme indiqué ci-dessus. La programmation d'assemblage est un endroit où l'utilisation de goto n'est pas une mauvaise forme!

Numéros de Fibonacci

La série de Fibonacci est définie comme ceci: prenez deux nombres, 1 et 1. Ensuite, additionnez à plusieurs reprises les deux derniers nombres de la série pour faire le suivant: 1, 1, 2, 3, 5, 8, 13, et ainsi de suite . Le nombre de Fibonacci fib (n) est le nième nombre de la série. Voici un simple programme assembleur Parrot qui trouve les 20 premiers nombres de Fibonacci:

# Some simple code to print some Fibonacci numbers

        print   "The first 20 fibonacci numbers are:\n"
        set     I1, 0
        set     I2, 20
        set     I3, 1
        set     I4, 1
REDO:   eq      I1, I2, DONE, NEXT
NEXT:   set     I5, I4
        add     I4, I3, I4
        set     I3, I5
        print   I3
        print   "\n"
        inc     I1
        branch  REDO
DONE:   end

C'est le code équivalent en Perl:

print "The first 20 fibonacci numbers are:\n";
        my $i = 0;
        my $target = 20;
        my $a = 1;
        my $b = 1;
        until ($i == $target) {
           my $num = $b;
           $b += $a;
           $a = $num;
           print $a,"\n";
           $i++;
        }

NOTE:Comme point intéressant, l'un des moyens les plus courts et certainement les plus beaux d'imprimer une série de Fibonacci en Perl est perl -le '$ b = 1; print $ a + = $ b tandis que print $ b + = $ a '.

Calcul factoriel récursivement

Dans cet exemple, nous définissons une fonction factorielle et l'appelons récursivement pour calculer factorielle.

.sub _fact
      # Get input parameter.
      .param int n

      # return (n > 1 ? n * _fact(n - 1) : 1)
      .local int result

      if n > 1 goto recurse
      result = 1
      goto return

  recurse:
      $I0 = n - 1
      result = _fact($I0)
      result *= n

  return:
      .return (result)
  .end


  .sub _main :main
      .local int f, i

      # We'll do factorial 0 to 10.
      i = 0
  loop:
      f = _fact(i)

      print "Factorial of "
      print i
      print " is "
      print f
      print ".\n"

      inc i
      if i <= 10 goto loop

      # That's it.
      end
  .end

Regardons d'abord le sous _fact. Un point qui a été passé sous silence plus tôt est la raison pour laquelle les noms des sous-programmes commencent tous par un trait de soulignement! Ceci est fait simplement pour montrer que l'étiquette est globale plutôt que portée à un sous-programme particulier. Ceci est important car l'étiquette est alors visible pour les autres sous-programmes.

La première ligne, .param int n, spécifie que ce sous-programme prend un paramètre entier et que nous aimerions faire référence au registre dans lequel il a été passé par le nom n pour le reste du sous.

Une grande partie de ce qui suit a été vue dans les exemples précédents, à part la lecture de la ligne:

result = _fact($I0)

Cette seule ligne de PIR représente en fait pas mal de lignes de PASM. Tout d'abord, la valeur du registre $ I0 est déplacée dans le registre approprié pour qu'elle soit reçue comme paramètre entier par la fonction _fact. D'autres registres liés à l'appel sont alors configurés, suivis de l'appel de _fact. Ensuite, une fois _fact retourné, la valeur retournée par _fact est placée dans le registre en fonction du nom result.

Juste avant la fin du sous _fact, une directive .return est utilisée pour garantir la valeur contenue dans le registre; Le résultat nommé est placé dans le registre correct pour qu'il soit vu comme une valeur de retour par le code appelant le sous.

L'appel à _fact dans main fonctionne exactement de la même manière que l'appel récursif à _fact dans le sous _fact lui-même. Le seul bit restant de nouvelle syntaxe est le: main, écrit après .sub _main. Par défaut, PIR suppose que l'exécution commence par le premier sous du fichier. Ce comportement peut être modifié en marquant le sous pour commencer par: main.

Compilation vers PBC

Pour compiler PIR en bytecode, utilisez l'indicateur -o et spécifiez un fichier de sortie avec l'extension .pbc.

parrot -o factorial.pbc factorial.pir

PIR contre PASM

PIR peut être transformé en PASM en exécutant:

parrot -o hello.pasm hello.pir

Le PASM pour l'exemple final ressemble à ceci:

_main:
      set S30, "Hello world!\n"
      print S30
      end

PASM ne gère pas l'allocation de registre ni ne prend en charge les registres nommés. Il n'a pas non plus les directives .sub et .end, mais les remplace par une étiquette au début des instructions.