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