Как написать парсер, который проверяет свои входные данные по предикату и в противном случае терпит неудачу - PullRequest
1 голос
/ 20 сентября 2011

Я хочу написать анализатор, который создает некоторую структуру данных и проверяет ее согласованность, выполняя предикат для нее. Если предикат возвращает false, парсер должен вернуть пользовательский объект Error (в отличие от Failure, поскольку это может быть достигнуто с помощью ^?).

Я ищу оператора синтаксического анализатора, который может этого добиться. Например, предположим, что я хочу проанализировать список целых чисел и проверить, что они различны. Я хотел бы иметь что-то вроде этого:

import util.parsing.combinator.RegexParsers

object MyParser extends RegexParsers {
  val number: Parser[Int] = """\d+""".r ^^ {_.toInt }
  val list = repsep(number, ",") ^!(checkDistinct, "numbers have to be unique")

  def checkDistinct(numbers: List[Int]) = (numbers.length == numbers.distinct.length)
}

^! в приведенном выше коде - это то, что я ищу. Как я могу проверить вывод парсера и вернуть полезное сообщение об ошибке, если оно не проверяется?

Ответы [ 4 ]

3 голосов
/ 20 сентября 2011

Один из способов добиться этого - использовать шаблон Pimp My Library для добавления оператора ^! к Parser[List[T]] (тип возврата repsep).Определите неявный def, затем импортируйте его в область, когда вам нужно его использовать:

class ParserWithMyExtras[T](val parser:Parser[List[T]]){
  def ^!(predicate:List[T]=>Boolean, errorMessage:String) = {...}
}

implicit def augmentParser[T](parser:Parser[List[T]]) = 
  new ParserWithMyExtras(parser)
2 голосов
/ 20 сентября 2011

Parsers.commit преобразует сбой в ошибку.Таким образом, первым шагом будет

commit(p ^?(condition, message))

Однако это даст ошибку, если p выдаст ошибку, что, я полагаю, не то, что вам нужно, вы хотите получить ошибку только тогда, когда p успешен, а затем проверка не пройдена.Поэтому вам лучше сделать

p into {result => commit(success(result) ^? (condition,message))}

Это может показаться довольно надуманным, вы можете также реализовать напрямую, просто скопируйте реализацию ^?заменить сбой с ошибкой.

Наконец, вам, вероятно, следует сделать то, что предложил Дилан, и добавить оператор.Если вы хотите сделать это вне вашей грамматики (Parsers), я думаю, вам понадобится миксин:

trait PimpedParsers { self: Parsers => 
   implicit def ...
}

В противном случае вы не можете легко обратиться к (одиночному) Parser.

1 голос
/ 20 сентября 2011

Вот полная реализация Pimp My Library :

implicit def validatingParsers[T](parser: Parser[T]) = new {
  def ^!(predicate: T => Boolean, error: => String) = Parser { in =>
    parser(in) match {
      case s @Success(result, sin) => predicate(result) match {
        case true => s
        case false => Error(error, sin)   // <--
      }
      case e @NoSuccess(_, _) => e
    }
  }
}

Новый оператор ^! преобразует парсер слева в новый парсер, который применяет предикат.

Одна важная вещь, которую стоит отметить, это sin на линии, отмеченной <--.Поскольку ошибка, которая в конечном итоге возвращается библиотекой синтаксического анализатора Scala, является последней в входной позиции, крайне важно передать sin в этой строке вместо in, поскольку sin представляет точку, в которой внутренний анализаторзавершил собственный синтаксический анализудалось, если мы добрались до этой линии).

0 голосов
/ 20 сентября 2011

^? принимает генератор сообщений об ошибках, commit преобразует Failure в Error:

val list = commit {
  repsep(number, ",") ^? (
    { case numbers if checkDistinct(numbers) => true},
    _ => "numbers have to be unique" )
}
...