Parrot - Ejemplos de programación

La programación de Parrot es similar a la programación en lenguaje ensamblador y tiene la oportunidad de trabajar en un nivel inferior. Aquí está la lista de ejemplos de programación para que conozca los distintos aspectos de la programación Parrot.

¡Hola mundo clásico!

Cree un archivo llamado hello.pir que contenga el siguiente código:

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

Luego ejecútelo escribiendo:

parrot hello.pir

Como se esperaba, esto mostrará el texto '¡Hola mundo!' en la consola, seguida de una nueva línea (debido al \ n).

En este ejemplo anterior, '.sub _main' indica que las instrucciones que siguen forman una subrutina llamada '_main', hasta que se encuentra un '.end'. La segunda línea contiene la instrucción de impresión. En este caso, llamamos a la variante de la instrucción que acepta una cadena constante. El ensamblador se encarga de decidir qué variante de la instrucción utilizar para nosotros. La tercera línea contiene la instrucción 'end', que hace que el intérprete termine.

Usando registros

Podemos modificar hello.pir para almacenar primero la cadena ¡Hola mundo! \ N en un registro y luego usar ese registro con la instrucción de impresión.

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

Aquí hemos indicado exactamente qué registro utilizar. Sin embargo, reemplazando S1 con $ S1 podemos delegar la elección de qué registro usar a Parrot. También es posible utilizar una notación = en lugar de escribir la instrucción establecida.

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

Para que PIR sea aún más legible, se pueden utilizar registros con nombre. Estos se asignan más tarde a registros numerados reales.

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

La directiva '.local' indica que el registro nombrado solo se necesita dentro de la unidad de compilación actual (es decir, entre .sub y .end). Después de '.local' es un tipo. Puede ser int (para registros I), float (para registros N), string (para registros S), pmc (para registros P) o el nombre de un tipo de PMC.

Sumar cuadrados

Este ejemplo presenta más instrucciones y sintaxis PIR. Las líneas que comienzan con # son comentarios.

.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 proporciona un poco de azúcar sintáctico que lo hace parecer de más alto nivel que el ensamblaje. Por ejemplo:

temp = i * i

Es solo otra forma de escribir más ensamblado:

mul temp, i, i

Y:

if i <= maxnum goto loop

Es lo mismo que:

le i, maxnum, loop

Y:

total += temp

Es lo mismo que:

add total, temp

Como regla general, siempre que una instrucción Parrot modifique el contenido de un registro, ese será el primer registro al escribir la instrucción en forma ensamblada.

Como es habitual en los lenguajes ensambladores, los bucles y las selecciones se implementan en términos de declaraciones y etiquetas de ramas condicionales, como se muestra arriba. ¡La programación de ensamblaje es un lugar donde usar goto no es una mala forma!

Números de Fibonacci

La serie de Fibonacci se define así: tome dos números, 1 y 1. Luego sume repetidamente los dos últimos números de la serie para hacer el siguiente: 1, 1, 2, 3, 5, 8, 13, y así sucesivamente. . El número de Fibonacci fib (n) es el número n de la serie. Aquí hay un programa ensamblador de Parrot simple que encuentra los primeros 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 es el código equivalente 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:Como buen punto de interés, una de las formas más cortas y ciertamente más hermosas de imprimir una serie de Fibonacci en Perl es perl -le '$ b = 1; imprimir $ a + = $ b mientras imprime $ b + = $ a '.

Calculando factorial recursivamente

En este ejemplo definimos una función factorial y la llamamos recursivamente para calcular factorial.

.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

Veamos primero el sub _fact. Un punto que se pasó por alto anteriormente es por qué los nombres de las subrutinas, ¡todos comienzan con un guión bajo! Esto se hace simplemente como una forma de mostrar que la etiqueta es global en lugar de tener el alcance de una subrutina en particular. Esto es importante ya que la etiqueta es visible para otras subrutinas.

La primera línea, .param int n, especifica que esta subrutina toma un parámetro entero y que nos gustaría hacer referencia al registro que se le pasó con el nombre n para el resto del sub.

Mucho de lo que sigue se ha visto en ejemplos anteriores, además de la lectura de líneas:

result = _fact($I0)

Esta única línea de PIR en realidad representa bastantes líneas de PASM. Primero, el valor en el registro $ I0 se mueve al registro apropiado para que la función _fact lo reciba como un parámetro entero. Luego se configuran otros registros relacionados con la llamada, seguidos de la invocación de _fact. Luego, una vez que _fact regresa, el valor devuelto por _fact se coloca en el registro dado el nombre resultado.

Justo antes del .end del sub _fact, se usa una directiva .return para asegurar el valor que se mantiene en el registro; El resultado con nombre se coloca en el registro correcto para que el código que llama al sub lo vea como un valor de retorno.

La llamada a _fact en main funciona de la misma manera que la llamada recursiva a _fact dentro del sub _fact mismo. El único bit restante de nueva sintaxis es: main, escrito después de .sub _main. Por defecto, PIR asume que la ejecución comienza con el primer sub del archivo. Este comportamiento se puede cambiar marcando el sub para comenzar con: main.

Compilando a PBC

Para compilar PIR en bytecode, use la marca -o y especifique un archivo de salida con la extensión .pbc.

parrot -o factorial.pbc factorial.pir

PIR frente a PASM

PIR se puede convertir en PASM ejecutando:

parrot -o hello.pasm hello.pir

El PASM para el ejemplo final se ve así:

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

PASM no maneja la asignación de registros ni brinda soporte para registros con nombre. Tampoco tiene las directivas .sub y .end, sino que las reemplaza con una etiqueta al comienzo de las instrucciones.