Ответ 1
Этот ответ нацелен на то, чтобы:
-
Вводный общий ответ на "Я хочу разобрать X с Perl 6. Может кто-нибудь помочь?"
-
Полный и подробный ответ, который в точности соответствует требованиям @Suman.
В одном утверждении (опытный пользователь)
"$_[0]: $_[1]\n" .put
for (slurp 'derm.bib')
~~ m:g/ '@article{' (<-[,]>+) ',' \s+ 'title={' ~ '}' (<-[}]>+) /
(Запустите этот код на glot.io.)
Я решил начать с того, что разработчик, знакомый с P6, напишет через несколько минут, чтобы выполнить простую задачу, которую вы указали в своем вопросе, если они не очень заботятся о читаемости для новичков.
Я не собираюсь объяснять это. Это просто делает работу. Если вы новичок P6, это может быть ошеломляющим. Если это так, пожалуйста, прочитайте остальную часть моего ответа - он идет медленнее и имеет исчерпывающий комментарий. Возможно, вернитесь сюда и посмотрите, имеет ли это смысл после прочтения остальных.
Базовое решение Perl 6
my \input = slurp 'derm.bib' ;
my \pattern = rule { '@article{' ( <-[,]>+ ) ','
'title={' ~ '}' ( <-[}]>+ ) }
my \articles = input.match: pattern, :global ;
for articles -> $/ { print "$0: $1\n\n" }
Это почти идентично коду "одиночный оператор (опытный пользователь)" - разбит на четыре оператора, а не на один. Я мог бы заставить его более точно скопировать первую версию кода, но вместо этого внес несколько изменений, которые я объясню. Я сделал это, чтобы прояснить, что у P6 преднамеренно есть свои функции, представляющие собой масштабируемый и рефакторизированный континуум, чтобы можно было смешивать и, следовательно, сопоставлять любые функции, которые лучше всего подходят для данного варианта использования.
my \input = slurp 'derm.bib' ;
Perls славятся своими символами. В P6, если они вам не нужны, вы можете "вырезать" их. Перлз также известен тем, что у него есть лаконичные способы ведения дел. slurp
читает файл целиком за один раз.
my \pattern = rule { '@article{' ( <-[,]>+ ) ','
'title={' ~ '}' ( <-[}]>+ ) }
Шаблоны Perl 6 обычно называются регулярными выражениями или правилами. Существует несколько типов регулярных выражений/правил. Язык шаблонов одинаков; различные типы просто направляют соответствующий механизм, чтобы изменить способ обработки данного шаблона.
Один тип регулярных выражений - это P6-эквивалент классических регулярных выражений. Они объявляются либо с помощью /.../
либо с помощью regex {...}
. Регулярное выражение в вводном коде "опытного пользователя" было одним из этих регулярных выражений. Их различие заключается в том, что они возвращаются при необходимости, как классические регулярные выражения.
Нет необходимости возвращаться назад, чтобы соответствовать формату .bib
. Если вам не нужно возвращаться назад, целесообразно рассмотреть возможность использования одного из других типов правил. Я переключился на правило, объявленное с ключевым словом rule
.
Правило, объявленное с помощью rule
, идентично rule
объявленному с помощью regex
(или /.../
), за исключением того, что A) оно не возвращает назад и B) оно интерпретирует пробелы в своем шаблоне как соответствующие возможным пробелам во входных данных. Вы \s+
что я \s+
из шаблона непосредственно перед 'title={'
? Это потому, что rule
заботится об этом автоматически.
Другое отличие в том, что я написал:
'title={' ~ '}' ( ... )
вместо:
'title={' ( ... ) '}'
т.е. перемещение шаблона в соответствии с битом между фигурными скобками после фигурных скобок и вместо этого вставка ~
между фигурными скобками. Они соответствуют одной общей схеме. Я мог бы написать что-либо в шаблоне для /.../
пользователей /.../
и в любом случае в этом шаблоне rule
раздела. Но я хотел, чтобы этот раздел был более ориентирован на "лучшие практики". Я отложу полное объяснение этого различия и всех других деталей этого паттерна до раздела " Объяснение грамматики bib" ниже.
my \articles = input.match: pattern, :global ;
Эта строка использует форму метода подпрограммы m
использовавшуюся в более ранней версии "опытного пользователя".
:global
- это то же самое, что и :g
. Я мог бы написать так или иначе в обеих версиях.
Добавьте :global
(или :g
) в список аргументов при вызове метода .match
(или m
подпрограммы), если вы хотите выполнить поиск по всей сопоставляемой строке, найдя столько совпадений, сколько есть, а не только первое. Затем метод (или подпрограмма m
) возвращает список объектов Match
а не только один. В этом случае мы получим три, соответствующие трем статьям во входном файле.
for articles -> $/ { print "$0: $1\n\n" }
В соответствии с документом P6 для $/, " $/
является переменной соответствия... поэтому обычно содержит объекты типа Match.". Это также обеспечивает некоторые другие удобства, и мы воспользуемся одним из этих удобств, как описано ниже.
Цикл for
последовательно связывает каждый из общих объектов Match (соответствующих каждой из статей в вашем примере файла, которые были успешно проанализированы грамматикой) с символом $/
внутри блока for
.
Шаблон содержит две пары скобок. Они генерируют "Позиционные захваты". Общий объект Match обеспечивает доступ к своим двум захватам положения посредством подписки положения (postfix []
). Таким образом, в блоке for
$/[0]
и $/[1]
обеспечивают доступ к двум позиционным захватам для данной статьи. Но так же поступают $0
и $1
- потому что стандартный P6 объединяет эти последние символы в $/[0]
и $/[1]
для вашего удобства.
Все еще со мной?
Вторая половина этого ответа строится и подробно объясняет грамматический подход. Чтение этого может дать дальнейшее понимание решений выше.
Но сначала...
"Скучный" практический ответ
Я хочу разобрать это с Perl 6. Может кто-нибудь помочь?
P6 может сделать написание синтаксических анализаторов менее утомительным, чем с другими инструментами. Но менее утомительно все еще утомительно. И разбор P6 в настоящее время идет медленно.
В большинстве случаев практический ответ, когда вы хотите разобрать что-либо, кроме самых тривиальных форматов файлов - особенно хорошо известный формат, которому несколько десятилетий - - это найти и использовать существующий синтаксический анализатор.
Вы можете начать с поиска "bib" на modules.perl6.org в надежде найти общедоступный модуль анализа "bib". Либо чистый Perl 6 one, либо какая-нибудь оболочка P6 вокруг библиотеки не-P6. Но на момент написания этой статьи не было совпадений для "нагрудника".
Почти наверняка уже доступна библиотека синтаксического анализа "bib". И это, вероятно, будет самым быстрым решением. Также вероятно, что вы можете легко и элегантно использовать внешнюю библиотеку синтаксического анализа, упакованную как C lib, в своем собственном коде P6, даже если вы не знаете C. Если NativeCall - это слишком много или слишком мало объяснений, рассмотрите посещение freenode IRC канал # perl6 и запрос на любую помощь NativeCall, в которой вы нуждаетесь или хотите.
Если C lib не подходит для конкретного случая использования, вы, вероятно, все еще можете использовать пакеты, написанные на Perl 5, Python, Ruby, Lua и т.д. Через их языковые адаптеры Inline::*
. Просто установите Perl 5, Python или любой другой пакет, который вам нужен; убедитесь, что он работает на другом языке; установить соответствующий языковой адаптер; затем используйте пакет и его функции, как если бы это был пакет P6, содержащий экспортированные функции P6, классы, объекты, значения и т.д.
Адаптер Perl 5 является наиболее зрелым, поэтому я буду использовать его в качестве примера. Допустим, вы используете пакеты Perl 5 Text :: BibTex и теперь хотите использовать Perl 6 с существующим модулем Text :: BibTeX :: BibFormat из Perl 5. Сначала настройте пакеты Perl 5 так, как они должны быть в соответствии с их README и т.д. Затем в Perl 6 напишите что-то вроде:
use Text::BibTeX::BibFormat:from<Perl5>;
...
@blocks = $entry.format;
Первая строка - как вы говорите P6, что вы хотите загрузить модуль P5. (Он не будет работать, если Inline::Perl5
уже установлен и работает. Но так должно быть, если вы используете популярный пакет Rakudo Perl 6. И если нет, то по крайней мере вы должны иметь zef
установщика модуля, чтобы вы могли запустить zef install Inline::Perl5
.)
Последняя строка - это просто механический перевод P6 @blocks = $entry->format;
строка из ОПИСАНИЯ Perl 5 Text :: BibTeX :: BibFormat.
Создание грамматики/парсера P6
ХОРОШО. Достаточно "скучных" практических советов. Давайте теперь попробуем повеселиться, создав парсер P6, достаточно хороший для примера из вашего вопроса.
# use Grammar::Tracer;
grammar bib {
rule TOP { <article>* }
rule article { '@article{' $<id>=<-[,]>+ ','
<kv-pairs>
'}'
}
rule kv-pairs { <kv-pair>* % ',' }
rule kv-pair { $<key>=\w* '={' ~ '}' $<value>=<-[}]>* }
}
Имея эту грамматику, мы можем написать что-то вроде:
die "Maybe use Grammar::Tracer?" unless bib.parsefile: 'derm.bib';
for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" }
генерировать точно такой же вывод, что и в более ранних решениях "опытный пользователь" и "базовый Perl 6", но с использованием подхода грамматики/парсера.
Объяснение грамматики "нагрудник"
# use Grammar::Tracer;
Если анализ не выполнен, возвращаемое значение равно Nil
. P6 не скажет вам, как далеко это зашло. Вы не будете иметь ни малейшего понятия, почему ваш анализ не удался.
Если у вас нет лучшего варианта (?), То, когда ваша грамматика не сработает, use Grammar::Tracer
для отладки (сначала установите его, если он еще не установлен).
grammar bib {
Ключевое слово grammar
похоже на class
, но грамматика может содержать не только именованный method
как обычно, но также и regex
s, token
s и rule
s
rule TOP {
Если не указано иное, процедуры синтаксического анализа начинаются с вызова rule
(или token
, regex
или method
) с именем TOP
.
В качестве эмпирического правила, если вы не знаете, следует ли использовать какое-то rule
, regex
, token
или method
для некоторого анализа, используйте token
. (В отличие от шаблонов regex
, token
не возвращаются назад, поэтому они исключают риск излишней медленной работы из-за возврата).
Но в этом случае я использовал rule
. Как и шаблоны token
, rule
также позволяет избежать возврата. Но кроме того, они берут пробелы, следующие за любым атомом в шаблоне, чтобы быть значимыми естественным образом. Это обычно подходит к вершине дерева разбора. (Жетоны и случайные регулярные выражения обычно подходят к листьям.)
rule TOP { <article>* }
Пробел в конце правила означает, что грамматика будет соответствовать любому количеству пробелов в конце ввода.
<article>
вызывает другое именованное правило (или токен/регулярное выражение/метод) в этой грамматике.
Похоже, что нужно разрешить любое количество статей на файл bib, я добавил *
(ноль или более квантификатор) в конце <article>*
.
rule article { '@article{' $<id>=<-[,]>+ ','
<kv-pairs>
'}'
}
Иногда я выкладываю правила, напоминающие то, как выглядит типичный ввод. Я пытался сделать это здесь.
<[...]>
- это синтаксис P6 для символьного класса, как [...]
в традиционном синтаксисе регулярных выражений. Он более мощный, но пока все, что вам нужно знать, это то, что -
in <-[,]>
указывает на отрицание, то есть то же самое, что и синтаксис ^
in ye olde [^,]
. Так что <-[,]>+
пытается матч одного или нескольких персонажей, ни один из которых ,
.
$<id>=<-[,]>+
говорит P6 попытаться сопоставить квантифицированный атом справа от бита =
(т.е. бита <-[,]>+
) и сохранить результаты в ключе 'id'
внутри текущий объект Match. Последний будет подвешен к ветки дерева разбора; мы попадем именно туда, где позже.
rule kv-pairs { <kv-pair>* % ',' }
Этот код регулярного выражения иллюстрирует одну из нескольких удобных функций регулярного выражения P6. Это говорит о том, что вы хотите сопоставить ноль или более kv-pair
разделенных запятыми.
(Более подробно, инфиксный оператор %
regex требует, чтобы совпадения квантифицированного атома слева были разделены атомом справа.)
rule kv-pair { $<key>=\w* '={' ~ '}' $<value>=<-[}]>* }
Новый бит здесь '={' ~ '}'
. Это еще одна удобная функция регулярных выражений. Оператор тильды регулярных выражений анализирует структуру с разделителями (в данном случае структуру с ={
opener и }
ближе) с битом между разделителями, совпадающим с количественным атомом регулярного выражения справа от ближе. Это дает несколько преимуществ, но главное состоит в том, что сообщения об ошибках могут быть намного понятнее.
Объяснение конструкции/деконструкции дерева разбора
Биты $<article>
и .<id>
и т.д. в последней строке (for $<article> { say.<id> ~ ':' ~.<kv-pairs><kv-pair>[0]<value> ~ "\n" }
) относится к объектам Match, которые хранятся в дереве разбора, сгенерированном и возвращенном в результате успешного разбора.
Возвращаясь к началу грамматики:
rule TOP {
Если анализ выполнен успешно, возвращается один объект Match уровня TOP, соответствующий верху дерева анализа. (Он также стал доступным для кода сразу после вызова метода parse через переменную $/
.)
Но до того, как произойдет окончательный возврат из синтаксического анализа, многие другие объекты Match, представляющие части общего анализа, будут сгенерированы и добавлены в дерево анализа. Добавление объектов Match в дерево разбора выполняется путем назначения либо одного сгенерированного объекта Match, либо их списка, либо позиционному, либо ассоциативному элементу "родительского" объекта Match, как описано далее.
rule TOP { <article>* }
Вызов правила, такой как <article>
имеет два эффекта. Сначала P6 пытается соответствовать правилу. Во-вторых, если он совпадает, P6 генерирует соответствующий объект Match и добавляет его в дерево разбора.
Если бы успешно сопоставленный шаблон был просто <article>
, а не <article>*
, то была бы предпринята попытка только одного совпадения, и только одно значение, единственный объект Match, было бы сгенерировано и добавлено в дерево разбора.
Но шаблон был <article>*
, а не просто <article>
. Таким образом, P6 пытается сопоставить правило article
несколько раз. Если он соответствует хотя бы один раз, он генерирует и сохраняет соответствующий список из одного или нескольких объектов Match. (Более подробное объяснение см. В моем ответе на вопрос "Как получить доступ к захватам в матче?".)
Таким образом, список объектов Match назначается клавише 'article'
объекта Match верхнего уровня. (Если бы соответствующее выражение регулярного выражения было просто <article>
а не <article>*
тогда совпадение привело бы к тому, что 'article'
ключа 'article'
был назначен только один объект Match, а не их список.)
Итак, теперь я попытаюсь объяснить часть $<article>
последней строки кода, которая была:
for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" }
$<article>
- это сокращение от $/.<article>
.
В соответствии с документом P6 для $/, " $/
- это переменная соответствия. Она хранит результат последнего совпадения с регулярным выражением и поэтому обычно содержит объекты типа Match.".
Последнее совпадение с Regex в нашем случае было правилом TOP
из грамматики bib
.
Таким образом, $<article>
- это значение под ключом 'article'
объекта Match верхнего уровня, возвращаемого анализом. Это значение представляет собой список из 3 объектов Match уровня "article".
rule article { '@article{' $<id>=<-[,]>+ ','
Регулярное выражение article
в свою очередь, содержит $<id>
в левой части задания. Это соответствует назначению объекта Match новому ключу 'id'
добавленному к объекту Match уровня статьи.
Надеюсь, этого достаточно (возможно, слишком много!), И теперь я могу объяснить последнюю строку кода, которая, опять же, была:
for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" }
Параметр for
выполняет итерацию по списку из 3 объектов Match (соответствующих 3 статьям во входных данных), которые были сгенерированы во время анализа и сохранены под ключом 'article'
объекта Match верхнего уровня.
(Эта итерация автоматически присваивает каждому из этих трех объектов Sub Match значение $_
, иначе "it" или "theme", а затем, после каждого назначения, выполняет код в блоке ({... }
). Код в блок обычно ссылается, явно или неявно, на $_
.)
Бит .<id>
в блоке эквивалентен $_.<id>
, т.е. неявно ссылается на $_
. Как только что объяснено, $_
- это объект соответствия уровня article
, обрабатываемый на этот раз в цикле for
. Бит <id>
означает .<id>
возвращает объект Match, сохраненный под ключом 'id'
объекта Match уровня article
.
Наконец, бит .<kv-pairs><kv-pair>[0]<value>
относится к объекту Match, хранящемуся под ключом 'value'
объекта Match, хранящегося в качестве первого (0-го) элемента в списке Match объекты, хранящиеся в ключе kv-pair
объекта Match, соответствующего правилу kv-pairs
которое, в свою очередь, хранится в ключе 'kv-pairs'
объекта Match уровня article
.
Уф!
Когда автоматически сгенерированное дерево разбора не то, что вы хотите
Как будто всего вышеперечисленного было недостаточно, я должен упомянуть еще одну вещь.
Дерево разбора сильно отражает неявную древовидную структуру грамматики. Но получение этой структуры в результате анализа иногда неудобно - вместо этого может потребоваться другая древовидная структура, возможно, гораздо более простое дерево, возможно, некоторая не древовидная структура данных.
Основным механизмом генерации именно того, что вы хотите из анализа, когда автоматические результаты не подходят, является использование make. (Это может быть использовано в блоках кода внутри правил или выделено в классы Action, которые отделены от грамматик.)
В свою очередь, основным вариантом использования make
является генерация разреженного дерева узлов, свисающих с дерева разбора.
Наконец, основным вариантом использования этих разреженных деревьев является хранение AST.