Parrot - Exemples de programmation

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 branche 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, etc. . 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:Il est intéressant de noter que 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 en tant que 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.