Parrot - Esempi di programmazione

La programmazione di Parrot è simile alla programmazione in linguaggio assembly e hai la possibilità di lavorare a un livello inferiore. Di seguito è riportato un elenco di esempi di programmazione per farti conoscere i vari aspetti della programmazione Parrot.

Ciao mondo classico!

Crea un file chiamato hello.pir che contiene il codice seguente:

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

Quindi eseguilo digitando:

parrot hello.pir

Come previsto, verrà visualizzato il testo "Hello world!" sulla console, seguito da una nuova riga (a causa del \ n).

In questo esempio precedente, ".sub _main" afferma che le istruzioni che seguono costituiscono una subroutine denominata "_main", finché non viene incontrato un ".end". La seconda riga contiene l'istruzione di stampa. In questo caso, stiamo chiamando la variante dell'istruzione che accetta una stringa costante. L'assemblatore si occupa di decidere quale variante dell'istruzione utilizzare per noi. La terza riga contiene l'istruzione "end", che fa terminare l'interprete.

Utilizzo dei registri

Possiamo modificare hello.pir per memorizzare prima la stringa Hello world! \ N in un registro e quindi utilizzare quel registro con l'istruzione di stampa.

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

Qui abbiamo indicato esattamente quale registro utilizzare. Tuttavia, sostituendo S1 con $ S1 possiamo delegare a Parrot la scelta di quale registro utilizzare. È anche possibile utilizzare una notazione = invece di scrivere l'istruzione set.

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

Per rendere il PIR ancora più leggibile, è possibile utilizzare registri con nome. Questi vengono successivamente mappati su registri numerati reali.

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

La direttiva ".local" indica che il registro denominato è necessario solo all'interno dell'unità di compilazione corrente (ovvero, tra .sub e .end). Il seguente ".local" è un tipo. Questo può essere int (per i registri I), float (per N registri), string (per i registri S), pmc (per i registri P) o il nome di un tipo PMC.

Somma dei quadrati

Questo esempio introduce altre istruzioni e la sintassi PIR. Le righe che iniziano con un # sono commenti.

.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

Il PIR fornisce un po 'di zucchero sintattico che lo fa sembrare di livello più alto rispetto all'assemblaggio. Per esempio:

temp = i * i

È solo un altro modo di scrivere più assembly-ish:

mul temp, i, i

E:

if i <= maxnum goto loop

Equivale a:

le i, maxnum, loop

E:

total += temp

Equivale a:

add total, temp

Di norma, ogni volta che un'istruzione Parrot modifica il contenuto di un registro, quello sarà il primo registro quando si scrive l'istruzione in forma di assemblaggio.

Come di consueto nei linguaggi assembly, i cicli e le selezioni sono implementati in termini di istruzioni ed etichette di rami condizionali, come mostrato sopra. La programmazione in assembly è un posto in cui usare goto non è una cattiva forma!

Numeri di Fibonacci

La serie di Fibonacci è definita in questo modo: prendi due numeri, 1 e 1. Quindi somma ripetutamente gli ultimi due numeri della serie per formare il successivo: 1, 1, 2, 3, 5, 8, 13 e così via . Il numero di Fibonacci fib (n) è l'ennesimo numero della serie. Ecco un semplice programma di assemblaggio Parrot che trova i primi 20 numeri di 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

Questo è il codice equivalente in 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:Un bel punto di interesse, uno dei modi più brevi e certamente più belli per stampare una serie di Fibonacci in Perl è perl -le '$ b = 1; print $ a + = $ b mentre print $ b + = $ a '.

Calcolo ricorsivo fattoriale

In questo esempio definiamo una funzione fattoriale e la chiamiamo ricorsivamente per calcolare fattoriale.

.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

Diamo prima un'occhiata al sub _fact. Un punto che è stato sorvolato in precedenza è il motivo per cui i nomi delle subroutine iniziano tutti con un trattino basso! Questo viene fatto semplicemente come un modo per mostrare che l'etichetta è globale piuttosto che limitata a una particolare subroutine. Ciò è significativo in quanto l'etichetta è quindi visibile ad altre subroutine.

La prima riga, .param int n, specifica che questa subroutine accetta un parametro intero e che vorremmo fare riferimento al registro in cui è stato passato con il nome n per il resto del sub.

Molto di ciò che segue è stato visto negli esempi precedenti, a parte la lettura della riga:

result = _fact($I0)

Questa singola riga di PIR in realtà rappresenta alcune righe di PASM. Per prima cosa, il valore nel registro $ I0 viene spostato nel registro appropriato per essere ricevuto come parametro intero dalla funzione _fact. Vengono quindi impostati altri registri relativi alle chiamate, seguiti dall'invocazione di _fact. Quindi, una volta che _fact ritorna, il valore restituito da _fact viene inserito nel registro dato il nome risultato.

Subito prima del .end del sub _fact, viene utilizzata una direttiva .return per garantire il valore contenuto nel registro; Il risultato denominato viene inserito nel registro corretto in modo che possa essere visto come valore di ritorno dal codice che chiama il sub.

La chiamata a _fact in main funziona esattamente allo stesso modo della chiamata ricorsiva a _fact all'interno del sub _fact stesso. L'unico bit rimanente della nuova sintassi è: main, scritto dopo .sub _main. Per impostazione predefinita, PIR presuppone che l'esecuzione inizi con il primo sub nel file. Questo comportamento può essere modificato contrassegnando il sottotitolo per iniziare con: main.

Compilazione su PBC

Per compilare PIR in bytecode, utilizzare il flag -o e specificare un file di output con estensione .pbc.

parrot -o factorial.pbc factorial.pir

PIR contro PASM

PIR può essere trasformato in PASM eseguendo:

parrot -o hello.pasm hello.pir

Il PASM per l'esempio finale ha questo aspetto:

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

PASM non gestisce l'allocazione dei registri né fornisce supporto per i registri denominati. Inoltre non ha le direttive .sub e .end, sostituendole invece con un'etichetta all'inizio delle istruzioni.