diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala index a6d5474aa..bc1229ad1 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/complete/Parser.scala @@ -127,6 +127,9 @@ sealed trait RichParser[A] { /** Applies the original parser, applies `f` to the result to get the next parser, and applies that parser and uses its result for the overall result. */ def flatMap[B](f: A => Parser[B]): Parser[B] + + /** Applied both the original parser and `b` on the same input and returns the results produced by each parser */ + def combinedWith(b: Parser[A]): Parser[Seq[A]] } /** Contains Parser implementation helper methods not typically needed for using parsers. */ @@ -310,6 +313,11 @@ trait ParserMain { def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg) def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString) def flatMap[B](f: A => Parser[B]) = bindParser(a, f) + def combinedWith(b: Parser[A]): Parser[Seq[A]] = + if (a.valid) + if (b.valid) new CombiningParser(a, b) else a.map(Seq(_)) + else + b.map(Seq(_)) } implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c) @@ -633,6 +641,24 @@ private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidPar def completions(level: Int) = a.completions(level) ++ b.completions(level) override def toString = "(" + a + " || " + b + ")" } +private final class CombiningParser[T](a: Parser[T], b: Parser[T]) extends ValidParser[Seq[T]] { + lazy val result: Option[Seq[T]] = (a.result.toSeq ++ b.result.toSeq) match { case Seq() => None; case seq => Some(seq) } + def completions(level: Int) = a.completions(level) ++ b.completions(level) + def derive(i: Char) = + (a.valid, b.valid) match { + case (true, true) => new CombiningParser(a derive i, b derive i) + case (true, false) => a derive i map (Seq(_)) + case (false, true) => b derive i map (Seq(_)) + case (false, false) => new Invalid(mkFailure("No valid parser available.")) + } + def resultEmpty = + (a.resultEmpty, b.resultEmpty) match { + case (Value(ra), Value(rb)) => Value(Seq(ra, rb)) + case (Value(ra), _) => Value(Seq(ra)) + case (_, Value(rb)) => Value(Seq(rb)) + case _ => Value(Nil) + } +} private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String]) extends ValidParser[Seq[T]] { assert(a.nonEmpty) lazy val resultEmpty: Result[Seq[T]] = diff --git a/internal/util-complete/src/test/scala/ParserTest.scala b/internal/util-complete/src/test/scala/ParserTest.scala index 1db99b513..34e35efbe 100644 --- a/internal/util-complete/src/test/scala/ParserTest.scala +++ b/internal/util-complete/src/test/scala/ParserTest.scala @@ -108,6 +108,15 @@ object ParserTest extends Properties("Completing Parser") { property("repeatDep requires at least one token") = !matches(repeat, "") property("repeatDep accepts one token") = matches(repeat, colors.toSeq.head) property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" ")) + property("combined parser gives completion of both parsers") = { + val prefix = "fix" + val p1Suffixes = Set("", "ated", "ation") + val p2Suffixes = Set("es", "ed") + val p1: Parser[String] = p1Suffixes map (suffix => (prefix + suffix): Parser[String]) reduce (_ | _) + val p2: Parser[String] = p2Suffixes map (suffix => (prefix + suffix): Parser[String]) reduce (_ | _) + val suggestions: Set[Completion] = p1Suffixes ++ p2Suffixes map (new Suggestion(_)) + checkAll(prefix, p1 combinedWith p2, Completions(suggestions)) + } } object ParserExample { val ws = charClass(_.isWhitespace).+