Raku: yakalama işaretçilerinin etkisi "daha yukarılarda" kaybolur

Aug 15 2020

Aşağıdaki Raku komut dosyası:

#!/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!"');

aşağıdaki çıktıya sahiptir:

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

İkinci öğe, not için strvalueyakalama pazarları ile amaçlandığı gibi, tırnaklar olmadan dize değeri içerir <(... )>. Ancak, benim için sürpriz, tırnak vardır dahil value.

Bunun bir yolu var mı?

Yanıtlar

6 raiph Aug 15 2020 at 22:30

TL; DR "çoklu gönderim" kullanın. [1,2] Her şeyin neden olduğu gibi olduğuna dair kapsamlı bir açıklama için @ user0721090601'in cevabına bakın. Sayı sözdiziminizin Raku'nunki ile eşleşmesini istiyorsanız, gramerinizde gerçekten akıllı bir değişiklik için @ p6steve'ye bakın.

Çoklu gönderim çözümü

Bunun bir yolu var mı?

Bunun bir yolu, açık çoklu gönderime geçmektir.

Şu anda, valueözel olarak adlandırılmış değer çeşitlerini çağıran bir jetonunuz var :

    token value { <strvalue> | <numvalue> }

Bunu şununla değiştir:

    proto token value {*}

ve sonra çağrılan simgeleri dilbilgisi çoklu gönderim hedefleme kurallarına göre yeniden adlandırın, böylece dilbilgisi şöyle olur:

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!"');

Bu şunları gösterir:

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

Bu, varsayılan olarak bireysel alternatifleri yakalamaz. "Çoklu gönderim" ile devam edebiliriz, ancak alt yakalamaların isimlendirmelerini yeniden başlatabiliriz:

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!"');

görüntüler:

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

Sürprizler

Sürpriz olarak, alıntılar dahil edildi value.

Ben de başlangıçta şaşırmıştım. [3]

Ancak şu anki davranış da bana en azından şu anlamda mantıklı geliyor:

  • Mevcut davranışın bazı durumlarda değeri vardır;

  • Bunu bekliyorsam şaşırtıcı olmazdı, ki bunu başka koşullarda da yapmış olabilirim;

  • O eğer bir akım davranışı elde vereceğini görmek kolay değil edilmiş istediği ancak bunun yerine (ve) başlangıçta beklendiği gibi çalıştı;

  • Yukarıda anlatıldığı gibi bir çözüm var.

Dipnotlar

[1] Çoklu gönderimin [2] kullanılması bir çözümdür, ancak orijinal sorun göz önüne alındığında aşırı karmaşık imo gibi görünüyor. Belki daha basit bir çözüm vardır. Belki birisi sorunuza başka bir cevap verecektir. Olmazsa, umarım bir gün en az bir çok daha basit çözümümüz olur. Ancak, yıllarca bir tane alamazsak şaşırmam. Yukarıdaki çözüme sahibiz ve yapacak daha çok şey var.

[2] Eğer iken olabilir diyelim ki, beyan,method value:foo { ... }ve bir yöntem yazmak, ben Rakudo olmayan yöntem kuralı izlemesi sevk olağan birden yöntem sevk mekanizmasını kullanır sanmıyorum (her tür yöntem döndürür bir maç nesnesi sağlanan) ama onun yerine bir kullanır NFA .

[3] Raku beklediğimiz gibi yaparsa, bazıları bunun "olması gerektiğini", "yapabileceğini" veya "" en iyisi olacağını "iddia edebilir. Diğerleri dikkate yükseltmek olduğunu her türlü olumsuz yanları almaya hazırım sürece hatalar hakkında oulding / özellikleri [| | c w sh] Ben genellikle önlemek eğer benim en iyi düşencelerdeyken bulmak ve iş almak için gereken do yardım razıyım işler tamam. Dolayısıyla, şu anda bunu% 10 hata,% 90 özellik olarak gördüğümü söyleyeceğim, ancak belirli bir senaryoda bu davranışı isteyip istemediğime bağlı olarak% 100 hata veya% 100 özelliğe "geçebilir" ve başkalarının ne düşündüğüne bağlı olarak.

6 user0721090601 Aug 15 2020 at 22:46

<(Ve )>yakalama belirteçler sadece belirli bir belirteç verilen dahilinde çalışır. Temel olarak, her bir simge , nesneler dizilirken dikkate alınan " MatchX ( .from) dizininden Y ( .to) dizinine orijinal dizeyi eşleştirdim" yazan bir nesne döndürür Match. Strvalue simgenizde olan şey bu:

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!

Yalnızca iki sayı olduğunu fark edeceksiniz: başlangıç ​​ve bitiş değeri. Bu value, sahip olduğunuz jetona baktığınızda, bitişik olmayan bir eşleşme yaratamaz. Yani .from6'ya ayarlandı ve .to21'e ayarlandı .

Bunu aşmanın iki yolu vardır: (a) bir eylemler nesnesi veya (b) bir çok amaçlı anahtar kullanarak. Her ikisinin de avantajları vardır ve bunu daha büyük bir projede nasıl kullanmak istediğinize bağlı olarak, birini veya diğerini tercih etmek isteyebilirsiniz.

Eylemleri doğrudan bir dilbilgisi içinde teknik olarak tanımlayabilseniz de, bunları ayrı bir sınıf aracılığıyla yapmak çok daha kolaydır. Yani sizin için sahip olabiliriz:

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

Her seviye make, belirteç içerdiği her şeye kadar değerleri iletir. Ve çevreleyen jeton, .madeyöntem aracılığıyla değerlerine erişebilir . Bu, saf dizge değerleriyle çalışmak yerine, önce onları bir şekilde işlemek ve bir nesne veya benzeri bir şey oluşturmak istediğinizde gerçekten güzel.

Ayrıştırmak için yapmanız gereken:

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

Bu aslında bir Pairnesnedir. TOPYöntemi değiştirerek kesin sonucu değiştirebilirsiniz .

Bir şeyler etrafında çalışmanın ikinci yolu, bir multi token. Dilbilgisi geliştirmede benzer bir şey kullanmak oldukça yaygındır.

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

Ancak, actions sınıfından da görebileceğiniz gibi, hangisinin gerçekten eşleştiğini kontrol etmemizi ve görmemizi gerektirir. Bunun yerine, değiştirme ile tamamlandığında kabul edilebilirse |, bir çoklu jeton kullanabilirsiniz:

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

<foo>Dilbilginizde kullandığınızda , iki çoklu sürümden biri, temelde olduğu gibi eşleşecektir <foo>. Daha da iyisi, bir eylemler sınıfı kullanıyorsanız, benzer şekilde $<foo>herhangi bir koşul veya başka kontroller olmadan onu kullanabilir ve orada olduğunu bilebilirsiniz.

Sizin durumunuzda şöyle görünecektir:

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

Artık, bir eylem nesnesi kullanmadan, aslında beklediğiniz gibi şeylere erişebiliriz:

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!」

Referans için her iki tekniği de birleştirebilirsiniz. Çoklu belirteç verilen eylemler nesnesini şimdi nasıl yazacağım:

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

Bu, ilk bakışta biraz daha zahmetli.

2 p6steve Aug 16 2020 at 03:13

Str & belirteç değeri: Aksine kendi belirteç değeri haddeleme daha num Eğer Num (+) ve Str (~) eşleştirme için Regex Boole çek kullanmak isteyebilirsiniz - bana açıklandığı gibi burada ve belgelenmiş burada

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