Это команда perlinterp, которую можно запустить в бесплатном хостинг-провайдере OnWorks, используя одну из наших многочисленных бесплатных онлайн-рабочих станций, таких как Ubuntu Online, Fedora Online, онлайн-эмулятор Windows или онлайн-эмулятор MAC OS.
ПРОГРАММА:
ИМЯ
perlinterp - Обзор интерпретатора Perl
ОПИСАНИЕ
В этом документе представлен обзор того, как интерпретатор Perl работает на уровне C
code вместе с указателями на соответствующие файлы исходного кода C.
ЭЛЕМЕНТЫ OF УСТНЫЙ ПЕРЕВОДЧИК
Работа интерпретатора состоит из двух основных этапов: компиляция кода во внутреннюю
представление или байт-код, а затем его выполнение. "Скомпилированный код" в perlguts объясняет
как именно происходит этап компиляции.
Вот краткое описание работы Perl:
Стартап
Действие начинается в perlmain.c. (или же miniperlmain.c для миниперл) Это очень на высоком уровне
код, достаточно, чтобы поместиться на одном экране, и он напоминает код, найденный в perlembed; самый
реального действия происходит в perl.c
perlmain.c генерируется "ExtUtils :: Miniperl" из miniperlmain.c на время, так что вы
следует сделать так, чтобы perl продолжал это делать.
Первое perlmain.c выделяет некоторую память и создает интерпретатор Perl на основе этих
линии:
1 PERL_SYS_INIT3 (& argc, & argv, & env);
2
3 если (! PL_do_undump) {
4 my_perl = perl_alloc ();
5 если (! My_perl)
6 выход(1);
7 perl_construct(мой_перл);
8 PL_perl_destruct_level = 0;
9}
Строка 1 - это макрос, и его определение зависит от вашей операционной системы. Строка 3
ссылается на "PL_do_undump", глобальную переменную - все глобальные переменные в Perl начинаются с
«ПЛ_». Это говорит вам, была ли текущая запущенная программа создана с флагом "-u"
на Perl, а затем откачивать, что означает, что он будет ложным в любом нормальном контексте.
Строка 4 вызывает функцию в perl.c выделить память для интерпретатора Perl. Это довольно
простая функция, и в целом она выглядит так:
my_perl = (PerlInterpreter *) PerlMem_malloc (sizeof (PerlInterpreter));
Здесь вы видите пример системной абстракции Perl, который мы увидим позже:
«PerlMem_malloc» - это либо «malloc» вашей системы, либо собственный «malloc» Perl, как определено в
malloc.c если вы выбрали эту опцию во время настройки.
Затем в строке 7 мы создаем интерпретатор, используя perl_construct, также в perl.c; это
устанавливает все специальные переменные, необходимые Perl, стеки и так далее.
Теперь мы передаем Perl параметры командной строки и говорим:
exitstatus = perl_parse (my_perl, xs_init, argc, argv, (char **) NULL);
если (! статус выхода)
perl_run(мой_перл);
статус выхода = perl_destruct(мой_перл);
perl_free(мой_перл);
"perl_parse" на самом деле является оболочкой для "S_parse_body", как определено в perl.c, который
обрабатывает параметры командной строки, настраивает любые статически связанные модули XS, открывает
программа и вызывает yyparse для ее анализа.
анализ
Цель этого этапа - взять исходный код Perl и превратить его в дерево операций. Посмотрим
как один из них будет выглядеть позже. Строго говоря, здесь происходит три вещи.
"yyparse", парсер, живет в perly.c, хотя вам лучше прочитать оригинал
Вход YACC в перлы.у. (Да, Вирджиния, там is грамматика YACC для Perl!)
парсер должен взять ваш код и "понять" его, разбить его на предложения, решить
какие операнды связаны с какими операторами и т. д.
Синтаксическому анализатору благородно помогает лексер, который разбивает ваш ввод на токены и
решает, к какому типу относится каждый токен: имя переменной, оператор, простое слово,
подпрограмма, основная функция и т. д. Основная точка входа в лексер - yylex,
и это и связанные с ним процедуры можно найти в токе.с. Perl не сильно похож на другие
компьютерные языки; время от времени он сильно зависит от контекста, его сложно понять
что это за токен или где заканчивается токен. Таким образом, есть много
взаимодействие между токенизатором и синтаксическим анализатором, которое может стать довольно пугающим, если вы
не привык.
Поскольку парсер понимает программу Perl, он строит дерево операций для
интерпретатор для выполнения во время выполнения. Подпрограммы, которые строятся и связываются вместе
различные операции можно найти в оп.с, и будет рассмотрено позже.
Оптимизация
Теперь этап синтаксического анализа завершен, и готовое дерево представляет операции, которые
интерпретатор Perl должен выполнить нашу программу. Затем Perl выполняет пробный запуск
по дереву в поисках оптимизации: постоянные выражения, такие как "3 + 4", будут
вычисляется сейчас, и оптимизатор также увидит, можно ли заменить какие-либо несколько операций
с одним. Например, чтобы получить переменную $ foo, вместо получения глобуса
* foo и глядя на скалярный компонент, оптимизатор изменяет дерево операций, чтобы использовать
функция, которая напрямую ищет рассматриваемый скаляр. Главный оптимизатор - "писк" в
оп.с, и многие операторы имеют свои собственные функции оптимизации.
Бег
Теперь мы, наконец, готовы к работе: мы скомпилировали байт-код Perl, и все, что осталось сделать
это запустить. Фактическое выполнение выполняется функцией runops_standard в бег.c; Больше
в частности, это сделано этими тремя невинно выглядящими линиями:
в то время как ((PL_op = PL_op-> op_ppaddr (aTHX))) {
PERL_ASYNC_CHECK ();
}
Возможно, вам будет удобнее работать с версией Perl:
PERL_ASYNC_CHECK (), а $ Perl :: op = & {$ Perl :: op -> {function}};
Ну, может, и нет. В любом случае, каждая операция содержит указатель на функцию, который определяет
функция, которая будет фактически выполнять операцию. Эта функция вернет следующий
op в последовательности - это позволяет использовать такие вещи, как "если", которые динамически выбирают следующую операцию
во время выполнения. "PERL_ASYNC_CHECK" гарантирует, что такие вещи, как сигналы, прерывают
исполнение при необходимости.
Фактические вызываемые функции известны как код PP, и они распределены между четырьмя файлами:
pp_hot.c содержит «горячий» код, который наиболее часто используется и сильно оптимизирован, pp_sys.c
содержит все системные функции, pp_ctl.c содержит функции, которые
реализовать управляющие структуры («если», «пока» и т.п.) и стр.с содержит все
еще. Это, если хотите, код C для встроенных функций и операторов Perl.
Обратите внимание, что каждая функция "pp_" должна возвращать указатель на следующую операцию. Звонки в
Подпрограммы perl (и блоки eval) обрабатываются в одном и том же цикле runops и не потребляют
дополнительное место в стеке C. Например, «pp_entersub» и «pp_entertry» просто нажимают
Блочная структура «CxSUB» или «CxEVAL» в стеке контекста, которая содержит адрес
op после подвызова или eval. Затем они возвращают первую операцию этой подпрограммы или eval.
блок, и поэтому выполнение этого субблока или блока продолжается. Позже "pp_leavesub" или
Операция «pp_leavetry» выводит «CxSUB» или «CxEVAL», извлекает из нее операцию возврата и
возвращает его.
Исключение вручая
Обработка исключений Perl (например, "die" и т. Д.) Построена на низкоуровневом
"setjmp ()" / "longjmp ()" Функции библиотеки Си. Они в основном предоставляют способ захвата
текущие регистры ПК и SP и их последующее восстановление; т.е. "longjmp ()" продолжается в
точка в коде, где выполнялась предыдущая "setjmp ()", а все, что находится дальше на C
стек теряется. Вот почему код всегда должен сохранять значения, используя "SAVE_FOO", а не
в автоматических переменных.
Ядро Perl оборачивает "setjmp ()" и т. Д. В макросы "JMPENV_PUSH" и "JMPENV_JUMP". В
Основное правило исключений perl состоит в том, что "exit" и "die" (при отсутствии eval) выполняются
a JMPENV_JUMP(2), в то время как "die" внутри "eval" выполняет JMPENV_JUMP(3).
В точках входа в perl, таких как perl_parse (), perl_run () и call_sv (cv, G_EVAL)
каждый выполняет "JMPENV_PUSH", затем входит в цикл runops или что-то еще и обрабатывает возможные
исключение возвращается. Для возврата 2 выполняется окончательная очистка, например, выталкивание стеков и
вызов блоков «ПРОВЕРКА» или «КОНЕЦ». Среди прочего, так до сих пор происходит очистка прицела.
происходит во время «выхода».
Если «кубик» может найти блок «CxEVAL» в стеке контекста, тогда стек выталкивается в
этот уровень и операция возврата в этом блоке назначаются «PL_restartop»; затем
JMPENV_JUMP(3) выполняется. Обычно это передает управление обратно охраннику. В этом случае
из "perl_run" и "call_sv" ненулевое значение "PL_restartop" запускает повторный вход в runops
петля. Это нормальный способ обработки "die" или "croak" в "eval".
Иногда операции выполняются во внутреннем цикле runops, например, привязка, сортировка или перегрузка.
код. В этом случае что-то вроде
sub FETCH {eval {die}}
вызовет longjmp обратно к охраннику в "perl_run", выскочив оба цикла runops,
что явно неверно. Один из способов избежать этого - сделать код связи
"JMPENV_PUSH" перед выполнением "FETCH" во внутреннем цикле runops, но для эффективности
По причинам, Perl фактически просто устанавливает флаг, используя "CATCH_SET (TRUE)". "Pp_require",
Операторы "pp_entereval" и "pp_entertry" проверяют этот флаг, и если это правда, они вызывают "docatch",
который выполняет "JMPENV_PUSH" и запускает новый уровень runops для выполнения кода, а не
делая это в текущем цикле.
В качестве дальнейшей оптимизации при выходе из блока eval в "FETCH" выполнение
код, следующий за блоком, по-прежнему выполняется во внутреннем цикле. Когда исключение
поднят, "docatch" сравнивает уровень "JMPENV" для "CxEVAL" с "PL_top_env" и если
они отличаются, просто повторно генерирует исключение. Таким образом появляются любые внутренние петли.
Вот пример.
1: eval {tie @a, 'A'};
2: sub A :: TIEARRAY {
3: eval {умереть};
4: умереть;
5:}
Для запуска этого кода вызывается "perl_run", который выполняет "JMPENV_PUSH", а затем вводит runops
петля. Этот цикл выполняет eval и связующие операции в строке 1, при этом eval нажимает "CxEVAL"
в стек контекста.
«Pp_tie» выполняет «CATCH_SET (TRUE)», затем запускает второй цикл runops для выполнения
корпус "TIEARRAY". Когда он выполняет операцию ввода в строке 3, "CATCH_GET" истинно, поэтому
"pp_entertry" вызывает "docatch", который выполняет "JMPENV_PUSH" и запускает третий цикл runops,
который затем выполняет команду die op. На данный момент стек вызовов C выглядит так:
Perl_pp_die
Perl_runops # третий цикл
S_docatch_body
S_docatch
Perl_pp_entertry
Perl_runops # второй цикл
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # первый цикл
S_run_body
perl_run
main
а стеки контекста и данных, как показано «-Dstv», выглядят так:
СТЕК 0: ГЛАВНЫЙ
CX 0: БЛОК =>
CX 1: EVAL => AV () PV ("A" \ 0)
retop = уйти
СТЕК 1: МАГИЯ
CX 0: SUB =>
retop = (ноль)
CX 1: EVAL => *
retop = nextstate
Матрица извлекает первое «CxEVAL» из стека контекста, устанавливает из него «PL_restartop», выполняет
JMPENV_JUMP(3), и управление возвращается в верхний «докач». Затем начинается еще одна третья -
level runops level, который выполняет следующее состояние, нажимает и умирает в строке 4. На
указывает, что вызывается второй "pp_die", стек вызовов C выглядит точно так же, как указано выше,
даже если мы больше не во внутреннем eval; это из-за оптимизации
упомянутый ранее. Однако стек контекста теперь выглядит так, то есть с верхним CxEVAL
выскочил:
СТЕК 0: ГЛАВНЫЙ
CX 0: БЛОК =>
CX 1: EVAL => AV () PV ("A" \ 0)
retop = уйти
СТЕК 1: МАГИЯ
CX 0: SUB =>
retop = (ноль)
Кость в строке 4 выталкивает контекстный стек обратно в CxEVAL, оставляя его как:
СТЕК 0: ГЛАВНЫЙ
CX 0: БЛОК =>
Как обычно, «PL_restartop» извлекается из «CxEVAL», а JMPENV_JUMP(3) сделано, что
возвращает стек C обратно в документ:
S_docatch
Perl_pp_entertry
Perl_runops # второй цикл
S_call_body
Perl_call_sv
Perl_pp_tie
Perl_runops # первый цикл
S_run_body
perl_run
main
В этом случае, поскольку уровень «JMPENV», записанный в «CxEVAL», отличается от уровня
текущий, "docatch" просто делает JMPENV_JUMP(3) и стек C раскручивается до:
perl_run
main
Поскольку "PL_restartop" не равно NULL, "run_body" запускает новый цикл runops и выполнение
Продолжает.
ИНТЕРЬЕР ПЕРЕМЕННЫЕ ВИДЫ
К настоящему времени вы должны были взглянуть на perlguts, в котором рассказывается о внутреннем
типы переменных: SV, HV, AV и другие. Если нет, сделайте это сейчас.
Эти переменные используются не только для представления переменных пространства Perl, но и любых
константы в коде, а также некоторые структуры, полностью внутренние по отношению к Perl. Символ
table, например, является обычным хешем Perl. Ваш код представлен SV, поскольку он
читать в парсер; любые программные файлы, которые вы вызываете, открываются через обычные файловые дескрипторы Perl,
и т. д.
Базовый модуль Devel :: Peek позволяет нам исследовать SV из программы Perl. Посмотрим, для
Например, как Perl обрабатывает константу "hello".
% perl -MDevel :: Peek -e 'Дамп ("привет")'
1 СВ = PV(0xa041450) в 0xa04ecbc
2 REFCNT = 1
3 ФЛАГА = (POK, ТОЛЬКО ЧТЕНИЕ, pPOK)
4 PV = 0xa0484e0 "привет" \ 0
5 КУР = 5
6 ЛЕН = 6
Чтение вывода "Devel :: Peek" требует некоторой практики, поэтому давайте рассмотрим его построчно.
Строка 1 сообщает нам, что мы смотрим на SV, который находится в памяти по адресу 0xa04ecbc. Сами КА
очень простые структуры, но они содержат указатель на более сложную структуру. В
в данном случае это PV, структура, которая содержит строковое значение в позиции 0xa041450. Линия
2 - счетчик ссылок; других ссылок на эти данные нет, так что это 1.
Строка 3 - это флаги для этого SV - можно использовать его как PV, это SV только для чтения (потому что
это константа), а данные - это внутренняя PV. Далее у нас есть содержимое
строка, начиная с адреса 0xa0484e0.
Строка 5 дает нам текущую длину строки - обратите внимание, что это включают
нулевой терминатор. Строка 6 - это не длина строки, а длина текущего
выделенный буфер; по мере роста строки Perl автоматически расширяет доступное хранилище
через процедуру под названием "SvGROW".
Вы можете легко получить любую из этих величин из C; просто добавьте "Sv" к имени
поле, показанное во фрагменте, и у вас есть макрос, который вернет значение:
«SvCUR (sv)» возвращает текущую длину строки, «SvREFCOUNT (sv)» возвращает
счетчик ссылок, «SvPV (sv, len)» возвращает саму строку с ее длиной и так далее.
Дополнительные макросы для управления этими свойствами можно найти в perlguts.
Давайте возьмем пример управления PV из "sv_catpvn" в св.с
1 недействительно
2 Perl_sv_catpvn (pTHX_ SV * sv, const char * ptr, STRLEN len)
3 {
4 СТРЛЕН Тлен;
5 знаков * барахло;
6 мусор = SvPV_force (sv, tlen);
7 SvGROW (sv, tlen + len + 1);
8 если (ptr == мусор)
9 ptr = SvPVX (sv);
10 Перемещение (ptr, SvPVX (sv) + tlen, len, char);
11 SvCUR (sv) + = len;
12 * СВЕНД (св) = '\ 0';
13 (недействительно) SvPOK_only_UTF8 (sv); / * проверяем указатель * /
14 SvTAINT (св);
15}
Это функция, которая добавляет строку «ptr» длиной «len» в конец PV.
хранится в "св". Первое, что мы делаем в строке 6, это проверяем, что SV и действительный PV,
вызывая макрос «SvPV_force» для принудительного создания PV. В качестве побочного эффекта "tlen" устанавливается на
текущее значение PV, а сам PV возвращается в «утиль».
В строке 7 мы убеждаемся, что на SV будет достаточно места для размещения старой струны,
новая строка и нулевой ограничитель. Если LEN недостаточно велик, SvGROW будет
перераспределите пространство для нас.
Теперь, если "мусор" совпадает со строкой, которую мы пытаемся добавить, мы можем получить строку
прямо с КА; «SvPVX» - это адрес PV в SV.
Строка 10 выполняет фактическую цепочку: макрос «Move» перемещает фрагмент памяти вокруг: мы
переместите строку "ptr" в конец PV - это начало PV плюс его текущий
длина. Мы перемещаем "len" байтов типа "char". После этого нам нужно сообщить Perl
мы расширили строку, изменив «CUR», чтобы отразить новую длину. «СвЕНД» - это макрос
что дает нам конец строки, так что это должно быть "\ 0".
Строка 13 управляет флагами; поскольку мы изменили PV, любые значения IV или NV не будут
дольше действительны: если у нас есть "$ a = 10; $ a. =" 6 ";" мы не хотим использовать старый IV 10.
"SvPOK_only_utf8" - это специальная версия макроса "SvPOK_only", поддерживающая UTF-8,
выключить флажки IOK и NOK и включить POK. Последний "SvTAINT" - это макрос, отмывающий
испорченные данные, если включен режим заражения.
AV и HV более сложны, но SV, безусловно, являются наиболее распространенным типом переменных.
брошенный. Увидев кое-что о том, как мы ими манипулируем, давайте продолжим и посмотрим на
как строится операционное дерево.
OP дЕРЕВЬЯ
Во-первых, что такое дерево операций? Дерево операций - это проанализированное представление вашего
программа, как мы видели в нашем разделе, посвященном синтаксическому анализу, и именно последовательность операций
Perl выполняет вашу программу, как мы видели в «Запуск».
Операция - это фундаментальная операция, которую может выполнять Perl: все встроенные функции и
операторы - это операторы, и есть ряд операций, которые имеют дело с концепциями, которые интерпретатор
внутренние потребности - вход и выход из блока, завершение оператора, выборка переменной,
и т. д.
Дерево операций связано двумя способами: вы можете представить, что есть два «маршрута» через
это два порядка, в которых вы можете пройти по дереву. Во-первых, порядок синтаксического анализа отражает то, как
парсер понял код, а во-вторых, порядок выполнения сообщает Perl, какой порядок выполнять
операции в.
Самый простой способ проверить дерево операций - остановить Perl после того, как он завершит синтаксический анализ, и
заставить его вывалить дерево. Именно это и поддерживает компилятор B :: Terse,
B :: Concise и B :: Debug делают.
Давайте посмотрим, как Perl видит "$ a = $ b + $ c":
% perl -MO = Terse -e '$ a = $ b + $ c'
1 LISTOP (0x8179888) оставить
2 OP (0x81798b0) введите
3 COP (0x8179850) следующее состояние
4 БИНОП (0x8179828) назначить
5 BINOP (0x8179800) добавить [1]
6 UNOP (0x81796e0) ноль [15]
7 SVOP (0x80fafe0) gvsv GV (0x80fa4cc) * b
8 UNOP (0x81797e0) ноль [15]
9 SVOP (0x8179700) gvsv GV (0x80efeb0) * c
10 UNOP (0x816b4f0) ноль [15]
11 SVOP (0x816dcf0) gvsv GV (0x80fa460) * а
Начнем с середины, со строки 4. Это БИНОП, бинарный оператор, который находится в
расположение 0x8179828. Конкретный рассматриваемый оператор - "sassign" - скалярное присвоение -
и вы можете найти код, который реализует его в функции "pp_sassign" в pp_hot.c. Так как
бинарный оператор, у него есть два дочерних элемента: оператор сложения, предоставляющий результат «$ b + $ c»,
находится вверху в строке 5, а левая часть - в строке 10.
Строка 10 - это нулевая операция: она ничего не делает. Что это там делает? Если ты видишь
null op, это признак того, что что-то было оптимизировано после синтаксического анализа. Как мы
упоминается в разделе «Оптимизация», на этапе оптимизации иногда две операции преобразуются в
один, например, при выборке скалярной переменной. Когда это произойдет, вместо того, чтобы переписывать
дерево операций и очистка болтающихся указателей, проще просто заменить
избыточная операция с нулевой операцией. Первоначально дерево выглядело бы так:
10 СВОП (0x816b4f0) rv2sv [15]
11 SVOP (0x816dcf0) gv GV (0x80fa460) * а
То есть выберите запись "a" из основной таблицы символов, а затем посмотрите на скаляр
его компонент: "gvsv" ("pp_gvsv" в pp_hot.c) делает и то, и другое.
Правая часть, начинающаяся со строки 5, похожа на то, что мы только что видели: у нас есть
"add" op ("pp_add" также в pp_hot.c) сложите два "gvsv".
О чем это?
1 LISTOP (0x8179888) оставить
2 OP (0x81798b0) введите
3 COP (0x8179850) следующее состояние
"вход" и "выход" являются операциями по определению объема работ, и их задача - выполнять любую уборку каждый
когда вы входите и выходите из блока: лексические переменные приводятся в порядок, переменные, на которые нет ссылок
уничтожаются и т. д. В каждой программе будут эти первые три строки: "leave" - это
list, а его дочерние элементы - это все операторы в блоке. Заявления разделяются
"nextstate", поэтому блок представляет собой набор операций "nextstate", которые должны быть выполнены
для каждого оператора, являющегося дочерним элементом "nextstate". "Enter" - это единственная операция, которая
действует как маркер.
Вот как Perl разбирал программу сверху вниз:
Программа
|
заявление
|
=
/\
/\
$ а +
/\
$ b $ c
Однако невозможно выполнять операции в таком порядке: вы должны найти
значения $ b и $ c, например, перед их сложением. Итак, другой поток, который
проходит через дерево операций - это порядок выполнения: каждая операция имеет поле op_next, которое
указывает на следующую операцию, которая должна быть запущена, поэтому следование этим указателям говорит нам, как Perl выполняет
код. Мы можем перемещаться по дереву в этом порядке, используя параметр «exec» для «B :: Terse»:
% perl -MO = Краткий, exec -e '$ a = $ b + $ c'
1 OP (0x8179928) введите
2 COP (0x81798c8) следующее состояние
3 SVOP (0x81796c8) gvsv GV (0x80fa4d4) * b
4 SVOP (0x8179798) gvsv GV (0x80efeb0) * c
5 BINOP (0x8179878) добавить [1]
6 SVOP (0x816dd38) gvsv GV (0x80fa468) * а
7 назначение BINOP (0x81798a0)
8 LISTOP (0x8179900) оставить
Это, вероятно, имеет больше смысла для человека: введите блок, начните утверждение. Получить
значения $ b и $ c и сложите их вместе. Найдите $ a и назначьте одно другому. потом
Покидать.
То, как Perl создает эти операционные деревья в процессе синтаксического анализа, можно раскрыть с помощью
изучающий перлы.у, грамматика YACC. Возьмем кусок, который нам нужен для построения дерева.
для "$ a = $ b + $ c"
1 термин: термин ASSIGNOP термин
2 {$$ = newASSIGNOP (OPf_STACKED, $ 1, $ 2, $ 3); }
3 | срок ADDOP срок
4 {$$ = newBINOP ($ 2, 0, скаляр ($ 1), скаляр ($ 3)); }
Если вы не привыкли читать грамматики BNF, вот как это работает: вас накормили определенными
вещи от токенизатора, которые обычно заканчиваются в верхнем регистре. Здесь "ADDOP" предоставляется
когда токенизатор видит "+" в вашем коде. "ASSIGNOP" предоставляется, когда "=" используется для
присвоение. Это «терминальные символы», потому что ничего проще их не найти.
Грамматика, первая и третья строки приведенного выше фрагмента, расскажет, как создать больше
сложные формы. Эти сложные формы, «нетерминальные символы» обычно помещаются в нижние
кейс. «термин» здесь - нетерминальный символ, представляющий одно выражение.
Грамматика дает вам следующее правило: вы можете сделать то, что слева от двоеточия
если вы видите все вещи справа по порядку. Это называется «редукцией», и
цель синтаксического анализа - полностью уменьшить ввод. Есть несколько разных способов
выполнить сокращение, разделенное вертикальными полосами: так, "термин", затем "=", а затем
«термин» образует «термин», а «термин», за которым следует «+», за которым следует «термин», также может образовывать
"срок".
Итак, если вы видите два термина со знаком «=» или «+» между ними, вы можете превратить их в один
выражение. Когда вы это сделаете, вы выполните код в блоке на следующей строке: если вы
см. «=», вы выполните код в строке 2. Если вы увидите «+», вы выполните код в строке 4. Это
этот код, который вносит вклад в дерево операций.
| срок ADDOP срок
{$$ = newBINOP ($ 2, 0, скаляр ($ 1), скаляр ($ 3)); }
Это создает новую двоичную операцию и передает ей ряд переменных. В
переменные относятся к токенам: $ 1 - это первый токен во входных данных, $ 2 - второй, и поэтому
on - подумайте об обратных ссылках на регулярные выражения. $$ - это операция, возвращенная из этого сокращения.
Итак, мы вызываем «newBINOP», чтобы создать новый бинарный оператор. Первый параметр "newBINOP",
функция в оп.с, это тип операции. Это оператор сложения, поэтому мы хотим, чтобы тип был
«ДОБАВИТЬ». Мы могли бы указать это напрямую, но это уже второй токен в
input, поэтому мы используем $ 2. Второй параметр - это флаги операции: 0 означает «ничего особенного».
Затем, что нужно добавить: левая и правая части нашего выражения в скалярном контексте.
STACKS
Когда perl выполняет что-то вроде «addop», как он передает свои результаты следующей операции?
Ответ заключается в использовании стеков. Perl имеет несколько стеков для хранения вещей, которые он
в настоящее время мы работаем, и мы рассмотрим три наиболее важных из них.
Аргумент стек
Аргументы передаются в код PP и возвращаются из кода PP с использованием стека аргументов «ST».
Типичный способ обработки аргументов - вынуть их из стека и обращаться с ними, как вы
желаем, а затем помещаем результат обратно в стек. Так, например, косинус
оператор работает:
Значение NV;
значение = POPn;
значение = Perl_cos (значение);
XPUSHn (значение);
Мы увидим более сложный пример этого, когда рассмотрим макросы Perl ниже. «POPn» дает
вы - NV (значение с плавающей запятой) верхнего SV в стеке: $ x в "cos ($ x)". Тогда мы
вычислить косинус и вернуть результат в виде NV. «X» в «XPUSHn» означает, что
стек при необходимости следует расширить - здесь в этом нет необходимости, потому что мы знаем
в стеке есть место для еще одного предмета, поскольку мы только что удалили его! "XPUSH *"
макросы как минимум гарантируют безопасность.
В качестве альтернативы вы можете напрямую возиться со стеком: "SP" дает вам первый элемент в
ваша часть стека, а «TOP *» дает вам верхний SV / IV / NV / и т. д. в стеке. Так,
например, чтобы выполнить унарное отрицание целого числа:
SETi (-TOPi);
Просто установите целочисленное значение верхней записи стека на отрицание.
Управление стеком аргументов в ядре точно такое же, как и в XSUB - см.
perlxstut, perlxs и perlguts для более подробного описания макросов, используемых в стеке.
манипуляция.
Отметьте стек
Выше я сказал «ваша часть стека», потому что код PP не обязательно получает всю
стек для себя: если ваша функция вызывает другую функцию, вам нужно только открыть
аргументы, нацеленные на вызываемую функцию, и не (обязательно) позволять ей добиваться ваших собственных
данные. Мы делаем это так, чтобы иметь "виртуальную" нижнюю часть стека, доступную каждому
функция. Стек меток хранит закладки в местах в стеке аргументов, которые может использовать каждый.
функция. Например, при работе с связанной переменной (внутри, что-то с "P"
magic) Perl должен вызывать методы для доступа к связанным переменным. Однако нам необходимо
разделите аргументы, представленные методу, на аргумент, представленный исходному
функция - магазин или выборка, или что-то еще. Вот примерно как завязанный "толчок"
реализовано; см. "av_push" в ср.с:
1 ПУШМАРКА (SP);
2 РАСШИРЕНИЯ (SP, 2);
3 PUSH (SvTIED_obj ((SV *) av, мг));
4 PUSH (val);
5 ВОЗВРАТ;
6 ВВОД;
7 call_method («НАЖАТЬ», G_SCALAR | G_DISCARD);
8 ЛИСТ;
Давайте для практики рассмотрим всю реализацию:
1 ПУШМАРКА (SP);
Переместите текущее состояние указателя стека в стек меток. Это так, что когда
мы закончили добавлять элементы в стек аргументов, Perl знает, сколько вещей мы добавили
в последнее время.
2 РАСШИРЕНИЯ (SP, 2);
3 PUSH (SvTIED_obj ((SV *) av, мг));
4 PUSH (val);
Мы собираемся добавить еще два элемента в стек аргументов: когда у вас есть связанный массив,
Подпрограмма "PUSH" получает объект и значение, которое нужно передать, и это именно то, что
у нас есть связанный объект, полученный с помощью «SvTIED_obj», и значение, SV «val».
5 ВОЗВРАТ;
Затем мы говорим Perl обновить указатель глобального стека из нашей внутренней переменной: «dSP»
дал нам только локальную копию, а не ссылку на глобальную.
6 ВВОД;
7 call_method («НАЖАТЬ», G_SCALAR | G_DISCARD);
8 ЛИСТ;
«ENTER» и «LEAVE» локализуют блок кода - они проверяют, что все переменные
приведено в порядок, все, что было локализовано, получает свое предыдущее значение и так далее.
Думайте о них как о "{" и "}" блока Perl.
Чтобы действительно выполнить вызов магического метода, мы должны вызвать подпрограмму в пространстве Perl:
Об этом позаботится call_method, и это описано в perlcall. Мы называем "PUSH"
в скалярном контексте, и мы собираемся отказаться от его возвращаемого значения. В call_method ()
функция удаляет верхний элемент стека меток, поэтому вызывающей стороне нечего
убирать.
Сохранено стек
В C нет концепции локальной области видимости, поэтому perl ее предоставляет. Мы видели, что "ENTER" и
«ЛИСТЬЯ» используются как скобы для определения объема; стек сохранения реализует эквивалент C, для
пример:
{
местный $ foo = 42;
...
}
См. «Локализация изменений» в perlguts, чтобы узнать, как использовать стек сохранения.
МИЛЛИОНЫ OF МАКРОСЫ
Одна вещь, которую вы заметите в исходном коде Perl, - это то, что он полон макросов. Некоторые имеют
назвали повсеместное использование макросов самой сложной для понимания вещью, другие считают, что это добавляет
ясность. Возьмем для примера код, реализующий оператор сложения:
1 PP (pp_add)
2 {
3 dSP; DATARGET; tryAMAGICbin (добавить, opASSIGN);
4 {
5 dPOPTOPnnrl_ul;
6 SETn (левый + правый);
7 ВОЗВРАТ;
8}
9}
Каждая строка здесь (кроме фигурных скобок, конечно) содержит макрос. Первая строка устанавливает
вверх объявление функции, как Perl ожидает от кода PP; строка 3 устанавливает переменную
объявления для стека аргументов и цели, возвращаемое значение операции.
Наконец, он пытается увидеть, не перегружена ли операция сложения; если да, то соответствующий
вызывается подпрограмма.
Строка 5 - это еще одно объявление переменной - все объявления переменных начинаются с буквы "d", что
извлекает из вершины стека аргументов два NV (отсюда "nn") и помещает их в
переменные "право" и "лево", отсюда и "rl". Это два операнда сложения
оператор. Затем мы вызываем «SETn», чтобы установить NV возвращаемого значения как результат добавления
два значения. Это сделано, мы возвращаемся - макрос "RETURN" гарантирует, что наше возвращаемое значение
обрабатывается должным образом, и мы передаем следующий оператор для возврата в основной цикл выполнения.
Большинство этих макросов объясняется в perlapi, а некоторые из наиболее важных из них
также объясняется в perlxs. Обратите особое внимание на «Предпосылки и
PERL_IMPLICIT_CONTEXT "в perlguts для получения информации о макросах" [pad] THX_? ".
ДАЛЬШЕ ЧТЕНИЕ
Для получения дополнительной информации о внутреннем устройстве Perl см. Документы, перечисленные в разделе «Внутреннее устройство».
и интерфейс языка C »в perl.
Используйте perlinterp онлайн с помощью сервисов onworks.net