Parrot - exemplos de programação

A programação do Parrot é semelhante à programação em linguagem assembly e você tem a chance de trabalhar em um nível inferior. Aqui está a lista de exemplos de programação para torná-lo ciente dos vários aspectos da Programação do Parrot.

Olá, mundo clássico!

Crie um arquivo chamado hello.pir que contém o seguinte código:

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

Em seguida, execute-o digitando:

parrot hello.pir

Como esperado, isso exibirá o texto 'Olá, mundo!' no console, seguido por uma nova linha (devido ao \ n).

Neste exemplo acima, '.sub _main' afirma que as instruções que seguem formam uma sub-rotina chamada '_main', até que um '.end' seja encontrado. A segunda linha contém a instrução de impressão. Nesse caso, estamos chamando a variante da instrução que aceita uma string constante. O montador se encarrega de decidir qual variante da instrução usar para nós. A terceira linha contém a instrução 'fim', que faz com que o interpretador termine.

Usando registros

Podemos modificar hello.pir para primeiro armazenar a string Hello world! \ N em um registro e então usar esse registro com a instrução de impressão.

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

Aqui declaramos exatamente qual registro usar. No entanto, ao substituir S1 por $ S1, podemos delegar ao Parrot a escolha de qual registro usar. Também é possível usar uma notação = em vez de escrever a instrução de conjunto.

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

Para tornar o PIR ainda mais legível, podem ser usados ​​registradores nomeados. Estes são posteriormente mapeados para registros reais numerados.

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

A diretiva '.local' indica que o registro nomeado só é necessário dentro da unidade de compilação atual (ou seja, entre .sub e .end). Seguir '.local' é um tipo. Isso pode ser int (para registros I), float (para registros N), string (para registros S), pmc (para registros P) ou o nome de um tipo PMC.

Somando quadrados

Este exemplo apresenta mais algumas instruções e sintaxe PIR. As linhas que começam com # são comentários.

.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

O PIR fornece um pouco de açúcar sintático que o faz parecer mais de alto nível do que o assembly. Por exemplo:

temp = i * i

É apenas outra forma de escrever, mais assembly:

mul temp, i, i

E:

if i <= maxnum goto loop

É o mesmo que:

le i, maxnum, loop

E:

total += temp

É o mesmo que:

add total, temp

Via de regra, sempre que uma instrução Parrot modifica o conteúdo de um registro, este será o primeiro registro ao escrever a instrução na forma de montagem.

Como é usual em linguagens assembly, loops e seleções são implementados em termos de declarações e rótulos de desvio condicional, conforme mostrado acima. A programação de montagem é um lugar onde usar goto não é uma forma ruim!

Números de Fibonacci

A série de Fibonacci é definida assim: pegue dois números, 1 e 1. Em seguida, some repetidamente os dois últimos números da série para fazer o próximo: 1, 1, 2, 3, 5, 8, 13 e assim por diante . O número de Fibonacci fib (n) é o enésimo número da série. Aqui está um programa montador Parrot simples que encontra os primeiros 20 números 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

Este é o código equivalente em 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:Como um bom ponto de interesse, uma das maneiras mais curtas e certamente as mais bonitas de imprimir uma série de Fibonacci em Perl é perl -le '$ b = 1; print $ a + = $ b enquanto print $ b + = $ a '.

Computando recursivamente o fatorial

Neste exemplo, definimos uma função fatorial e a chamamos recursivamente para calcular o fatorial.

.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

Vejamos primeiro o subfacto _fact. Um ponto que foi esquecido anteriormente é porque os nomes das sub-rotinas começam todos com um sublinhado! Isso é feito simplesmente como uma forma de mostrar que o rótulo é global, em vez de ter como escopo uma sub-rotina específica. Isso é significativo porque o rótulo fica então visível para outras sub-rotinas.

A primeira linha, .param int n, especifica que esta sub-rotina leva um parâmetro inteiro e que gostaríamos de nos referir ao registro que foi passado pelo nome n para o resto da sub.

Muito do que se segue foi visto em exemplos anteriores, além da leitura de linha:

result = _fact($I0)

Esta única linha de PIR realmente representa algumas linhas de PASM. Primeiro, o valor no registro $ I0 é movido para o registro apropriado para ser recebido como um parâmetro inteiro pela função _fact. Outros registros relacionados à chamada são configurados, seguidos por _fact sendo invocado. Então, uma vez que _fact retorna, o valor retornado por _fact é colocado no registro dado o nome resultado.

Logo antes do final do sub _fact, uma diretiva .return é usada para garantir o valor mantido no registro; o resultado nomeado é colocado no registro correto para ser visto como um valor de retorno pelo código que chama o sub.

A chamada para _fact em main funciona da mesma maneira que a chamada recursiva para _fact dentro do próprio sub _fact. O único pedaço restante da nova sintaxe é: main, escrito após .sub _main. Por padrão, o PIR assume que a execução começa com a primeira sub-rotina no arquivo. Este comportamento pode ser alterado marcando o sub para começar com: principal.

Compilando para PBC

Para compilar PIR para bytecode, use o sinalizador -o e especifique um arquivo de saída com a extensão .pbc.

parrot -o factorial.pbc factorial.pir

PIR vs. PASM

PIR pode ser transformado em PASM executando:

parrot -o hello.pasm hello.pir

O PASM para o exemplo final se parece com isto:

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

O PASM não controla a alocação de registros nem fornece suporte para registros nomeados. Ele também não tem as diretivas .sub e .end, substituindo-as por um rótulo no início das instruções.