Perl 6で書かれたPerl 5のポッドパーサー(単純な古いドキュメント)の開発を終えたところです。驚くほど簡単に文法を作成することができました。 #perl6の人たちの助けを借りて、途中で多くの興味深いことを学びました。これをみんなと共有したいと思います。 もちろん、コードも添付されています。
ところで、
最初にPerl 6の文法の紹介を読むことをお勧めします。この記事の多くはより明確になります。
文法デザイン
Perl 6では、文法はテキストを解析するための特別な種類のクラスです。 アイデアは、正規表現のシーケンスを宣言し、トークンを割り当てることです。トークンは、入力の解析に使用できます。 Pod :: Perl5 :: Grammarの場合、perlpod仕様を詳細に作成し、標準を勉強するときに必要なトークンを追加しました。
もちろん、いくつかの問題があります。 まず、リストの規則性を定義する方法は? ポッドでは、リストにリストを含めることができます-定義自体にリストを含めることができますか? 再帰的な定義は、長さがゼロの文字列と一致しない限り可能であり、無限ループにつながることが判明しています。 定義は次のとおりです。
token over_back { <over> [ <_item> | <paragraph> | <verbatim_paragraph> | <blank_line> | <_for> | <begin_end> | <pod> | <encoding> | <over_back> ]* <back> } token over { ^^\=over [\h+ <[0..9]>+ ]? \n } token _item { ^^\=item \h+ <name> [ [ \h+ <paragraph> ] | [ \h* \n <blank_line> <paragraph>? ] ] } token back { ^^\=back \h* \n }
over_backトークンは、リスト全体を最初から最後まで記述します。 簡単に言えば、シートは= overで始まり、= backで終わるべきであり、途中には別のover_backを含む多くのものがあり得ると書かれています!
簡単にするために、私は通常、ポッドで記述された方法でトークンを呼び出しましたが、名前空間の交差のためにうまくいかない場合がありました。
私は特に次のテンプレートが好きなので、私はよくそれに目を向けました。
[ <pod_section> | <?!before <pod_section> > .]*
テンプレートを見つける必要がある場合に便利ですが、見つからない場合は他のすべてを無視します。 私たちの場合、pod_sectionはpodのセクションを定義するトークンですが、多くの場合、podはPerlコードで直接記述されるため、不要なものはすべて無視する必要があります。 したがって、定義の2番目の部分では、ネガティブな先読みを使用しますか?!前に、テキストの次の部分がpod_sectionと等しくないことを確認し、ピリオドを使用して改行を含む「その他すべて」をキャッチします。 両方の条件は、テキストを文字ごとにチェックするために、外側にアスタリスクの付いた角括弧でグループ化されます。
文法を使用して、ポッドを個別に解析し、コードに含めることができます。 すべてのポッドセクションを切り取り、マッチオブジェクトに配置します。 使い方は簡単です:
use Pod::Perl5::Grammar; my $match = Pod::Perl5::Grammar.parse($pod);
アクションクラス
アクションクラスは、解析中に文法に渡すことができる通常のPerl 6クラスです。 テンプレートが一致した時点で動作するように動作(アクション)トークンを割り当てることができます。 クラス内のメソッドと、それを実行する必要があるトークンに名前を付けるだけです。 ポッドからHTMLへのアクションクラスを作成しました。 = head1をHTMLに変換する方法は次のとおりです。
method head1 ($/) { self.add_to_html('body', "<h1>{$/<singleline_text>.Str}</h1>\n"); }
文法がhead1トークンを使用するたびに、このメソッドが実行されます。 見つかったシーケンスhead1を含む$ /変数が渡され、そこからテキスト文字列が抽出されます。
HTMLへの変換の場合、各アクションクラスは単に目的のトークンからテキストを抽出し、再フォーマットして表示します。 テキストの段落内のフォーマットコードのようなネストされたトークンに出会うまで、すべてがうまくいきました。 代わりに:
There are different ways to emphasize text, I<this is in italics> and B<this is in bold>
それは判明した:
<i>this is in italics</i> <b>this is in bold</b> <p>There are different ways to emphasize text, I<this is in italics> and B<this is in bold></p>
これは、イタリックとボールドがそもそも常連であるためです。 バッファを使用して、第2レベルのトークンからHTMLを保存する必要がありました。 段落トークンが見つかると、パーサーはテキストではなくこのバッファーの内容を置き換えます。 クラスは次のようになります。
method paragraph ($/ is copy) { my $original_text = $/<text>.Str.chomp; my $para_text = $/<text>.Str.chomp; for self.get_buffer('paragraph').reverse -> $pair
常連で働くことに特に注意を払う必要があります。 各サンプルアクションクラスは、$ /を使用します。 これは間違いです-次の結果として何が起こるかを推測してください:
method head1 ($/) { if $/.Str ~~ m/foobar/
変数を読み取り専用または値に割り当てます。核爆発。 $ /がhead1に渡されると、読み取り専用になります。 同じレキシカルスコープでレギュラーを実行すると、$ /が上書きされます。 私はこれに数回遭遇し、チャンネル#perl6を使用してこのオプションに決めました:
method head1 ($/ is copy) { my $match = $/; if $match.Str ~~ m/foobar/ { self.add_to_html('body', "<h1>{$match<singleline_text>.Str}</h1>\n"); } }
パラメーターにコピーを追加することで、$ /を指す代わりに値のコピーを作成します。 次に、match変数を$ matchにコピーすると、次のレギュラーは$ /でシームレスに機能します。 これを行う方が良いと思います:
method head1 ($match) { if $match.Str ~~ m/foobar/ { self.add_to_html('body', "<h1>{$match<singleline_text>.Str}</h1>\n"); } }
パラメータに$ /という名前を付けないでください。すべてが機能します。 しかし、包括的には、まだテストしていません。
アクションクラスを使用するには、単純に文法に渡します。
use Pod::Perl5::Grammar; use Pod::Perl5::ToHTML; my $actions = Pod::Perl5::ToHTML.new; my $match = Pod::Perl5::Grammar.parse($pod, :$actions);
最初の例では、位置引数$アクションを使用しています。 アクションと呼ばれる必要があります。 2番目の例では、引数にactions($ actions)という名前を付けました。この場合、actionクラスのオブジェクトは何でも呼び出すことができます。
ポッドの改善
PerlTricks.comの記事は、クラス名とスパンタグとともにHTMLで記述されています。 編集することも書くことも困難です。 編集にはポッドを使用したいと思います-ライターとエディターにとっては簡単です。 したがって、あらゆる種類の便利なブログ機能を追加して、ポッドを拡張したいと思います。 たとえば、フォーマットはB <...>および同様の関数を介して行われます。 Twitterへのリンクには@ <...>を、MetaCPANへのリンクにはM <...>を追加してみませんか?
Perl 6の文法はクラスであるため、継承して再定義できます。 そのため、次のような独自のコードを追加できます。
grammar Pod::Perl5::Grammar::PerlTricks is Pod::Perl5::Grammar { token twitter { @\< <name> \> } token metacpan { M\< <name> \> } }
また、format_codesトークンを再定義して、新しいトークンを含める必要があります。
token format_codes { [ <italic>|<bold>|<code>|<link> |<escape>|<filename>|<singleline> |<index>|<zeroeffect>|<twitter|<metacpan> ] }
とても簡単です。 新しい文法は、ポッドを解析し、新しい書式設定コードを処理できるようになります。 もちろん、Pod :: Perl5 :: Podクラスも拡張および再定義でき、結果は次のようになります。
Pod::Perl5::ToHTML::PerlTricks is Pod::Perl5::ToHTML { method twitter ($match) { self.add_to_buffer('paragraph', $match.Str => "<a href="http://twitter.com/{$match<name>.Str}">{$match<name>.Str}</a>"); } method metacpan ($match) { self.add_to_buffer('paragraph', $match.Str => "<a href="https://metacpan.org/pod//{$match<name>.Str}">{$match<name>.Str}</a>"); } }
それだけではありません
トークンのグループ、マルチディスパッチを使用するより視覚的な方法があります。 format_codesを代替トークンのリストとして定義する代わりに、プロトタイプメソッドを宣言し、各フォーマットメソッドをマルチプロトタイプオプションとして宣言します。
proto token format_codes { * } multi token format_codes:italic { I\< <multiline_text> \> } multi token format_codes:bold { B\< <multiline_text> \> } multi token format_codes:code { C\< <multiline_text> \> } ...
文法を継承する場合、format_codesをオーバーライドする必要はありません。 マルチを通じて新しいものを追加できます:
grammar Pod::Perl5::Grammar::PerlTricks is Pod::Perl5::Grammar { token format_codes:twitter { @\< <name> \> } token format_codes:metacpan { M\< <name> \> } }
また、このアプローチは、データを取得する方法に関して、一致オブジェクトでの作業を簡素化します。 たとえば、次のコードは、ポッドブロックの3番目の段落からリンクセクションを選択します。
is $match<pod_section>[0]<paragraph>[2]<text><format_codes>[0]<link><section>.Str
最初の例では、トークン形式名への参照が必要です。 ただし、2番目の例に示すように、マルチディスパッチではこれを回避できます。
おわりに
一般に、Perl 6でポッドパーサーを記述するのは非常に簡単です。 Perl 6でプログラミングする際に質問がある場合は、freenodeサーバー上のircチャネル#perl6を強くお勧めします。そこに集まった人々はとてもフレンドリーで親切です。