Reputation: 3855
Parsing a string of the form Value[,Value]+
can be easily accomplished via rep1sep(Value, ',')
. Is there a way to achieve rep1sep functionality when the Value parser is dependent on the previously parsed Values in the repetition? For example, enforcing a requirement that each value must be unique?
The standard technique for dependent parsers is flatMap, but I am having trouble getting that working. Here's one such attempt:
def Values(soFar: Set[Value]): Parser[Set[Value]] =
Value(soFar) flatMap { v => (',' ~> Values(soFar + v)).?.map { _ getOrElse soFar } }
def Value(soFar: Set[Value]): Parser[Value] =
Num+ flatMap { v => if (soFar.contains(v)) failure("%d already appears".format(v)) else success(v) }
Generically, I need a form of rep1sep where the parser argument is a function from Seq[A]
to Parser[A]
:
def rep1sepDependent(rep: Seq[A] => Parser[A], sep: Parser[Any]): Seq[A] = ???
Note: I realize the use case is questionable here and that validating uniqueness is better handled post parsing. I've encountered this problem while using the SBT parsing combinators for the purpose of tab completion -- specifically, I'd like to present completion options for key/value pairs for only those keys that the user has not already entered. See this parser for a full example and buildable SBT project.
Upvotes: 4
Views: 671
Reputation: 7012
Here is a solution for selecting some items with assisted completion, avoiding duplicated items:
def select1(items: Iterable[String], separator: Parser[_] = Space) =
token(separator ~> StringBasic.examples(FixedSetExamples(items)))
def selectSome(items: Seq[String], separator: Parser[_] = Space): Parser[Seq[String]] = {
select1(items, separator).flatMap { v ⇒
val remaining = items filter { _ != v }
if (remaining.size == 0)
success(v :: Nil)
else
selectSome(remaining).?.map(v +: _.getOrElse(Seq()))
}
}
Example of use:
val myTask = inputTask[Unit]("Print selected numbers")
myTask := {
val numbers = selectSome(Seq("One", "Two", "Three", "Four")).parsed
numbers.foreach{ println _ }
}
Tested with SBT 0.13.9.
Upvotes: 0
Reputation: 139028
The following isn't quite as generic as your rep1sepDependent
, but it works:
def rep1sepUnique[T](p: => Parser[T], q: => Parser[Any]) = {
def checkIfSeen(seen: Set[T]): Parser[Set[T]] = q ~> p >> (v =>
if (seen(v)) failure("Duplicate: %s".format(v)) else checkIfSeen(seen + v)
) | success(seen)
p >> (v => checkIfSeen(Set(v)))
}
For example:
import scala.util.parsing.combinator._
object parseUniqueWords extends RegexParsers {
def rep1sepUnique[T](p: => Parser[T], q: => Parser[Any]) = {
def checkIfSeen(seen: Set[T]): Parser[Set[T]] = q ~> p >> (v =>
if (seen(v)) failure("Duplicate: %s".format(v)) else checkIfSeen(seen + v)
) | success(seen)
p >> (v => checkIfSeen(Set(v)))
}
def apply(s: String) = parseAll(rep1sepUnique("\\w+".r, ","), s)
}
Which gives us:
scala> parseUniqueWords("aaa,bb,c")
res0: parseUniqueWords.ParseResult[Set[String]] = [1.9] parsed: Set(aaa, bb, c)
scala> parseUniqueWords("aaa,bb,aaa")
res1: parseUniqueWords.ParseResult[Set[String]] =
[1.11] failure: Duplicate: aaa
aaa,bb,aaa
^
Which is what we want.
Upvotes: 4