อาจเป็นสิ่งสำคัญที่ทิ้งมันคือ\s
ตรงกับพื้นที่แนวนอนและแนวตั้ง เพื่อให้ตรงกับพื้นที่แนวนอนให้ใช้\h
และเพื่อให้ตรงกับพื้นที่แนวตั้ง\v
เท่านั้น
ข้อเสนอแนะเล็ก ๆ ที่ฉันควรทำก็คือหลีกเลี่ยงการรวมบรรทัดใหม่ในโทเค็น คุณอาจต้องการใช้ตัวดำเนินการสำรอง%
หรือ%%
เนื่องจากพวกมันถูกออกแบบมาเพื่อจัดการงานประเภทนี้:
grammar Parser {
token TOP {
<headerRow> \n
<valueRow>+ %% \n
}
token headerRow { <.ws>* %% <header> }
token valueRow { <.ws>* %% <value> }
token header { \S+ }
token value { \S+ }
token ws { \h* }
}
ผลลัพธ์ของParser.parse($dat)
สิ่งนี้คือ:
「ID Name Email
1 test test@email.com
321 stan stan@nowhere.net
」
headerRow => 「ID Name Email」
header => 「ID」
header => 「Name」
header => 「Email」
valueRow => 「 1 test test@email.com」
value => 「1」
value => 「test」
value => 「test@email.com」
valueRow => 「 321 stan stan@nowhere.net」
value => 「321」
value => 「stan」
value => 「stan@nowhere.net」
valueRow => 「」
ซึ่งแสดงให้เราเห็นว่าไวยากรณ์แยกวิเคราะห์ทุกอย่างเรียบร้อยแล้ว อย่างไรก็ตามขอให้คุณมุ่งเน้นไปที่ส่วนที่สองของคำถามที่คุณต้องการให้มีอยู่ในตัวแปรให้คุณ ในการทำเช่นนั้นคุณจะต้องระบุคลาสการกระทำที่ง่ายมากสำหรับโครงการนี้ คุณเพียงแค่สร้างคลาสที่เมธอดตรงกับเมธอดของไวยากรณ์ของคุณ (แม้ว่าคลาสที่ง่ายมากเช่นvalue
/ header
ที่ไม่ต้องการการประมวลผลพิเศษนอกเหนือจากการทำให้เป็นสตริงสามารถละเว้นได้) มีวิธีที่สร้างสรรค์ / กะทัดรัดในการจัดการการประมวลผลของคุณ แต่ฉันจะใช้วิธีการพื้นฐานในการแสดงตัวอย่าง นี่คือคลาสของเรา:
class ParserActions {
method headerRow ($/) { ... }
method valueRow ($/) { ... }
method TOP ($/) { ... }
}
แต่ละวิธีมีลายเซ็น($/)
ซึ่งเป็นตัวแปรการจับคู่ regex ดังนั้นตอนนี้มาถามข้อมูลที่เราต้องการจากโทเค็นแต่ละอัน ในแถวส่วนหัวเราต้องการค่าส่วนหัวแต่ละค่าในแถว ดังนั้น:
method headerRow ($/) {
my @headers = $<header>.map: *.Str
make @headers;
}
โทเค็นใด ๆ กับปริมาณที่มันจะได้รับการปฏิบัติเป็นPositional
ดังนั้นเรายังสามารถเข้าถึงการแข่งขันแต่ละส่วนหัวของบุคคลที่มี$<header>[0]
, $<header>[1]
ฯลฯ แต่ผู้ที่มีวัตถุการแข่งขันเพื่อให้เราได้อย่างรวดเร็วเพียงแค่ stringify พวกเขา make
คำสั่งอนุญาตให้ราชสกุลอื่น ๆ ในการเข้าถึงข้อมูลพิเศษนี้ที่เราได้สร้าง
แถวมูลค่าของเราจะมีลักษณะเหมือนกันเพราะ$<value>
โทเค็นเป็นสิ่งที่เราใส่ใจ
method valueRow ($/) {
my @values = $<value>.map: *.Str
make @values;
}
เมื่อเราไปถึงวิธีสุดท้ายเราจะต้องการสร้างอาร์เรย์ด้วยแฮช
method TOP ($/) {
my @entries;
my @headers = $<headerRow>.made;
my @rows = $<valueRow>.map: *.made;
for @rows -> @values {
my %entry = flat @headers Z @values;
@entries.push: %entry;
}
make @entries;
}
ที่นี่คุณสามารถดูวิธีที่เราเข้าถึงสิ่งที่เราดำเนินการheaderRow()
และvalueRow()
: คุณใช้.made
วิธีการ เนื่องจากมีค่าหลายค่าในการรับค่าแต่ละmade
ค่าเราต้องทำแผนที่ (นี่คือสถานการณ์ที่ฉันมักจะเขียนไวยากรณ์ของฉันให้มีเพียงแค่<header><data>
ในไวยากรณ์และกำหนดข้อมูลเป็นหลายแถว แต่นี่คือ ง่ายพอมันไม่ได้แย่เกินไป)
ตอนนี้เรามีส่วนหัวและแถวในสองอาร์เรย์มันเป็นเรื่องของการทำให้พวกเขาเป็นชุดของแฮชซึ่งเราทำในfor
วง การรวมกันflat @x Z @y
ขององค์ประกอบเพียงอย่างเดียวและการมอบหมายแฮชทำในสิ่งที่เราหมายถึง แต่มีวิธีอื่นในการรับอาร์เรย์ในแฮชที่คุณต้องการ
เมื่อคุณทำเสร็จแล้วคุณmake
จะได้มันแล้วมันจะพร้อมใช้งานในmade
การแยก:
say Parser.parse($dat, :actions(ParserActions)).made
-> [{Email => test@email.com, ID => 1, Name => test} {Email => stan@nowhere.net, ID => 321, Name => stan} {}]
เป็นเรื่องธรรมดาที่จะห่อสิ่งเหล่านี้เป็นวิธีเช่น
sub parse-tsv($tsv) {
return Parser.parse($tsv, :actions(ParserActions)).made
}
ด้วยวิธีนี้คุณสามารถพูดได้
my @entries = parse-tsv($dat);
say @entries[0]<Name>; # test
say @entries[1]<Email>; # stan@nowhere.net
Nil
. มันค่อนข้างแห้งแล้งหากมีความคิดเห็นเกิดขึ้นใช่ไหม สำหรับการดีบักให้ดาวน์โหลดคอมม่าถ้าคุณยังไม่ได้ทำและ / หรือดูการรายงานข้อผิดพลาดในแกรมม่าสามารถปรับปรุงได้อย่างไร . คุณได้Nil
เพราะรูปแบบของคุณคิดว่าความหมายย้อนรอย ดูคำตอบของฉันเกี่ยวกับเรื่องนั้น ฉันแนะนำให้คุณหลีกเลี่ยงการย้อนรอย ดูคำตอบของ @ user0721090601 เกี่ยวกับสิ่งนั้น สำหรับการปฏิบัติจริงและความเร็วที่แท้จริงดูคำตอบของ JJ นอกจากนี้คำตอบทั่วไปเบื้องต้นเกี่ยวกับ "ฉันต้องการแยก X กับ Raku ใครช่วยได้บ้าง" .