Попугай - Краткое руководство

Когда мы загружаем нашу программу в обычный Perl, она сначала компилируется во внутреннее представление или байт-код; этот байт-код затем передается в почти отдельную подсистему внутри Perl для интерпретации. Итак, есть две отдельные фазы работы Perl:

  • Компиляция в байт-код и

  • Интерпретация байт-кода.

Это не уникально для Perl. Другие языки, следующие этому дизайну, включают Python, Ruby, Tcl и даже Java.

Мы также знаем, что существует виртуальная машина Java (JVM), которая представляет собой платформо-независимую среду выполнения, которая преобразует байт-код Java в машинный язык и выполняет его. Если вы поймете эту концепцию, вы поймете Parrot.

Parrot- это виртуальная машина, предназначенная для эффективной компиляции и выполнения байт-кода для интерпретируемых языков. Parrot является целью окончательного компилятора Perl 6 и используется в качестве бэкэнда для Pugs, а также для множества других языков, таких как Tcl, Ruby, Python и т. Д.

Parrot был написан с использованием самого популярного языка "C".

Прежде чем мы начнем, давайте загрузим последнюю копию Parrot и установим ее на нашу машину.

Ссылка для скачивания Parrot доступна в Parrot CVS Snapshot . Загрузите последнюю версию Parrot и для ее установки выполните следующие действия:

  • Разархивируйте и распакуйте загруженный файл.

  • Убедитесь, что на вашем компьютере уже установлен Perl 5.

  • Теперь сделайте следующее:

% 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.
...
  • Затем вам будет задан ряд вопросов о вашей локальной конфигурации; вы почти всегда можете нажать Enter / Enter для каждого.

  • Наконец, вам будет предложено ввести - make test_prog, и Parrot успешно построит тестовый интерпретатор.

  • Теперь вам нужно запустить несколько тестов; так что введите 'make test', и вы должны увидеть следующее:

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

К тому времени, как вы это прочитаете, тестов может быть больше, и некоторые из пропущенных тестов можно не пропустить, но убедитесь, что ни один из них не завершится неудачно!

После того, как у вас установлен исполняемый файл parrot, вы можете проверить различные типы примеров, приведенные в разделе Parrot «Примеры» . Также вы можете проверить каталог примеров в репозитории parrot.

В настоящее время Parrot может принимать инструкции для выполнения в четырех формах. PIR (Промежуточное представление Parrot) разработан для написания людьми и генерации компиляторами. Он скрывает некоторые низкоуровневые детали, такие как способ передачи параметров функциям.

PASM (Parrot Assembly) находится на уровень ниже PIR - он по-прежнему доступен для чтения / записи и может быть сгенерирован компилятором, но автор должен позаботиться о деталях, таких как соглашения о вызовах и распределение регистров. PAST (Parrot Abstract Syntax Tree) позволяет Parrot принимать входные данные в виде абстрактного синтаксического дерева - полезно для тех, кто пишет компиляторы.

Все вышеперечисленные формы ввода автоматически преобразуются внутри Parrot в PBC (байт-код Parrot). Это очень похоже на машинный код, но понимается интерпретатором Parrot.

Он не предназначен для чтения или записи человеком, но в отличие от других форм выполнение может начаться немедленно, без необходимости фазы сборки. Байт-код Parrot не зависит от платформы.

Набор инструкций

Набор инструкций Parrot включает арифметические и логические операторы, сравнение и переход / переход (для реализации циклов, конструкций if ... then и т. Д.), Поиск и сохранение глобальных и лексических переменных, работу с классами и объектами, вызов подпрограмм и методов вместе с с их параметрами, вводом-выводом, потоками и т. д.

Как и виртуальная машина Java, Parrot избавляет вас от беспокойства по поводу выделения памяти.

  • Parrot обеспечивает сборку мусора.

  • Программы Parrot не нуждаются в явном освобождении памяти.

  • Выделенная память будет освобождена, когда она больше не будет использоваться, т.е. на нее больше не будет ссылаться.

  • Сборщик мусора Parrot запускается периодически, чтобы избавиться от нежелательной памяти.

ЦП Parrot имеет четыре основных типа данных:

  • IV

    Целочисленный тип; гарантированно будет достаточно широким для размещения указателя.

  • NV

    Архитектурно-независимый тип с плавающей запятой.

  • STRING

    Абстрактный строковый тип, не зависящий от кодировки.

  • PMC

    Скаляр.

Первые три типа не требуют пояснений; последний тип - Parrot Magic Cookies - немного сложнее для понимания.

Что такое ЧВК?

PMC расшифровывается как Parrot Magic Cookie. PMC представляют любую сложную структуру или тип данных, включая агрегированные типы данных (массивы, хэш-таблицы и т. Д.). PMC может реализовать свое собственное поведение для выполняемых над ним арифметических, логических и строковых операций, что позволяет ввести поведение, зависящее от языка. PMC могут быть встроены в исполняемый файл Parrot или загружаться динамически по мере необходимости.

Текущая виртуальная машина Perl 5 - это стековая машина. Он передает значения между операциями, сохраняя их в стеке. Операции загружают значения в стек, делают все, что им нужно, и помещают результат обратно в стек. С этим легко работать, но это медленно.

Чтобы сложить два числа, вам нужно выполнить три выталкивания стека и два выталкивания стека. Хуже того, стек должен расти во время выполнения, а это означает выделение памяти именно тогда, когда вы не хотите ее выделять.

Таким образом, Parrot собирается нарушить устоявшуюся традицию для виртуальных машин и использовать регистровую архитектуру, более похожую на архитектуру реального аппаратного процессора. В этом есть еще одно преимущество. Мы можем использовать всю существующую литературу о том, как писать компиляторы и оптимизаторы для регистровых процессоров для нашего программного процессора!

У Parrot есть специальные регистры для каждого типа: 32 регистра IV, 32 регистра NV, 32 строковых регистра и 32 регистра PMC. В ассемблере Parrot они называются I1 ... I32, N1 ... N32, S1 ... S32, P1 ... P32 соответственно.

Теперь посмотрим на какой-нибудь ассемблер. Мы можем установить эти регистры с помощью оператора установки:

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

Все операции Parrot имеют одинаковый формат: имя оператора, регистр назначения и затем операнды.

Вы можете выполнять множество операций. Например, мы можем распечатать содержимое регистра или константы:

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

Приведенные выше инструкции приведут к следующему : содержимое регистра I1: 10

Мы можем выполнять математические операции с регистрами:

# 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

Мы даже можем выполнить простую манипуляцию со строкой:

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

Код становится немного скучным без контроля потока; Во-первых, Parrot знает о ветвлениях и метках. Операция ветки эквивалентна goto в 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

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

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

Вот как это будет выглядеть в Perl для сравнения:

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

Оператор попугая

У нас есть полный спектр числовых компараторов: eq, ne, lt, gt, le и ge. Обратите внимание, что вы не можете использовать эти операторы для аргументов разных типов; вам может даже понадобиться добавить суффикс _i или _n к op, чтобы указать, какой тип аргумента вы используете, хотя ассемблер должен уловить это за вас к тому времени, когда вы это прочитаете.

Программирование 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, вместо этого они заменяются меткой в ​​начале инструкций.