Parrot - Примеры программирования

Программирование Parrot похоже на программирование на языке ассемблера, и у вас есть возможность работать на более низком уровне. Вот список примеров программирования, которые познакомят вас с различными аспектами программирования Parrot.

  • Классический Привет, мир!

  • Использование регистров

  • Суммирование квадратов

  • Числа Фибоначчи

  • Вычисление факториала

  • Компиляция в PBC

  • PIR против PASM

Классический Привет, мир!

Создайте файл hello.pir, содержащий следующий код:

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

Затем запустите его, набрав:

parrot hello.pir

Как и ожидалось, отобразится текст «Hello world!» на консоли, после чего следует новая строка (из-за \ n).

В приведенном выше примере .sub _main означает, что последующие инструкции составляют подпрограмму с именем «_main» до тех пор, пока не встретится «.end». Вторая строка содержит инструкцию печати. В этом случае мы вызываем вариант инструкции, который принимает постоянную строку. Ассемблер позаботится о том, чтобы решить, какой вариант инструкции использовать для нас. Третья строка содержит инструкцию конца, которая вызывает завершение работы интерпретатора.

Использование регистров

Мы можем изменить hello.pir, чтобы сначала сохранить строку Hello world! \ N в регистре, а затем использовать этот регистр с инструкцией печати.

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

Здесь мы точно указали, какой регистр использовать. Однако, заменив S1 на $ S1, мы можем делегировать выбор того, какой регистр использовать, Parrot. Также можно использовать обозначение = вместо записи инструкции set.

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

Чтобы сделать PIR еще более читабельным, можно использовать именованные регистры. Позже они отображаются в регистры с действительными номерами.

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

Директива .local указывает, что именованный регистр необходим только внутри текущего модуля компиляции (то есть между .sub и .end). После .local - это тип. Это может быть int (для регистров I), float (для регистров N), строка (для регистров S), pmc (для регистров P) или имя типа PMC.

Суммирование квадратов

В этом примере представлены еще несколько инструкций и синтаксис PIR. Строки, начинающиеся с символа #, являются комментариями.

.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 предоставляет немного синтаксического сахара, что делает его более высокоуровневым, чем ассемблер. Например:

temp = i * i

Это просто еще один способ написать более ассемблерный:

mul temp, i, i

И:

if i <= maxnum goto loop

Такой же как:

le i, maxnum, loop

И:

total += temp

Такой же как:

add total, temp

Как правило, всякий раз, когда инструкция Parrot изменяет содержимое регистра, это будет первый регистр при записи инструкции в ассемблерной форме.

Как обычно в языках ассемблера, циклы и выборки реализованы в терминах условных операторов ветвления и меток, как показано выше. Программирование на ассемблере - это то место, где использование goto не является плохим тоном!

Числа Фибоначчи

Ряд Фибоначчи определяется так: возьмите два числа, 1 и 1. Затем несколько раз сложите последние два числа в ряду, чтобы получить следующее: 1, 1, 2, 3, 5, 8, 13 и т. Д. . Число Фибоначчи fib (n) - это n-е число в ряду. Вот простая программа ассемблера Parrot, которая находит первые 20 чисел Фибоначчи:

# 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

Это эквивалентный код на 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:Интересно отметить, что одним из самых коротких и, безусловно, самых красивых способов распечатать ряд Фибоначчи в Perl является perl -le '$ b = 1; напечатайте $ a + = $ b, а напечатайте $ b + = $ a '.

Рекурсивное вычисление факториала

В этом примере мы определяем факториальную функцию и рекурсивно вызываем ее для вычисления факториала.

.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

Давайте сначала посмотрим на подпункт _fact. Момент, который ранее был упущен, - это то, почему все имена подпрограмм начинаются с подчеркивания! Это делается просто для того, чтобы показать, что метка глобальна, а не привязана к конкретной подпрограмме. Это важно, поскольку метка становится видимой для других подпрограмм.

Первая строка, .param int n, указывает, что эта подпрограмма принимает один целочисленный параметр и что мы хотели бы ссылаться на регистр, в который она была передана, по имени n для остальной части подпрограммы.

Многое из того, что следует ниже, было замечено в предыдущих примерах, помимо чтения строки:

result = _fact($I0)

Эта единственная линия PIR на самом деле представляет собой довольно много линий PASM. Сначала значение в регистре $ I0 перемещается в соответствующий регистр, чтобы функция _fact приняла его как целочисленный параметр. Затем устанавливаются другие регистры, связанные с вызовами, после чего вызывается _fact. Затем, как только _fact возвращается, значение, возвращаемое _fact, помещается в регистр с учетом имени result.

Прямо перед концом подпрограммы _fact используется директива .return, чтобы гарантировать, что значение хранится в регистре; Именованный результат помещается в правильный регистр, чтобы код, вызывающий подпрограмму, видел его как возвращаемое значение.

Вызов _fact в main работает точно так же, как рекурсивный вызов _fact внутри самого под _fact. Единственная оставшаяся часть нового синтаксиса - это: main, записанный после .sub _main. По умолчанию PIR предполагает, что выполнение начинается с первой подпрограммы в файле. Это поведение можно изменить, отметив подпрограмму для начала с: main.

Компиляция в PBC

Чтобы скомпилировать PIR в байт-код, используйте флаг -o и укажите выходной файл с расширением .pbc.

parrot -o factorial.pbc factorial.pir

PIR против PASM

PIR можно превратить в PASM, запустив:

parrot -o hello.pasm hello.pir

PASM для последнего примера выглядит так:

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

PASM не обрабатывает выделение регистров и не поддерживает именованные регистры. Он также не имеет директив .sub и .end, вместо этого они заменяются меткой в ​​начале инструкций.