Многострочное совпадение регулярного выражения Scala с отрицательным просмотром вперед

Я пишу DSL, используя комбинаторы синтаксического анализатора Scala. Недавно я изменил свой базовый класс со StandardTokenParsers на JavaTokenParsers, чтобы воспользоваться функциями регулярных выражений, которые, как мне кажется, мне нужны для последней части головоломки. (см. Анализ многострочной строки с разделителями с использованием scala StandardTokenParser)

Я пытаюсь извлечь блок текста, разделенный некоторыми символами ({{ и }} в этом примере). Этот блок текста может занимать несколько строк. На данный момент у меня есть:

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

где DocString - это класс случая в моем DSL. Однако это не работает. Это не сработает, если я скармливаю ему следующее:

{{
abc
}}

{{
abc
}}

Я не уверен, почему это не удается. Если я поставлю оболочку Deubg, вокруг парсера будет оболочка отладки (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), я смогу проанализировать несколько однострочных блоков (что мне не очень помогает).

Есть ли способ объединить многострочное регулярное выражение с отрицательным просмотром вперед?

Еще одна проблема, с которой я сталкиваюсь с этим подходом, заключается в том, что независимо от того, что я делаю, закрывающий разделитель должен находиться на отдельной строке от текста. В противном случае я получаю то же сообщение об ошибке, что и выше. Это почти похоже на то, что отрицательный прогноз не работает так, как я ожидал.


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 }} "

Предварительный просмотр просто означает «убедитесь, что это следует за мной, но не потребляйте его».

Отрицательный взгляд вперед просто означает, что он не следует за мной.

person som-snytt    schedule 16.07.2014
comment
Огромное спасибо. Я забыл о жадности. *, Которая съедала весь мой текст до и включая закрывающие разделители. Я думал, что негативный взгляд вперед позаботится об этом, но этого не произошло. Непотребный взгляд вперед немного сбивает с толку. Думаю, мне нужно переосмыслить, почему отрицательный взгляд вперед не работает, а положительный - работает. - person melston; 16.07.2014

Чтобы соответствовать {{the entire bracket}}, используйте это регулярное выражение:

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

Смотрите совпадения в демонстрации.

Чтобы соответствовать {{inside the brackets}}, используйте это:

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

Смотрите совпадения в демонстрации.

Объяснение

  • (?s) активирует DOTALL режим, позволяющий точке совпадать по строкам
  • Звездный квантификатор в .*? делается "ленивым" за счет ?, так что точка соответствует ровно столько, сколько необходимо. Без ? .* будет захватывать самое длинное совпадение, сначала совпадая со всей строкой, а затем возвращаясь только настолько, насколько это необходимо, чтобы позволить следующему токену совпадать.
  • (?<=\{\{) - это взгляд назад, который утверждает, что предшествующее {{
  • (?=\}\}) - это прогноз, который утверждает, что нижеследующее }}

Ссылка

person zx81    schedule 16.07.2014
comment
Спасибо. Я посмотрю на это. Раньше я довольно часто обрабатывал регулярные выражения. Они, кажется, сильно изменились за последние 10 лет или около того, и я не успеваю за ними. :) Этот ответ и ответ som-snytt предпочтительнее того, что я придумал. - person melston; 16.07.2014
comment
Хорошо, если у вас есть вопросы, дайте мне знать. :) - person zx81; 16.07.2014