Доступ к данным соответствия регулярному выражению в Scala Parser

Мне интересно, можно ли получить MatchData, сгенерированную из соответствующего регулярного выражения в грамматике ниже.

object DateParser extends JavaTokenParsers {

    ....

    val dateLiteral = """(\d{4}[-/])?(\d\d[-/])?(\d\d)""".r ^^ {
        ... get MatchData
    }
}

Один из вариантов, конечно, - снова выполнить сопоставление внутри блока, но поскольку RegexParser уже выполнил сопоставление, я надеюсь, что он передает MatchData в блок или сохраняет его?


person BefittingTheorem    schedule 29.11.2009    source источник


Ответы (4)


Вот неявное определение, которое преобразует ваш Regex в Parser:

  /** A parser that matches a regex string */
  implicit def regex(r: Regex): Parser[String] = new Parser[String] {
    def apply(in: Input) = {
      val source = in.source
      val offset = in.offset
      val start = handleWhiteSpace(source, offset)
      (r findPrefixMatchOf (source.subSequence(start, source.length))) match {
        case Some(matched) =>
          Success(source.subSequence(start, start + matched.end).toString, 
                  in.drop(start + matched.end - offset))
        case None =>
          Failure("string matching regex `"+r+"' expected but `"+in.first+"' found", in.drop(start - offset))
      }
    }
  }

Просто адаптируйте его:

object X extends RegexParsers {
  /** A parser that matches a regex string and returns the Match */
  def regexMatch(r: Regex): Parser[Regex.Match] = new Parser[Regex.Match] {
    def apply(in: Input) = {
      val source = in.source
      val offset = in.offset
      val start = handleWhiteSpace(source, offset)
      (r findPrefixMatchOf (source.subSequence(start, source.length))) match {
        case Some(matched) =>
          Success(matched,
                  in.drop(start + matched.end - offset))
        case None =>
          Failure("string matching regex `"+r+"' expected but `"+in.first+"' found", in.drop(start - offset))
      }
    }
  }
  val t = regexMatch("""(\d\d)/(\d\d)/(\d\d\d\d)""".r) ^^ { case m => (m.group(1), m.group(2), m.group(3)) }
}

Пример:

scala> X.parseAll(X.t, "23/03/1971")
res8: X.ParseResult[(String, String, String)] = [1.11] parsed: (23,03,1971)
person Daniel C. Sobral    schedule 29.11.2009
comment
Как ни странно, почему такая функциональность не является частью реализации стандартного (библиотечного) класса? Это выглядит довольно полезным, но каждый пользователь должен реализовать это самостоятельно ... - person Dmitry Bespalov; 21.03.2014
comment
@DmitryBespalov Можно просто снова применить шаблон для извлечения групп, и я бы предпочел использовать грамматику, чем более сложные правила регулярных выражений. Так что да, это может быть полезно, но в этом нет необходимости, и у раздутой библиотеки есть серьезные недостатки. - person Daniel C. Sobral; 22.03.2014
comment
Известно, что лексирование с регулярным выражением более эффективно, чем с обычным синтаксическим анализом. Между тем, Lexer с RegexParsers дает object Lexer inherits conflicting member type Elem in trait Scanners and RegexParsers, и я не могу не расширить RegexParsers, потому что он определяет функцию handleWhiteSpace. - person Valentin Tihomirov; 04.01.2016

Нет, ты не можешь этого сделать. Если вы посмотрите на определение парсера, используемого при преобразовании регулярного выражения в парсер, он отбрасывает весь контекст и просто возвращает полностью совпавшую строку:

http://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R<~7_7_final/src/library/scala/util/parsing/combinator/RegexParsers.scala?view=markup#L55 < / а>

Однако у вас есть несколько других вариантов:

  • разбейте ваш парсер на несколько более мелких парсеров (для токенов, которые вы действительно хотите извлечь)
  • определить настраиваемый синтаксический анализатор, который извлекает нужные вам значения и возвращает объект домена вместо строки

Первый будет выглядеть как

val separator = "-" | "/"
  val year = ("""\d{4}"""r) <~ separator
  val month = ("""\d\d"""r) <~ separator
  val day = """\d\d"""r

  val date = ((year?) ~ (month?) ~ day) map {
    case year ~ month ~ day =>
      (year.getOrElse("2009"), month.getOrElse("11"), day)
  }

<~ означает «требовать эти два токена вместе, но дать мне только результат первого.

~ означает "потребовать эти два токена вместе и связать их вместе в объект, совместимый с шаблоном ~".

? означает, что синтаксический анализатор является необязательным и вернет параметр.

Бит .getOrElse предоставляет значение по умолчанию, когда синтаксический анализатор не определил значение.

person David Winslow    schedule 29.11.2009
comment
Спасибо, Дэвид, хорошее решение. Я собираюсь использовать собственный синтаксический анализатор, так как он делает определение грамматики более читабельным. - person BefittingTheorem; 30.11.2009
comment
Теперь, когда я думаю об этом, настраиваемый парсер тоже более правильный. Каждый отдельный синтаксический анализатор регулярных выражений допускает ведущие пробелы, поэтому опубликованный мной код также будет соответствовать таким строкам, как 1999-02-28. - person David Winslow; 30.11.2009

Когда Regex используется в экземпляре RegexParsers, неявное def regex (Regex): Parser [String] в RegexParsers используется для применения этого Regex к входным данным. Экземпляр Match, полученный после успешного применения RE на текущем входе, используется для построения Success в методе regex (), но используется только его "конечное" значение, поэтому любые захваченные под-совпадения отбрасываются к тому времени, когда этот метод возвращается.

В нынешнем виде (в источнике 2.7, на который я смотрел), я полагаю, вам не повезло.

person Randall Schulz    schedule 29.11.2009

Я столкнулся с аналогичной проблемой, используя scala 2.8.1 и пытаясь проанализировать ввод формы «имя: значение» с использованием класса RegexParsers:

package scalucene.query

import scala.util.matching.Regex
import scala.util.parsing.combinator._

object QueryParser extends RegexParsers {
  override def skipWhitespace = false

  private def quoted = regex(new Regex("\"[^\"]+"))
  private def colon = regex(new Regex(":"))
  private def word = regex(new Regex("\\w+"))
  private def fielded = (regex(new Regex("[^:]+")) <~ colon) ~ word
  private def term = (fielded | word | quoted)

  def parseItem(str: String) = parse(term, str)
}

Кажется, что вы можете получить сопоставленные группы после разбора следующим образом:

QueryParser.parseItem("nameExample:valueExample") match {
  case QueryParser.Success(result:scala.util.parsing.combinator.Parsers$$tilde, _) => {
      println("Name: " + result.productElement(0) + " value: " + result.productElement(1))
  }
}
person Max    schedule 06.03.2011