Parrot - Guida rapida

Quando inseriamo il nostro programma in Perl convenzionale, viene prima compilato in una rappresentazione interna, o bytecode; questo bytecode viene quindi inserito in un sottosistema quasi separato all'interno di Perl per essere interpretato. Quindi ci sono due fasi distinte dell'operazione di Perl:

  • Compilazione in bytecode e

  • Interpretazione del bytecode.

Questo non è esclusivo di Perl. Altre lingue che seguono questo design includono Python, Ruby, Tcl e persino Java.

Sappiamo anche che esiste una Java Virtual Machine (JVM) che è un ambiente di esecuzione indipendente dalla piattaforma che converte il bytecode Java in linguaggio macchina e lo esegue. Se comprendi questo concetto, capirai Parrot.

Parrotè una macchina virtuale progettata per compilare ed eseguire in modo efficiente bytecode per linguaggi interpretati. Parrot è l'obiettivo del compilatore finale Perl 6 ed è usato come backend per Pugs, così come una varietà di altri linguaggi come Tcl, Ruby, Python ecc.

Parrot è stato scritto utilizzando il linguaggio più diffuso "C".

Prima di iniziare, scarichiamo l'ultima copia di Parrot e installiamola sul nostro computer.

Il collegamento per il download di Parrot è disponibile in Parrot CVS Snapshot . Scarica l'ultima versione di Parrot e per installarla segui i seguenti passaggi:

  • Decomprimere e decomprimere il file scaricato.

  • Assicurati di avere già Perl 5 installato sulla tua macchina.

  • Ora fai quanto segue:

% 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.
...
  • Ti verranno quindi poste una serie di domande sulla configurazione locale; puoi quasi sempre premere invio / invio per ognuno.

  • Infine, ti verrà detto di digitare - make test_prog e Parrot costruirà con successo l'interprete di prova.

  • Ora dovresti eseguire alcuni test; quindi digita 'make test' e dovresti vedere una lettura come la seguente:

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

Quando leggerai questo articolo, potrebbero esserci più test, e alcuni di quelli che sono stati saltati potrebbero non saltare, ma assicurati che nessuno di essi fallisca!

Dopo aver installato un eseguibile di Parrot, puoi controllare i vari tipi di esempi forniti nella sezione "Esempi" di Parrot . Inoltre puoi controllare la directory degli esempi nel repository di parrot.

Parrot può attualmente accettare istruzioni da eseguire in quattro forme. PIR (Parrot Intermediate Representation) è progettato per essere scritto da persone e generato da compilatori. Nasconde alcuni dettagli di basso livello, come il modo in cui i parametri vengono passati alle funzioni.

PASM (Parrot Assembly) è un livello inferiore al PIR: è ancora leggibile / scrivibile e può essere generato da un compilatore, ma l'autore deve occuparsi di dettagli come le convenzioni di chiamata e l'allocazione dei registri. PAST (Parrot Abstract Syntax Tree) consente a Parrot di accettare un input in stile albero di sintassi astratta, utile per chi scrive compilatori.

Tutte le forme di input di cui sopra vengono automaticamente convertite all'interno di Parrot in PBC (Parrot Bytecode). È molto simile al codice macchina, ma compreso dall'interprete Parrot.

Non è concepito per essere leggibile o scrivibile dall'uomo, ma a differenza degli altri moduli l'esecuzione può iniziare immediatamente senza la necessità di una fase di assemblaggio. Il bytecode di Parrot è indipendente dalla piattaforma.

Il set di istruzioni

Il set di istruzioni di Parrot include operatori aritmetici e logici, confronta e salta / salta (per implementare cicli, se ... poi costruisce, ecc.), Trovare e memorizzare variabili globali e lessicali, lavorare con classi e oggetti, chiamare subroutine e metodi insieme con i loro parametri, I / O, thread e altro.

Come Java Virtual Machine, anche Parrot ti evita di preoccuparti della de-allocazione della memoria.

  • Parrot fornisce la raccolta dei rifiuti.

  • I programmi Parrot non devono liberare esplicitamente memoria.

  • La memoria allocata verrà liberata quando non è più in uso, ovvero non è più referenziata.

  • Parrot Garbage Collector viene eseguito periodicamente per prendersi cura della memoria indesiderata.

La CPU Parrot ha quattro tipi di dati di base:

  • IV

    Un tipo intero; garantito per essere abbastanza largo da contenere un puntatore.

  • NV

    Un tipo a virgola mobile indipendente dall'architettura.

  • STRING

    Un tipo di stringa astratto e indipendente dalla codifica.

  • PMC

    Uno scalare.

I primi tre tipi sono praticamente autoesplicativi; il tipo finale - Parrot Magic Cookies, è leggermente più difficile da capire.

Cosa sono i PMC?

PMC è l'acronimo di Parrot Magic Cookie. I PMC rappresentano qualsiasi struttura o tipo di dati complessi, inclusi i tipi di dati aggregati (array, tabelle hash, ecc.). Un PMC può implementare il proprio comportamento per le operazioni aritmetiche, logiche e sulle stringhe eseguite su di esso, consentendo l'introduzione di comportamenti specifici del linguaggio. I PMC possono essere integrati nell'eseguibile Parrot o caricati dinamicamente quando necessario.

L'attuale macchina virtuale Perl 5 è una macchina stack. Comunica i valori tra le operazioni mantenendoli in pila. Le operazioni caricano i valori nello stack, fanno tutto ciò di cui hanno bisogno e rimettono il risultato nello stack. Questo è facile da lavorare, ma è lento.

Per aggiungere due numeri insieme, è necessario eseguire tre stack push e due stack pop. Peggio ancora, lo stack deve crescere in fase di esecuzione e ciò significa allocare memoria proprio quando non si desidera allocarlo.

Quindi Parrot romperà la tradizione consolidata per le macchine virtuali e utilizzerà un'architettura a registro, più simile all'architettura di una CPU hardware reale. Questo ha un altro vantaggio. Possiamo usare tutta la letteratura esistente su come scrivere compilatori e ottimizzatori per CPU basate su registri per la nostra CPU software!

Parrot dispone di registri specializzati per ogni tipo: 32 registri IV, 32 registri NV, 32 registri stringa e 32 registri PMC. In Parrot assembler, questi sono denominati rispettivamente I1 ... I32, N1 ... N32, S1 ... S32, P1 ... P32.

Ora diamo un'occhiata a qualche assemblatore. Possiamo impostare questi registri con l'operatore set:

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

Tutte le operazioni Parrot hanno lo stesso formato: il nome dell'operatore, il registro di destinazione e poi gli operandi.

Ci sono una varietà di operazioni che puoi eseguire. Ad esempio, possiamo stampare il contenuto di un registro o di una costante:

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

Le istruzioni di cui sopra risulteranno in Il contenuto del registro I1 è: 10

Possiamo eseguire operazioni matematiche sui registri:

# 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

Possiamo anche eseguire alcune semplici manipolazioni di stringhe:

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

Il codice diventa un po 'noioso senza controllo del flusso; per cominciare, Parrot conosce le ramificazioni e le etichette. L'operazione branch è equivalente a goto di 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

Può anche eseguire semplici test per vedere se un registro contiene un valore vero:

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

Ecco come apparirebbe in Perl, per confronto:

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

Operatore pappagallo

Abbiamo l'intera gamma di comparatori numerici: eq, ne, lt, gt, le e ge. Nota che non puoi usare questi operatori su argomenti di tipi disparati; potresti anche dover aggiungere il suffisso _i o _n all'op, per dirgli che tipo di argomento stai usando, sebbene l'assemblatore dovrebbe indovinarlo per te, quando lo leggerai.

La programmazione di Parrot è simile alla programmazione in linguaggio assembly e hai la possibilità di lavorare a un livello inferiore. Ecco 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 contenga 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. Può essere int (per i registri I), float (per N registri), stringa (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.