การจับคู่หลายบรรทัดของ Scala regex พร้อมการมองล่วงหน้าเชิงลบ

ฉันกำลังเขียน DSL โดยใช้ตัวแยกวิเคราะห์ของ Scala ฉันเพิ่งเปลี่ยนคลาสพื้นฐานจาก StandardTokenParsers เป็น JavaTokenParsers เพื่อใช้ประโยชน์จากฟีเจอร์ regex ที่ฉันคิดว่าฉันต้องการสำหรับปริศนาชิ้นสุดท้าย (ดู การแยกวิเคราะห์สตริงหลายบรรทัดที่ใช้ตัวคั่นโดยใช้ scala StandardTokenParser)

สิ่งที่ฉันพยายามทำคือแยกกลุ่มข้อความที่คั่นด้วยอักขระบางตัว ({{ และ }} ในตัวอย่างนี้) บล็อกข้อความนี้สามารถขยายได้หลายบรรทัด สิ่งที่ฉันมีจนถึงตอนนี้คือ:

  def docBlockRE = regex("""(?s)(?!}}).*""".r)
  def docBlock: Parser[DocString] =
      "{{" ~> docBlockRE <~ "}}" ^^ { case str => new DocString(str) }}

โดยที่ DocString เป็นคลาสเคสใน DSL ของฉัน อย่างไรก็ตาม สิ่งนี้ไม่ได้ผล มันจะล้มเหลวถ้าฉันป้อนสิ่งต่อไปนี้:

{{
abc
}}

{{
abc
}}

ฉันไม่แน่ใจว่าทำไมสิ่งนี้ถึงล้มเหลว ถ้าฉันใส่ Deubg wrapper ไว้รอบๆ parser (http://jim-mcbeath.blogspot.com/2011/07/debugging-scala-parser-combinators.html) ฉันได้รับสิ่งต่อไปนี้:

docBlock.apply for token 
   at position 10.2 offset 165 returns [19.1] failure: `}}' expected but end of source found

ถ้าฉันลองใช้บล็อกตัวคั่นเดียวที่มีหลายบรรทัด:

{{
abc
def
}}

จากนั้นมันก็ล้มเหลวในการแยกวิเคราะห์ด้วย:

docBlock.apply for token 
  at position 10.2 offset 165 returns [16.1] failure: `}}' expected but end of source found

ถ้าฉันลบคำสั่ง DOTALL (?s) ฉันสามารถแยกวิเคราะห์บล็อก บรรทัดเดียว ได้หลายบล็อก (ซึ่งไม่ได้ช่วยฉันมากนัก)

มีวิธีใดในการรวม regex หลายบรรทัดเข้ากับ lookahead เชิงลบหรือไม่?

ปัญหาอีกประการหนึ่งที่ฉันมีกับแนวทางนี้คือ ไม่ว่าฉันจะทำอะไรก็ตาม ตัวคั่นปิด ต้อง อยู่ในบรรทัดแยกจากข้อความ มิฉะนั้น ฉันจะได้รับข้อความแสดงข้อผิดพลาดแบบเดียวกับที่เห็นด้านบน มันเกือบจะเหมือนกับว่าการมองล่วงหน้าเชิงลบไม่ได้ผลอย่างที่ฉันคาดหวังไว้


person melston    schedule 16.07.2014    source แหล่งที่มา


คำตอบ (2)


ในบริบท:

scala> val rr = """(?s).*?(?=}})""".r
rr: scala.util.matching.Regex = (?s).*?(?=}})

scala> object X extends JavaTokenParsers {val r: Parser[String] = rr; val b: Parser[String] = "{{" ~>r<~"}}" ^^ { case s => s } }
defined object X

scala> X.parseAll(X.b, """{{ abc
     | def
     | }}""")
res15: X.ParseResult[String] =
[3.3] parsed: abc
def

เพิ่มเติมเพื่อแสดงความแตกต่างในความโลภ:

scala> val rr = """(?s)(.*?)(?=}})""".r.unanchored
rr: scala.util.matching.UnanchoredRegex = (?s)(.*?)(?=}})

scala> def f(s: String) = s match { case rr(x) => x case _ => "(none)" }
f: (s: String)String

scala> f("something }} }}")
res3: String = "something "

scala> val rr = """(?s)(.*)(?=}})""".r.unanchored
rr: scala.util.matching.UnanchoredRegex = (?s)(.*)(?=}})

scala> def f(s: String) = s match { case rr(x) => x case _ => "(none)" }
f: (s: String)String

scala> f("something }} }}")
res4: String = "something }} "

lookahead หมายถึง "ให้แน่ใจว่าสิ่งนี้ตามฉันมา แต่อย่าบริโภคมัน"

มองไปข้างหน้าเชิงลบหมายถึงให้แน่ใจว่ามันจะไม่ตามฉัน

person som-snytt    schedule 16.07.2014
comment
ขอบคุณมาก. ฉันลืมความโลภของ .* ที่กินข้อความทั้งหมดของฉันจนถึง และรวมถึงตัวคั่นปิดด้วย ฉันคิดว่าการมองไปข้างหน้าเชิงลบจะจัดการเรื่องนั้น แต่ก็ไม่เป็นเช่นนั้น การมองไปข้างหน้าแบบไม่สิ้นเปลืองทำให้เกิดความสับสนเล็กน้อย ฉันเดาว่าฉันต้องคิดใหม่ว่าทำไม lookahead เชิงลบ ไม่ได้ ทำงาน และ lookahead เชิงบวกจึงได้ผล - person melston; 16.07.2014

หากต้องการจับคู่ {{the entire bracket}} ให้ใช้ regex นี้:

(?s)\{\{.*?\}\}

ดูการแข่งขันในการสาธิต

หากต้องการจับคู่ {{inside the brackets}} ให้ใช้สิ่งนี้:

(?s)(?<=\{\{).*?(?=\}\})

ดูการแข่งขันในการสาธิต

คำอธิบาย

  • (?s) เปิดใช้งานโหมด DOTALL ช่วยให้จุดตรงกันข้ามเส้น
  • ตัวระบุปริมาณดาวใน .*? ถูกทำให้เป็น "lazy" ด้วย ? เพื่อให้จุดตรงกันเท่าที่จำเป็นเท่านั้น หากไม่มี ? ตัว .* จะจับการจับคู่ที่ยาวที่สุด โดยจับคู่สตริงทั้งหมดก่อน จากนั้นจึงย้อนรอยเท่าที่จำเป็นเพื่อให้โทเค็นถัดไปจับคู่ได้
  • (?<=\{\{) เป็นการมองข้างหลังที่ยืนยันว่าสิ่งที่อยู่ข้างหน้าคือ {{
  • (?=\}\}) คือ lookahead ที่ยืนยันว่าสิ่งต่อไปนี้คือ }}

ข้อมูลอ้างอิง

person zx81    schedule 16.07.2014
comment
ขอบคุณ ฉันจะดูเรื่องนี้ ฉันเคยจัดการกับนิพจน์ทั่วไปค่อนข้างบ่อย ดูเหมือนว่าพวกเขาจะเปลี่ยนแปลงไปมากในช่วง 10 ปีที่ผ่านมา และฉันก็ตามไม่ทัน :) คำตอบนี้และคำตอบของ som-snytt นั้นดีกว่าสิ่งที่ฉันคิดไว้ - person melston; 16.07.2014
comment
เอาล่ะ หากคุณมีคำถาม โปรดแจ้งให้เราทราบ :) - person zx81; 16.07.2014