Raku: เอฟเฟกต์ของเครื่องหมายการจับหายไป“ สูงขึ้น”

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

สำหรับรายการที่สองทราบว่าstrvalueมีค่าสตริงโดยไม่มีคำพูดตามที่ตั้งใจไว้กับตลาดจับ...<( )>แต่ฉันประหลาดใจคำพูดจะถูกvalueรวมอยู่ใน

มีวิธีแก้ปัญหานี้หรือไม่?

คำตอบ

6 raiph Aug 15 2020 at 22:30

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% ขึ้นอยู่กับว่าฉันต้องการพฤติกรรมนั้นหรือไม่ในสถานการณ์นั้น ๆ และขึ้นอยู่กับว่าคนอื่นคิดอย่างไร

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!

คุณจะสังเกตเห็นว่ามีเพียงสองตัวเลข: ค่าเริ่มต้นและค่าสิ้นสุด ผู้ชายคนนี้เมื่อคุณดูvalueโทเค็นที่คุณมีจะไม่สามารถสร้างการจับคู่ที่ไม่ชัดเจนได้ มันจึง.fromถูกตั้งค่าเป็น 6 และเป็น.to21

มีสองวิธีในการแก้ปัญหานี้: โดยใช้ (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 +$/ }
}

ซึ่งดูง่ายขึ้นเล็กน้อยในการดูครั้งแรก

2 p6steve Aug 16 2020 at 03:13

แทนที่จะกลิ้งค่าโทเค็นของคุณเอง: str & token value: num คุณอาจต้องการใช้การตรวจสอบ Regex Boolean สำหรับการจับคู่ Num (+) และ Str (~) ตามที่อธิบายให้ฉันทราบที่นี่และบันทึกไว้ที่นี่

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