Raku: efek penanda tangkap hilang "lebih tinggi"
Skrip Raku berikut ini:
#!/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!"');
memiliki keluaran sebagai berikut:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「"Hello, World!"」
strvalue => 「Hello, World!」
Untuk item kedua, catatan yang strvalue
berisi nilai string tanpa tanda kutip, sebagaimana dimaksud dengan menangkap pasar <(
… )>
. Namun, saya terkejut, tanda kutip yang termasuk dalam value
.
Apakah ada jalan keluarnya?
Jawaban
TL; DR Gunakan "multiple dispatch". [1,2] Lihat jawaban @ user0721090601 untuk penjelasan lengkap tentang mengapa hal-hal seperti itu. Lihat @ p6steve untuk perubahan yang sangat cerdas pada tata bahasa Anda jika Anda ingin sintaks nomor Anda cocok dengan Raku.
Solusi pengiriman ganda
Apakah ada jalan keluarnya?
Salah satu caranya adalah dengan beralih ke pengiriman ganda eksplisit.
Anda saat ini memiliki value
token yang memanggil varian nilai bernama khusus:
token value { <strvalue> | <numvalue> }
Gantilah dengan:
proto token value {*}
dan kemudian mengganti nama token yang dipanggil menurut tata bahasa beberapa aturan penargetan pengiriman, sehingga tata bahasanya menjadi:
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!"');
Ini menampilkan:
「foo = 42」
keyword => 「foo」
value => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
Ini tidak menangkap pergantian individu secara default. Kita bisa tetap menggunakan "beberapa pengiriman" tetapi memperkenalkan kembali penamaan sub-tangkapan:
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!"');
menampilkan:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
strvalue => 「Hello, World!」
Kejutan
yang mengejutkan saya, kutipannya disertakan
value
.
Saya juga awalnya terkejut. [3]
Tetapi perilaku saat ini juga masuk akal bagi saya setidaknya dalam pengertian berikut:
Perilaku yang ada memiliki manfaat dalam beberapa keadaan;
Tidaklah mengherankan jika saya mengharapkannya, yang menurut saya mungkin akan saya lakukan dalam beberapa keadaan lain;
Ini tidak mudah untuk melihat bagaimana seseorang akan mendapatkan perilaku saat ini jika itu ingin tetapi bekerja seperti yang Anda (dan saya) awalnya diharapkan;
Ada solusinya, seperti yang dibahas di atas.
Catatan kaki
[1] Penggunaan beberapa pengiriman [2] adalah sebuah solusi, tetapi tampaknya terlalu rumit untuk mengingat masalah aslinya. Mungkin ada solusi yang lebih sederhana. Mungkin seseorang akan memberikannya dalam jawaban lain untuk pertanyaan Anda. Jika tidak, saya berharap suatu hari kita memiliki setidaknya satu solusi yang lebih sederhana. Namun, saya tidak akan terkejut jika kami tidak mendapatkannya selama bertahun-tahun. Kami memiliki solusi di atas, dan masih banyak lagi yang harus dilakukan.
[2] Meskipun Anda dapat mendeklarasikan, mengatakan,method value:foo { ... }
dan menulis metode (asalkan setiap metode tersebut mengembalikan objek yang cocok), saya rasa Rakudo tidak menggunakan mekanisme pengiriman beberapa metode yang biasa untuk mengirimkan ke alternatif aturan non-metode melainkan menggunakan NFA .
[3] Beberapa orang mungkin berpendapat bahwa itu "harus", "bisa", atau "akan" "menjadi yang terbaik" jika Raku melakukan seperti yang kita harapkan. Saya merasa saya memikirkan pemikiran terbaik saya jika saya biasanya menghindari [sh | c | w] oulding tentang bug / fitur kecuali saya bersedia untuk mengambil setiap dan semua kelemahan yang orang lain angkat menjadi pertimbangan dan bersedia membantu melakukan pekerjaan yang diperlukan untuk mendapatkannya hal-hal selesai. Jadi saya hanya akan mengatakan bahwa saat ini saya melihatnya sebagai 10% bug, 90% fitur, tetapi "dapat" beralih ke 100% bug atau fitur 100% tergantung pada apakah saya menginginkan perilaku itu atau tidak dalam skenario tertentu , dan bergantung pada apa yang dipikirkan orang lain.
The <(
dan )>
menangkap spidol hanya bekerja dalam diberi tanda tertentu. Pada dasarnya, setiap token mengembalikan Match
objek yang mengatakan "Saya mencocokkan string asli dari indeks X ( .from
) ke indeks Y ( .to
)", yang diperhitungkan saat merangkai Match
objek. Itulah yang terjadi dengan token strvalue Anda:
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!
Anda akan melihat bahwa hanya ada dua angka: nilai awal dan akhir. Pria ini bahwa ketika Anda melihat value
token yang Anda miliki, itu tidak dapat membuat kecocokan yang tidak jelas. Jadi .from
disetel ke 6, dan .to
ke 21.
Ada dua cara untuk melakukannya: dengan menggunakan (a) objek tindakan atau (b) multitoken. Keduanya memiliki kelebihan, dan tergantung pada bagaimana Anda ingin menggunakannya dalam proyek yang lebih besar, Anda mungkin ingin memilih salah satunya.
Meskipun secara teknis Anda dapat menentukan tindakan langsung dalam tata bahasa, jauh lebih mudah melakukannya melalui kelas terpisah. Jadi kami mungkin memiliki untuk Anda:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made }
method keyword ($/) { make ~$/ }
method value ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}
Setiap level make
untuk meneruskan nilai ke token apa pun yang menyertakannya. Dan token terlampir memiliki akses ke nilainya melalui .made
metode. Ini sangat bagus ketika, alih-alih bekerja dengan nilai string murni, Anda ingin memprosesnya terlebih dahulu dan membuat objek atau serupa.
Untuk mengurai, Anda cukup melakukan:
my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!
Yang sebenarnya adalah sebuah Pair
benda. Anda dapat mengubah hasil persisnya dengan memodifikasi TOP
metode.
Cara kedua untuk mengatasi berbagai hal adalah dengan menggunakan file multi token
. Sangat umum dalam mengembangkan tata bahasa untuk menggunakan sesuatu yang mirip
token foo { <option-A> | <option-B> }
Tapi seperti yang Anda lihat dari kelas tindakan, kami harus memeriksa dan melihat mana yang benar-benar cocok. Sebagai gantinya, jika pergantian dapat diterima dengan dilakukan dengan |
, Anda dapat menggunakan multitoken:
proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
Ketika Anda menggunakan <foo>
dalam tata bahasa Anda, itu akan cocok dengan salah satu dari dua multi versi seolah-olah itu telah ada di garis dasar <foo>
. Lebih baik lagi, jika Anda menggunakan kelas tindakan, Anda juga dapat menggunakan $<foo>
dan mengetahuinya di sana tanpa persyaratan atau pemeriksaan lainnya.
Dalam kasus Anda, akan terlihat seperti ini:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
multi token value:sym<str> { '"' <( <-["]>* )> '"' }
multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}
Sekarang kita dapat mengakses hal-hal seperti yang Anda harapkan, tanpa menggunakan objek tindakan:
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!」
Sebagai referensi, Anda dapat menggabungkan kedua teknik tersebut. Begini cara saya sekarang menulis objek tindakan yang diberi multi token:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}
Yang sedikit lebih grokkable pada tampilan pertama.
Daripada menggulung nilai token Anda sendiri: str & nilai token: num Anda mungkin ingin menggunakan pemeriksaan Regex Boolean untuk pencocokan Num (+) dan Str (~) - seperti yang dijelaskan kepada saya di sini dan didokumentasikan di sini
token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }