Учебники

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

Что такое попугай

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

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

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

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

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

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

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

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

Попугай был написан с использованием самого популярного языка «C».

Установка попугая

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

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

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

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

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

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

Убедитесь, что на вашем компьютере уже установлен 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», и вы должны увидеть показания, подобные следующим:

Затем вам будет задан ряд вопросов о вашей локальной конфигурации; Вы почти всегда можете нажать 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 может принимать инструкции для выполнения в четырех формах. PIR (промежуточное представление Parrot) предназначено для написания людьми и генерируется компиляторами. Он скрывает некоторые детали низкого уровня, такие как способ передачи параметров в функции.

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

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

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

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

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

Сбор мусора в попугай

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

  • Попугай обеспечивает сбор мусора.

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

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

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

Попугай обеспечивает сбор мусора.

Программы-попугаи не должны явно освобождать память.

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

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

Типы попугаев

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

  • IV

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

  • Невада

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

  • STRING

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

  • PMC

    Скаляр

IV

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

Невада

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

STRING

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

PMC

Скаляр

Первые три типа в значительной степени говорят сами за себя; последний тип — Parrot Magic Cookies, немного сложнее для понимания.

Что такое PMC?

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:

	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

Попугай филиалов

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

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

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

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

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

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

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

  parrot hello.pir

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

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

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

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

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

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

  .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 регистров), string (для 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++;
        }

ПРИМЕЧАНИЕ. В качестве интересного факта один из самых коротких и, безусловно, самых красивых способов распечатать ряд Фибоначчи в Perl — это perl -le ‘$ b = 1; выведите $ a + = $ b, а print $ 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, помещается в регистр с заданным именем результата.

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

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

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

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

 parrot -o factorial.pbc factorial.pir

PIR vs. PASM

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

parrot -o hello.pasm hello.pir

PASM для последнего примера выглядит следующим образом:

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

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