楽:キャプチャマーカーの効果は「より高い」で失われます

Aug 15 2020

次のRakuスクリプト:

#!/usr/bin/env raku
use v6.d;

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    token value { <strvalue> | <numvalue> }
    token strvalue { '"' <( <-["]>* )> '"' }
    token numvalue { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

次の出力があります。

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「"Hello, World!"」
  strvalue => 「Hello, World!」

2番目の項目、ノートのstrvalueキャプチャ市場で意図したように、引用符なしの文字列値が含まれています<(... )>。ただし、驚いたことに、引用符はに含まれていvalueます。

これを回避する方法はありますか?

回答

6 raiph Aug 15 2020 at 22:30

TL; DR「多重ディスパッチ」を使用します。[1,2]物事が現状のままである理由の完全な説明については、@ user0721090601の回答を参照してください。数値の構文をRakuの構文と一致させたい場合は、文法の非常にスマートな変更について@ p6steveを参照してください。

多重ディスパッチソリューション

これを回避する方法はありますか?

1つの方法は、明示的な多重ディスパッチに切り替えることです。

現在、value具体的に名前が付けられた値バリアントを呼び出すトークンがあります。

    token value { <strvalue> | <numvalue> }

これを次のように置き換えます。

    proto token value {*}

次に、文法の多重ディスパッチターゲティングルールに従って、呼び出されたトークンの名前を変更します。これにより、文法は次のようになります。

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value {*}
    token value:str { '"' <( <-["]>* )> '"' }
    token value:num { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

これは以下を表示します:

「foo = 42」
 keyword => 「foo」
 value => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」

これは、デフォルトでは個々の交代をキャプチャしません。「多重ディスパッチ」に固執することはできますが、サブキャプチャの名前を再導入します。

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    token value:str { '"' <( $<strvalue>=(<-["]>*) )> '"' } token value:num { $<numvalue>=('-'? \d+ [ '.' \d* ]?) }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

表示:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」
  strvalue => 「Hello, World!」

サプライズ

驚いたことに、引用符はに含まれていvalueます。

私も最初はびっくりしました。[3]

しかし、現在の動作は、少なくとも次の意味でも私には意味があります。

  • 既存の動作には、状況によってはメリットがあります。

  • 私がそれを期待していたとしても驚くことではありません。それは他の状況でやったかもしれないと思います。

  • それは場合は、1つは現在の動作を取得する方法を見ることは容易ではありませんして欲しかったが、代わりに、あなた(と私は)最初は期待通りに働いていました。

  • 上で説明したように、解決策があります。

脚注

[1]複数のディスパッチの使用[2]である溶液が、IMOの元の問題与えられた過度に複雑と思われます。おそらく、もっと簡単な解決策があります。おそらく誰かがあなたの質問に対する別の答えでそれを提供するでしょう。そうでない場合は、いつか私たちが少なくとも1つのはるかに簡単な解決策を持っていることを願っています。しかし、何年も手に入れなくても驚かないでしょう。上記の解決策がありますが、他にもやるべきことがたくさんあります。

[2]メソッド宣言、言い、method value:foo { ... }書くことはできます(そのような各メソッドが一致オブジェクトを返す場合)、Rakudoは通常の複数メソッドディスパッチメカニズムを使用して非メソッドルールの変更にディスパッチするとは思いませんが、代わりにNFA。

[3]楽が期待通りにやったら、「すべき」、「できた」、「だろう」「最善を尽くす」と主張する人もいるかもしれません。私は他の人が考慮に引き上げることを、あらゆる欠点を取って喜んだ場合を除きバグについてoulding /ます[| | C wはSH]を私は、一般的に避ける場合、私は私の最高の考えを見つける仕事を得るために必要なん助けに喜んでやったこと。したがって、現在10%のバグ、90%の機能として表示されているとだけ言っておきますが、特定のシナリオでその動作が必要かどうかに応じて、100%のバグまたは100%の機能に「変更される可能性があります」 、そして他の人の考えに応じて。

6 user0721090601 Aug 15 2020 at 22:46

<(そして)>、キャプチャマーカ与えられている特定のトークン内でのみ動作します。基本的に、各トークンは、Match「元の文字列をインデックスX(.from)からインデックスY(.to)に一致させました」というオブジェクトを返します。これは、オブジェクトを文字列化するときに考慮されますMatch。それがあなたのstrvalueトークンで起こっていることです:

my $text = 'bar = "Hello, World!"'; my $m = MyGrammar.parse: $text; my $start = $m<value><strvalue>.from; # 7 my $end   = $m<value><strvalue>.to; # 20 say $text.substr: $start, $end - $start;  # Hello, World!

開始値と終了値の2つの数値しかないことに気付くでしょう。これvalueは、あなたが持っているトークンを見るとき、それが不連続な一致を作成することができないことを意味します。つまり.from、6に設定され、.to21に設定されます。

これを回避するには、(a)アクションオブジェクトまたは(b)マルチトークンを使用する2つの方法があります。どちらにも利点があり、大規模なプロジェクトでこれをどのように使用するかに応じて、どちらかを選択することをお勧めします。

技術的には文法内で直接アクションを定義できますが、別のクラスを介して行う方がはるかに簡単です。だから私たちはあなたのために持っているかもしれません:

class MyActions { 
  method TOP      ($/) { make $<keyword>.made => $<value>.made }
  method keyword  ($/) { make ~$/ }
  method value    ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}

各レベルmakeは、それを含むトークンに値を渡します。また、囲んでいるトークンは、.madeメソッドを介してそれらの値にアクセスできます。これは、純粋な文字列値を操作する代わりに、何らかの方法で最初にそれらを処理してオブジェクトなどを作成する場合に非常に便利です。

解析するには、次のようにします。

my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!

これは実際にはPairオブジェクトです。TOPメソッドを変更することで、正確な結果を変更できます。

物事を回避する2つ目の方法は、を使用することmulti tokenです。文法を開発する際に、次のようなものを使用することはかなり一般的です。

token foo { <option-A> | <option-B> }

ただし、actionsクラスからわかるように、実際に一致したものを確認する必要があります。代わりに、を使用して交代を受け入れる|ことができる場合は、マルチトークンを使用できます。

proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }

<foo>文法で使用すると、ベースラインにあるかのように2つのマルチバージョンのいずれかに一致します<foo>。さらに良いことに、アクションクラスを使用$<foo>している場合は、条件やその他のチェックを行わなくても、同様に使用して、そこにあることを知ることができます。

あなたの場合、それは次のようになります:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    multi token value:sym<str> { '"' <( <-["]>* )> '"' }
    multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}

これで、アクションオブジェクトを使用せずに、当初の期待どおりにアクセスできるようになりました。

my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;

say $m; # 「bar = "Hello, World!"」 # keyword => 「bar」 # value => 「Hello, World!」 say $m<value>; # 「Hello, World!」

参考までに、両方の手法を組み合わせることができます。マルチトークンを指定して、アクションオブジェクトを作成する方法は次のとおりです。

class MyActions { 
  method TOP            ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}

これは一見するともう少しグロッキーです。

2 p6steve Aug 16 2020 at 03:13

独自のトークン値:strとトークン値:numをロールするのではなく、ここで説明し、ここに文書化されているように、Num(+)とStr(〜)の一致に正規表現ブールチェックを使用することをお勧めします

token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }