Raku: เอฟเฟกต์ของเครื่องหมายการจับหายไป“ สูงขึ้น”
สคริปต์ 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!」
สำหรับรายการที่สองทราบว่าstrvalue
มีค่าสตริงโดยไม่มีคำพูดตามที่ตั้งใจไว้กับตลาดจับ...<(
)>
แต่ฉันประหลาดใจคำพูดจะถูกvalue
รวมอยู่ใน
มีวิธีแก้ปัญหานี้หรือไม่?
คำตอบ
TL; DRใช้ "การจัดส่งหลายรายการ" [1,2]ดูคำตอบของ @ user0721090601 สำหรับคำอธิบายอย่างละเอียดว่าเหตุใดสิ่งต่างๆจึงเป็นเช่นนั้น ดู @ p6steve สำหรับการเปลี่ยนแปลงไวยากรณ์ของคุณอย่างชาญฉลาดหากคุณต้องการให้ไวยากรณ์ของตัวเลขตรงกับของ Raku
โซลูชันการจัดส่งหลายรายการ
มีวิธีแก้ปัญหานี้หรือไม่?
วิธีหนึ่งคือเปลี่ยนไปใช้การจัดส่งหลายรายการอย่างชัดเจน
ขณะนี้คุณมี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]การใช้หลายจัดส่ง [2]เป็นวิธีการแก้ปัญหา แต่ดูเหมือนซับซ้อนเกินไปให้ IMO ปัญหาเดิม บางทีอาจมีวิธีแก้ปัญหาที่ง่ายกว่านี้ บางทีอาจมีคนให้คำตอบอื่นสำหรับคำถามของคุณ ถ้าไม่ฉันหวังว่าวันหนึ่งเราจะมีวิธีแก้ปัญหาที่ง่ายกว่านี้อย่างน้อยที่สุด อย่างไรก็ตามฉันจะไม่แปลกใจเลยถ้าเราไม่ได้มาหลายปีแล้ว เรามีวิธีแก้ปัญหาข้างต้นและมีอะไรให้ทำอีกมากมาย
[2]ในขณะที่คุณสามารถประกาศพูดmethod value:foo { ... }
และเขียนเมธอดได้ (หากแต่ละวิธีดังกล่าวส่งคืนอ็อบเจ็กต์ที่ตรงกัน) ฉันไม่คิดว่า Rakudo ใช้กลไกการจัดส่งหลายวิธีตามปกติในการส่งไปยังการสลับกฎที่ไม่ใช่วิธีการ แต่ใช้NFA
[3]บางคนอาจโต้แย้งว่า "ควร" "ทำได้" หรือ "จะ" "ดีที่สุด" หากราคุทำตามที่เราคาดไว้ ฉันพบว่าฉันคิดว่าความคิดที่ดีที่สุดของฉันถ้าฉันมักจะหลีกเลี่ยง [sh | c | w] เกี่ยวกับจุดบกพร่อง / คุณสมบัติเว้นแต่ฉันเต็มใจที่จะรับข้อเสียทั้งหมดที่คนอื่นหยิบยกมาพิจารณาและยินดีที่จะช่วยทำงานที่จำเป็นเพื่อให้ได้มา สิ่งที่ทำ ฉันจะบอกว่าตอนนี้ฉันเห็นว่ามันเป็นบั๊ก 10% ฟีเจอร์ 90% แต่ "สามารถ" เปลี่ยนเป็นบั๊ก 100% หรือฟีเจอร์ 100% ขึ้นอยู่กับว่าฉันต้องการพฤติกรรมนั้นหรือไม่ในสถานการณ์นั้น ๆ และขึ้นอยู่กับว่าคนอื่นคิดอย่างไร
<(
และ)>
เครื่องหมายการจับภาพการทำงานเฉพาะภายในกำหนดสัญลักษณ์ที่กำหนด โดยทั่วไปโทเค็นแต่ละรายการจะส่งคืน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!
คุณจะสังเกตเห็นว่ามีเพียงสองตัวเลข: ค่าเริ่มต้นและค่าสิ้นสุด ผู้ชายคนนี้เมื่อคุณดูvalue
โทเค็นที่คุณมีจะไม่สามารถสร้างการจับคู่ที่ไม่ชัดเจนได้ มันจึง.from
ถูกตั้งค่าเป็น 6 และเป็น.to
21
มีสองวิธีในการแก้ปัญหานี้: โดยใช้ (a) วัตถุการดำเนินการหรือ (b) มัลติโทเคน ทั้งสองอย่างมีข้อดีและขึ้นอยู่กับว่าคุณต้องการใช้สิ่งนี้ในโครงการขนาดใหญ่อย่างไรคุณอาจต้องการเลือกอย่างใดอย่างหนึ่ง
แม้ว่าคุณจะกำหนดการกระทำได้โดยตรงภายในไวยากรณ์ แต่การดำเนินการผ่านคลาสแยกกันนั้นง่ายกว่ามาก ดังนั้นเราอาจมีให้คุณ:
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
วิธีการ
วิธีที่สองที่คุณสามารถแก้ไขสิ่งต่างๆได้คือใช้ไฟล์multi token
. เป็นเรื่องปกติธรรมดาในการพัฒนาไวยากรณ์เพื่อใช้บางสิ่งที่คล้ายกัน
token foo { <option-A> | <option-B> }
แต่อย่างที่คุณเห็นจากคลาสการดำเนินการเราต้องตรวจสอบและดูว่าอันไหนถูกจับคู่จริง แต่ถ้าการสลับสามารถยอมรับได้โดยทำด้วย|
คุณสามารถใช้มัลติโทเค็น:
proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
เมื่อคุณใช้ในโรงเรียนมัธยมของคุณก็จะตรงกับทั้งสองรุ่นหลายราวกับว่ามันได้รับในพื้นฐาน<foo>
<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 +$/ }
}
ซึ่งดูง่ายขึ้นเล็กน้อยในการดูครั้งแรก
แทนที่จะกลิ้งค่าโทเค็นของคุณเอง: str & token value: num คุณอาจต้องการใช้การตรวจสอบ Regex Boolean สำหรับการจับคู่ Num (+) และ Str (~) ตามที่อธิบายให้ฉันทราบที่นี่และบันทึกไว้ที่นี่
token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }