diff --git a/README.md b/README.md
index df187b4d..054ed7ac 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,16 @@ This library is now community-maintained. If you are interested in helping pleas
As of Scala 2.11, this library is a separate jar that can be omitted from Scala projects that do not use Parser Combinators.
+#### New: completion parsers
+Mixing-in the `CompletionSupport` trait enables completion support for a grammar (use `RegexCompletionSupport` for `RegexParsers`):
+
+```scala
+object MyParsers extends RegexParsers with RegexCompletionSupport
+```
+
+Parsers are thus 'augmented' with a `completions` method which returns possible entry completions for a certain input. This can be used to elaborate as-you-type completions menus or tab-completion experiences, and is e.g. easy to plug with readline to implement a console application.
+A set of additional operators also allow overriding completions and specifying ordering and grouping properties for completions.
+
## Documentation
* [Latest version](http://www.scala-lang.org/files/archive/api/2.11.x/scala-parser-combinators/)
diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala
new file mode 100644
index 00000000..45419165
--- /dev/null
+++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala
@@ -0,0 +1,893 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import scala.annotation.tailrec
+import scala.util.parsing.combinator.Parsers
+import scala.util.parsing.input.Positional
+
+/** `CompletionSupport` adds completion capability to parsers.
+ *
+ * When mixed-in, this trait extends
+ * the [[scala.util.parsing.combinator.Parsers.Parser]] type with the abstract method
+ * [[scala.util.parsing.combinator.completion.CompletionSupport.Parser#completions]]
+ * which returns a instance of [[scala.util.parsing.combinator.completion.CompletionTypes.Completions]]
+ * for a certain input.
+ *
+ * Combinators are overloaded to cover the additional completion aspect, so that no change is required in the grammar.
+ *
+ * Note that the derived trait [[scala.util.parsing.combinator.completion.RegexCompletionSupport]] can be mixed-in
+ * with `RegexParsers` to automatically obtain completion behavior for string literals.
+ *
+ * A set of additional operators allow defining completions and specifying structural properties of completions
+ * (tag, score, kind, etc.) for a `Parser`.
+ *
+ * @author Jonas Chapuis
+ */
+trait CompletionSupport extends Parsers with CompletionTypes {
+ def Parser[T](f: Input => ParseResult[T], c: Input => Completions) = new Parser[T] {
+ def apply(in: Input) = f(in)
+ def completions(in: Input) = c(in)
+ }
+
+ /** The root class of completion parsers, overloading the `Parser` class.
+ * Completion parsers are functions from the Input type to ParseResult, with the
+ * addition of a `completions` function from the Input type to an instance of `Completions`
+ */
+ abstract class Parser[+T] extends super.Parser[T] {
+
+ def append[U >: T](p0: => Parser[U]): Parser[U] = {
+ lazy val p = p0
+ Parser(
+ in => super.append(p)(in),
+ in => {
+ val thisCompletions = this.completions(in)
+ lazy val combinedCompletions = thisCompletions | p.completions(in)
+ this(in) match {
+ case Success(_, rest) =>
+ // only return any completions if they start at the last position, otherwise it can behave badly e.g. with fuzzy matching
+ if (combinedCompletions.position < rest.pos) Completions.empty
+ else combinedCompletions
+ case Failure(_, rest) => combinedCompletions
+ case Error(_, _) =>
+ thisCompletions // avoids backtracking completions in the case of error, e.g. when using the ~! operator
+ }
+ }
+ )
+ }
+
+ /** An unspecified method that defines the possible completions for this parser
+ *
+ * @param in the input
+ * @return an instance of [[scala.util.parsing.combinator.completion.CompletionTypes.Completions]]
+ */
+ def completions(in: Input): Completions
+
+ /** An operator to specify completions of a parser
+ * @param completions possible completions for this parser
+ * @return a `Parser` that upon invocation of the `completions` method returns the passed completions
+ */
+ def %>(completions: Elems*): Parser[T] =
+ %>(completions.map(el => Completion(el)))
+
+ /** An operator to specify completion of a parser
+ * @param completion completion for this parser
+ * @return a `Parser` that upon invocation of the `completions` method returns the passed completion
+ */
+ def %>(completion: Completion): Parser[T] =
+ %>(Set(completion))
+
+ /** An operator to specify completions of a parser
+ * @param completions possible completions for this parser
+ * @return a `Parser` that upon invocation of the `completions` method returns the passed completions
+ */
+ def %>(completions: Iterable[Completion]): Parser[T] =
+ Parser(this, in => {
+ this(in) match {
+ case Failure(_, rest) if rest.atEnd =>
+ Completions(rest.pos, CompletionSet(completions))
+ case _ => Completions.empty
+ }
+ })
+
+ /** An operator to specify completions of a parser
+ * @param completioner function of input to completions
+ * @return a `Parser` that upon invocation of the `completions` method will invoke the passed function
+ */
+ def %>(completioner: Input => Completions): Parser[T] =
+ Parser(this, completioner)
+
+ /** Limits completions to the top `n` completions ordered by their score
+ * @param n the limit
+ * @return wrapper `Parser` instance limiting the number of completions
+ */
+ def topCompletions(n: Int): Parser[T] =
+ Parser(
+ this,
+ in => {
+ val completions = this.completions(in)
+ Completions(completions.position,
+ completions.sets.mapValues(s =>
+ CompletionSet(s.tag, s.completions.toList.sortBy(_.score).reverse.take(n).toSet)))
+ }
+ )
+
+ /** An operator to specify the completion tag of a parser (empty tag by default)
+ * @param tag the completion tag (to be used e.g. to structure a completion menu)
+ * @return wrapper `Parser` instance specifying the completion tag
+ */
+ def %(tag: String): Parser[T] =
+ Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), None, None, None))
+
+ /** An operator to specify the completions tag score of a parser (0 by default)
+ * @param tagScore the completion tag score (to be used e.g. to order sections in a completion menu)
+ * @return wrapper `Parser` instance specifying the completion tag score
+ */
+ def %(tagScore: Int): Parser[T] =
+ Parser(this, in => updateCompletionsTag(this.completions(in), None, Some(tagScore), None, None))
+
+ /** An operator to specify the completion tag and score of a parser
+ * @param tag the completion tag
+ * @param tagScore the completion tag score
+ * @return wrapper `Parser` instance specifying the completion tag
+ */
+ def %(tag: String, tagScore: Int): Parser[T] =
+ Parser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), None, None))
+
+ /** An operator to specify the completion tag, score and description of a parser
+ * @param tag the completion tag
+ * @param tagScore the completion tag score
+ * @param tagDescription the completion tag description
+ * @return wrapper `Parser` instance specifying completion tag
+ */
+ def %(tag: String, tagScore: Int, tagDescription: String): Parser[T] =
+ Parser(this,
+ in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), None))
+
+ /** An operator to specify the completion tag, score, description and kind of a parser
+ * @param tag the completion tag
+ * @param tagScore the completion tag score
+ * @param tagDescription the completion tag description
+ * @param tagKind the completion tag kind
+ * @return wrapper `Parser` instance specifying completion tag
+ */
+ def %(tag: String, tagScore: Int, tagDescription: String, tagKind: String): Parser[T] =
+ Parser(
+ this,
+ in =>
+ updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), Some(tagKind)))
+
+ /** An operator to specify the completion tag
+ * @param tag the completion tag
+ * @return wrapper `Parser` instance specifying completion tag
+ */
+ def %(tag: CompletionTag): Parser[T] =
+ Parser(
+ this,
+ in => updateCompletionsTag(this.completions(in), Some(tag.label), Some(tag.score), tag.description, tag.kind))
+
+ /** An operator to specify the completion tag description of a parser (empty by default)
+ * @param tagDescription the completion description (to be used e.g. to add information to a completion entry)
+ * @return wrapper `Parser` instance specifying the completion description
+ */
+ def %?(tagDescription: String): Parser[T] =
+ Parser(this, in => updateCompletionsTag(this.completions(in), None, None, Some(tagDescription), None))
+
+ /** An operator to specify the completion tag kind of a parser (empty by default)
+ * @param tagKind the completion tag kind (to be used e.g. to specify the visual style for a completion tag in the menu)
+ * @return wrapper `Parser` instance specifying the completion tag kind
+ */
+ def %%(tagKind: String): Parser[T] =
+ Parser(this, in => updateCompletionsTag(this.completions(in), None, None, None, Some(tagKind)))
+
+ /** An operator to specify the kind for completions of a parser (empty by default)
+ * @param kind the completion kind (to be used e.g. to specify the visual style for a completion entry in the menu)
+ * @return wrapper `Parser` instance specifying the completion kind
+ */
+ def %-%(kind: String): Parser[T] =
+ Parser(this, in => updateCompletions(this.completions(in), Some(kind)))
+
+ def flatMap[U](f: T => Parser[U]): Parser[U] =
+ Parser(super.flatMap(f), completions)
+
+ override def map[U](f: T => U): Parser[U] =
+ Parser(super.map(f), completions)
+
+ override def filter(p: T => Boolean): Parser[T] = withFilter(p)
+
+ override def withFilter(p: T => Boolean): Parser[T] =
+ Parser(super.withFilter(p), completions)
+
+ private def seqCompletions[U](in: Input, other: => Parser[U]): Completions = {
+ lazy val thisCompletions = this.completions(in)
+ this(in) match {
+ case Success(_, rest) =>
+ thisCompletions | other.completions(rest)
+ case NoSuccess(_, _) =>
+ thisCompletions
+ }
+ }
+
+ private def updateCompletionsSets(completions: Completions, updateSet: CompletionSet => CompletionSet) = {
+ Completions(completions.position,
+ completions.sets.values
+ .map(updateSet)
+ .map(s => s.tag.label -> s)
+ .toMap)
+ }
+
+ private def updateCompletionsTag(completions: Completions,
+ newTagLabel: Option[String],
+ newTagScore: Option[Int],
+ newTagDescription: Option[String],
+ newTagKind: Option[String]) = {
+ def updateSet(existingSet: CompletionSet) =
+ CompletionSet(existingSet.tag.update(newTagLabel, newTagScore, newTagDescription, newTagKind),
+ existingSet.completions)
+
+ updateCompletionsSets(completions, updateSet)
+ }
+
+ private def updateCompletions(completions: Completions, newCompletionKind: Option[String]) = {
+ def updateSet(existingSet: CompletionSet) =
+ CompletionSet(existingSet.tag, existingSet.completions.map(e => e.updateKind(newCompletionKind)))
+ updateCompletionsSets(completions, updateSet)
+ }
+
+ /** A parser combinator for sequential composition.
+ *
+ * `p ~ q` succeeds if `p` succeeds and `q` succeeds on the input left over by `p`.
+ *
+ * @param q a parser that will be executed after `p` (this parser)
+ * succeeds -- evaluated at most once, and only when necessary.
+ * @return a `Parser` that -- on success -- returns a `~` (like a `Pair`,
+ * but easier to pattern match on) that contains the result of `p` and
+ * that of `q`. The resulting parser fails if either `p` or `q` fails.
+ */
+ def ~[U](q: => Parser[U]): Parser[~[T, U]] = {
+ lazy val p = q
+ Parser(super.~(q), in => seqCompletions(in, p))
+ }.named("~")
+
+ /** A parser combinator for sequential composition which keeps only the right result.
+ *
+ * `p ~> q` succeeds if `p` succeeds and `q` succeeds on the input left over by `p`.
+ *
+ * @param q a parser that will be executed after `p` (this parser)
+ * succeeds -- evaluated at most once, and only when necessary.
+ * @return a `Parser` that -- on success -- returns the result of `q`.
+ */
+ def ~>[U](q: => Parser[U]): Parser[U] = {
+ lazy val p = q
+ Parser(super.~>(q), in => seqCompletions(in, p))
+ }.named("~>")
+
+ /** A parser combinator for sequential composition which keeps only the left result.
+ *
+ * `p <~ q` succeeds if `p` succeeds and `q` succeeds on the input
+ * left over by `p`.
+ *
+ * @note <~ has lower operator precedence than ~ or ~>.
+ *
+ * @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary
+ * @return a `Parser` that -- on success -- returns the result of `p`.
+ */
+ def <~[U](q: => Parser[U]): Parser[T] = {
+ lazy val p = q
+ Parser(super.<~(q), in => seqCompletions(in, p))
+ }.named("<~")
+
+ /** A parser combinator for non-back-tracking sequential composition.
+ *
+ * `p ~! q` succeeds if `p` succeeds and `q` succeeds on the input left over by `p`.
+ * In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator).
+ *
+ * @param q a parser that will be executed after `p` (this parser) succeeds
+ * @return a `Parser` that -- on success -- returns a `~` (like a Pair, but easier to pattern match on)
+ * that contains the result of `p` and that of `q`.
+ * The resulting parser fails if either `p` or `q` fails, this failure is fatal.
+ */
+ def ~: Parser[~[T, U]] = {
+ lazy val p = q
+ Parser(super.~!(q), in => seqCompletions(in, p))
+ }.named("<~")
+
+ /** A parser combinator for non-back-tracking sequential composition which only keeps the right result.
+ *
+ * `p ~>! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`.
+ * In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator).
+ *
+ * @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary
+ * @return a `Parser` that -- on success -- reutrns the result of `q`.
+ * The resulting parser fails if either `p` or `q` fails, this failure is fatal.
+ */
+ def ~>: Parser[U] = {
+ lazy val p = q
+ Parser(super.~>!(q), in => seqCompletions(in, p))
+ }.named("~>!")
+
+ /** A parser combinator for non-back-tracking sequential composition which only keeps the left result.
+ *
+ * `p <~! q` succeeds if `p` succeds and `q` succeds on the input left over by `p`.
+ * In case of failure, no back-tracking is performed (in an earlier parser produced by the `|` combinator).
+ *
+ * @param q a parser that will be executed after `p` (this parser) succeeds -- evaluated at most once, and only when necessary
+ * @return a `Parser` that -- on success -- reutrns the result of `p`.
+ * The resulting parser fails if either `p` or `q` fails, this failure is fatal.
+ */
+ def <~: Parser[T] = {
+ lazy val p = q
+ Parser(super.<~!(q), in => seqCompletions(in, p))
+ }.named("<~!")
+
+ /** A parser combinator for alternative composition.
+ *
+ * `p | q` succeeds if `p` succeeds or `q` succeeds.
+ * Note that `q` is only tried if `p`s failure is non-fatal (i.e., back-tracking is allowed).
+ *
+ * @param q a parser that will be executed if `p` (this parser) fails (and allows back-tracking)
+ * @return a `Parser` that returns the result of the first parser to succeed (out of `p` and `q`)
+ * The resulting parser succeeds if (and only if)
+ * - `p` succeeds, ''or''
+ * - if `p` fails allowing back-tracking and `q` succeeds.
+ */
+ def |[U >: T](q: => Parser[U]): Parser[U] =
+ append(q).named("|")
+
+ /** A parser combinator for alternative with longest match composition.
+ *
+ * `p ||| q` succeeds if `p` succeeds or `q` succeeds.
+ * If `p` and `q` both succeed, the parser that consumed the most characters accepts.
+ *
+ * @param q a parser that accepts if p consumes less characters. -- evaluated at most once, and only when necessary
+ * @return a `Parser` that returns the result of the parser consuming the most characters (out of `p` and `q`).
+ */
+ def |||[U >: T](q: => Parser[U]): Parser[U] = {
+ lazy val p = q
+ Parser(super.|||(q), in => this.completions(in) | p.completions(in))
+ }
+
+ /** A parser combinator for function application.
+ *
+ * `p ^^ f` succeeds if `p` succeeds; it returns `f` applied to the result of `p`.
+ *
+ * @param f a function that will be applied to this parser's result (see `map` in `ParseResult`).
+ * @return a parser that has the same behaviour as the current parser, but whose result is
+ * transformed by `f`.
+ */
+ override def ^^[U](f: T => U): Parser[U] =
+ Parser(super.^^(f), completions).named(toString + "^^")
+
+ /** A parser combinator that changes a successful result into the specified value.
+ *
+ * `p ^^^ v` succeeds if `p` succeeds; discards its result, and returns `v` instead.
+ *
+ * @param v The new result for the parser, evaluated at most once (if `p` succeeds), not evaluated at all if `p` fails.
+ * @return a parser that has the same behaviour as the current parser, but whose successful result is `v`
+ */
+ override def ^^^[U](v: => U): Parser[U] = {
+ Parser(super.^^^(v), completions)
+ }.named(toString + "^^^")
+
+ /** A parser combinator for partial function application.
+ *
+ * `p ^? (f, error)` succeeds if `p` succeeds AND `f` is defined at the result of `p`;
+ * in that case, it returns `f` applied to the result of `p`. If `f` is not applicable,
+ * error(the result of `p`) should explain why.
+ *
+ * @param f a partial function that will be applied to this parser's result
+ * (see `mapPartial` in `ParseResult`).
+ * @param error a function that takes the same argument as `f` and produces an error message
+ * to explain why `f` wasn't applicable
+ * @return a parser that succeeds if the current parser succeeds and `f` is applicable
+ * to the result. If so, the result will be transformed by `f`.
+ */
+ override def ^?[U](f: PartialFunction[T, U], error: T => String): Parser[U] =
+ Parser(super.^?(f, error), completions).named(toString + "^?")
+
+ /** A parser combinator for partial function application.
+ *
+ * `p ^? f` succeeds if `p` succeeds AND `f` is defined at the result of `p`;
+ * in that case, it returns `f` applied to the result of `p`.
+ *
+ * @param f a partial function that will be applied to this parser's result
+ * (see `mapPartial` in `ParseResult`).
+ * @return a parser that succeeds if the current parser succeeds and `f` is applicable
+ * to the result. If so, the result will be transformed by `f`.
+ */
+ override def ^?[U](f: PartialFunction[T, U]): Parser[U] =
+ Parser(super.^?(f), completions)
+
+ /** A parser combinator that parameterizes a subsequent parser with the
+ * result of this one.
+ *
+ * Use this combinator when a parser depends on the result of a previous
+ * parser. `p` should be a function that takes the result from the first
+ * parser and returns the second parser.
+ *
+ * `p into fq` (with `fq` typically `{x => q}`) first applies `p`, and
+ * then, if `p` successfully returned result `r`, applies `fq(r)` to the
+ * rest of the input.
+ *
+ * ''From: G. Hutton. Higher-order functions for parsing. J. Funct. Program., 2(3):323--343, 1992.''
+ *
+ * @example {{{
+ * def perlRE = "m" ~> (".".r into (separator => """[^%s]*""".format(separator).r <~ separator))
+ * }}}
+ *
+ * @param fq a function that, given the result from this parser, returns
+ * the second parser to be applied
+ * @return a parser that succeeds if this parser succeeds (with result `x`)
+ * and if then `fq(x)` succeeds
+ */
+ def into[U](fq: T => Parser[U]): Parser[U] =
+ Parser(super.into(fq), in => {
+ this(in) match {
+ case Success(result, next) => fq(result).completions(next)
+ case _: NoSuccess => this.completions(in)
+ }
+ })
+
+ /** Returns `into(fq)`. */
+ def >>[U](fq: T => Parser[U]) = into(fq)
+
+ /** Returns a parser that repeatedly parses what this parser parses.
+ *
+ * @return rep(this)
+ */
+ override def * = rep(this)
+
+ /** Returns a parser that repeatedly parses what this parser parses,
+ * interleaved with the `sep` parser. The `sep` parser specifies how
+ * the results parsed by this parser should be combined.
+ *
+ * @return chainl1(this, sep)
+ */
+ def *[U >: T](sep: => Parser[(U, U) => U]) = chainl1(this, sep)
+
+ /** Returns a parser that repeatedly (at least once) parses what this parser parses.
+ *
+ * @return rep1(this)
+ */
+ override def + = rep1(this)
+
+ /** Returns a parser that optionally parses what this parser parses.
+ *
+ * @return opt(this)
+ */
+ override def ? = opt(this)
+
+ /** Changes the failure message produced by a parser.
+ *
+ * This doesn't change the behavior of a parser on neither
+ * success nor error, just on failure. The semantics are
+ * slightly different than those obtained by doing `| failure(msg)`,
+ * in that the message produced by this method will always
+ * replace the message produced, which is not guaranteed
+ * by that idiom.
+ *
+ * For example, parser `p` below will always produce the
+ * designated failure message, while `q` will not produce
+ * it if `sign` is parsed but `number` is not.
+ *
+ * {{{
+ * def p = sign.? ~ number withFailureMessage "Number expected!"
+ * def q = sign.? ~ number | failure("Number expected!")
+ * }}}
+ *
+ * @param msg The message that will replace the default failure message.
+ * @return A parser with the same properties and different failure message.
+ */
+ override def withFailureMessage(msg: String) =
+ Parser(super.withFailureMessage(msg), completions)
+
+ /** Changes the failure message produced by a parser.
+ *
+ * This doesn't change the behavior of a parser on neither
+ * success nor error, just on failure. The semantics are
+ * slightly different than those obtained by doing `| failure(msg)`,
+ * in that the message produced by this method will always
+ * replace the message produced, which is not guaranteed
+ * by that idiom.
+ *
+ * For example, parser `p` below will always produce the
+ * designated failure message, while `q` will not produce
+ * it if `sign` is parsed but `number` is not.
+ *
+ * {{{
+ * def p = sign.? ~ number withFailureMessage "Number expected!"
+ * def q = sign.? ~ number | failure("Number expected!")
+ * }}}
+ *
+ * @param msg The message that will replace the default failure message.
+ * @return A parser with the same properties and different failure message.
+ */
+ override def withErrorMessage(msg: String) =
+ Parser(super.withErrorMessage(msg), completions)
+
+ }
+
+ /** Wrap a parser so that its failures become errors (the `|` combinator
+ * will give up as soon as it encounters an error, on failure it simply
+ * tries the next alternative).
+ */
+ def commit[T](p: => Parser[T]): Parser[T] =
+ Parser(super.commit(p), p.completions)
+
+ /** A parser matching input elements that satisfy a given predicate.
+ *
+ * `elem(kind, p)` succeeds if the input starts with an element `e` for which `p(e)` is true.
+ *
+ * @param kind The element kind, used for error messages
+ * @param p A predicate that determines which elements match.
+ * @param completions Possible alternatives (for completion)
+ * @return
+ */
+ def elem(kind: String, p: Elem => Boolean, completions: Set[Elem] = Set()): Parser[Elem] =
+ acceptIf(p, completions)(inEl => kind + " expected")
+
+ /** A parser that matches only the given element `e`.
+ *
+ * `elem(e)` succeeds if the input starts with an element `e`.
+ *
+ * @param e the `Elem` that must be the next piece of input for the returned parser to succeed
+ * @return a `Parser` that succeeds if `e` is the next available input (and returns it).
+ */
+ override def elem(e: Elem): Parser[Elem] = accept(e)
+
+ /** A parser that matches only the given element `e`.
+ *
+ * The method is implicit so that elements can automatically be lifted to their parsers.
+ * For example, when parsing `Token`s, `Identifier("new")` (which is a `Token`) can be used directly,
+ * instead of first creating a `Parser` using `accept(Identifier("new"))`.
+ *
+ * @param e the `Elem` that must be the next piece of input for the returned parser to succeed
+ * @return a `tParser` that succeeds if `e` is the next available input.
+ */
+ override implicit def accept(e: Elem): Parser[Elem] =
+ acceptIf(_ == e, Set(e))("'" + e + "' expected but " + _ + " found")
+
+ /** A parser that matches only the given list of element `es`.
+ *
+ * `accept(es)` succeeds if the input subsequently provides the elements in the list `es`.
+ *
+ * @param es the list of expected elements
+ * @return a Parser that recognizes a specified list of elements
+ */
+ override def accept[ES <% List[Elem]](es: ES): Parser[List[Elem]] =
+ acceptSeq(es)
+
+ /** The parser that matches an element in the domain of the partial function `f`.
+ *
+ * If `f` is defined on the first element in the input, `f` is applied
+ * to it to produce this parser's result.
+ *
+ * Example: The parser `accept("name", {case Identifier(n) => Name(n)})`
+ * accepts an `Identifier(n)` and returns a `Name(n)`
+ *
+ * @param expected a description of the kind of element this parser expects (for error messages)
+ * @param f a partial function that determines when this parser is successful and what its output is
+ * @param completions Possible alternatives (for completion)
+ * @return A parser that succeeds if `f` is applicable to the first element of the input,
+ * applying `f` to it to produce the result.
+ */
+ def accept[U](expected: String, f: PartialFunction[Elem, U], completions: Set[Elem] = Set()): Parser[U] =
+ acceptMatch(expected, f, completions.map(Completion(_)))
+
+ /** A parser matching input elements that satisfy a given predicate.
+ *
+ * `acceptIf(p)(el => "Unexpected "+el)` succeeds if the input starts with an element `e` for which `p(e)` is true.
+ *
+ * @param err A function from the received element into an error message.
+ * @param p A predicate that determines which elements match.
+ * @param completions Possible completions
+ * @return A parser for elements satisfying p(e).
+ */
+ def acceptIf(p: Elem => Boolean, completions: Set[Elem])(err: Elem => String): Parser[Elem] = {
+ lazy val completionSet =
+ if (completions.isEmpty)
+ None
+ else
+ Some(CompletionSet(completions.map(c => Completion(c))))
+ Parser(
+ super.acceptIf(p)(err),
+ in =>
+ completionSet match {
+ case None => Completions.empty
+ case Some(c) =>
+ super.acceptIf(p)(err)(in) match {
+ case Success(_, _) => Completions.empty
+ case _ => Completions(in.pos, c)
+ }
+ }
+ )
+ }
+
+ def acceptMatch[U](expected: String, f: PartialFunction[Elem, U], completions: Set[Completion]): Parser[U] = {
+ lazy val completionSet =
+ if (completions.nonEmpty)
+ Some(CompletionSet(CompletionTag(expected), completions))
+ else None
+ Parser(
+ super.acceptMatch(expected, f),
+ in =>
+ completionSet match {
+ case None => Completions.empty
+ case Some(c) =>
+ super.acceptMatch(expected, f)(in) match {
+ case Success(_, _) => Completions.empty
+ case _ => Completions(in.pos, c)
+ }
+ }
+ ).named(expected)
+ }
+
+ /** A parser that matches only the given [[scala.collection.Iterable]] collection of elements `es`.
+ *
+ * `acceptSeq(es)` succeeds if the input subsequently provides the elements in the iterable `es`.
+ *
+ * @param es the list of expected elements
+ * @return a Parser that recognizes a specified list of elements
+ */
+ override def acceptSeq[ES <% Iterable[Elem]](es: ES): Parser[List[Elem]] =
+ Parser(super.acceptSeq(es),
+ in =>
+ es.tail
+ .foldLeft(accept(es.head))((a, b) => a ~> accept(b))
+ .completions(in))
+
+ /** A parser that always fails.
+ *
+ * @param msg The error message describing the failure.
+ * @return A parser that always fails with the specified error message.
+ */
+ override def failure(msg: String): Parser[Nothing] =
+ Parser(super.failure(msg), _ => Completions.empty)
+
+ /** A parser that always succeeds.
+ *
+ * @param v The result for the parser
+ * @return A parser that always succeeds, with the given result `v`
+ */
+ override def success[T](v: T): Parser[T] =
+ Parser(super.success(v), _ => Completions.empty)
+
+ /** A parser that results in an error.
+ *
+ * @param msg The error message describing the failure.
+ * @return A parser that always fails with the specified error message.
+ */
+ override def err(msg: String): Parser[Nothing] =
+ Parser(super.err(msg), _ => Completions.empty)
+
+ /** A helper method that turns a `Parser` into one that will
+ * print debugging information to stdout before and after
+ * being applied.
+ */
+ def log[T](p: => Parser[T])(name: String): Parser[T] =
+ Parser(super.log(p)(name), p.completions)
+
+ /** A parser generator for repetitions.
+ *
+ * `rep(p)` repeatedly uses `p` to parse the input until `p` fails
+ * (the result is a List of the consecutive results of `p`).
+ *
+ * @param p a `Parser` that is to be applied successively to the input
+ * @return A parser that returns a list of results produced by repeatedly applying `p` to the input.
+ */
+ def rep[T](p: => Parser[T]): Parser[List[T]] =
+ rep1(p) | success(List())
+
+ /** A parser generator for interleaved repetitions.
+ *
+ * `repsep(p, q)` repeatedly uses `p` interleaved with `q` to parse the input, until `p` fails.
+ * (The result is a `List` of the results of `p`.)
+ *
+ * Example: `repsep(term, ",")` parses a comma-separated list of term's, yielding a list of these terms.
+ *
+ * @param p a `Parser` that is to be applied successively to the input
+ * @param q a `Parser` that parses the elements that separate the elements parsed by `p`
+ * @return A parser that returns a list of results produced by repeatedly applying `p` (interleaved with `q`) to the input.
+ * The results of `p` are collected in a list. The results of `q` are discarded.
+ */
+ def repsep[T](p: => Parser[T], q: => Parser[Any]): Parser[List[T]] =
+ rep1sep(p, q) | success(List())
+
+ /** A parser generator for non-empty repetitions.
+ *
+ * `rep1(p)` repeatedly uses `p` to parse the input until `p` fails -- `p` must succeed at least
+ * once (the result is a `List` of the consecutive results of `p`)
+ *
+ * @param p a `Parser` that is to be applied successively to the input
+ * @return A parser that returns a list of results produced by repeatedly applying `p` to the input
+ * (and that only succeeds if `p` matches at least once).
+ */
+ def rep1[T](p: => Parser[T]): Parser[List[T]] =
+ rep1(p, p)
+
+ /** A parser generator for non-empty repetitions.
+ *
+ * `rep1(f, p)` first uses `f` (which must succeed) and then repeatedly
+ * uses `p` to parse the input until `p` fails
+ * (the result is a `List` of the consecutive results of `f` and `p`)
+ *
+ * @param first a `Parser` that parses the first piece of input
+ * @param p0 a `Parser` that is to be applied successively to the rest of the input (if any) -- evaluated at most once, and only when necessary
+ * @return A parser that returns a list of results produced by first applying `f` and then
+ * repeatedly `p` to the input (it only succeeds if `f` matches).
+ */
+ def rep1[T](first: => Parser[T], p0: => Parser[T]): Parser[List[T]] = {
+ lazy val p = p0 // lazy argument
+ Parser(
+ super.rep1(first, p0),
+ in => {
+ def continue(in: Input): Completions = {
+ val currentCompletions = p.completions(in)
+ p(in) match {
+ case Success(_, rest) => currentCompletions | continue(rest)
+ case NoSuccess(_, _) => currentCompletions
+ }
+ }
+ val firstCompletions = first.completions(in)
+ first(in) match {
+ case Success(_, rest) => firstCompletions | continue(rest)
+ case NoSuccess(_, _) => firstCompletions
+ }
+ }
+ )
+ }
+
+ /** A parser generator for a specified number of repetitions.
+ *
+ * `repN(n, p)` uses `p` exactly `n` time to parse the input
+ * (the result is a `List` of the `n` consecutive results of `p`).
+ *
+ * @param p0 a `Parser` that is to be applied successively to the input
+ * @param num the exact number of times `p` must succeed
+ * @return A parser that returns a list of results produced by repeatedly applying `p` to the input
+ * (and that only succeeds if `p` matches exactly `n` times).
+ */
+ def repN[T](num: Int, p0: => Parser[T]): Parser[List[T]] = {
+ lazy val p = p0 // lazy argument
+ if (num == 0) { success(Nil) } else {
+ Parser(
+ super.repN(num, p0),
+ in => {
+ var parsedCount = 0
+ def completions(in0: Input): Completions =
+ if (parsedCount == num) {
+ Completions.empty
+ } else {
+ val currentCompletions = p.completions(in0)
+ p(in0) match {
+ case Success(_, rest) => parsedCount += 1; currentCompletions | completions(rest)
+ case ns: NoSuccess => currentCompletions
+ }
+ }
+
+ val result = completions(in)
+ if (parsedCount < num) result else Completions.empty
+ }
+ )
+ }
+ }
+
+ /** A parser generator for non-empty repetitions.
+ *
+ * `rep1sep(p, q)` repeatedly applies `p` interleaved with `q` to parse the
+ * input, until `p` fails. The parser `p` must succeed at least once.
+ *
+ * @param p a `Parser` that is to be applied successively to the input
+ * @param q a `Parser` that parses the elements that separate the elements parsed by `p`
+ * (interleaved with `q`)
+ * @return A parser that returns a list of results produced by repeatedly applying `p` to the input
+ * (and that only succeeds if `p` matches at least once).
+ * The results of `p` are collected in a list. The results of `q` are discarded.
+ */
+ def rep1sep[T](p: => Parser[T], q: => Parser[Any]): Parser[List[T]] =
+ p ~ rep(q ~> p) ^^ { case x ~ y => x :: y }
+
+ /** A parser generator that, roughly, generalises the rep1sep generator so
+ * that `q`, which parses the separator, produces a left-associative
+ * function that combines the elements it separates.
+ *
+ * ''From: J. Fokker. Functional parsers. In J. Jeuring and E. Meijer, editors, Advanced Functional Programming,
+ * volume 925 of Lecture Notes in Computer Science, pages 1--23. Springer, 1995.''
+ *
+ * @param p a parser that parses the elements
+ * @param q a parser that parses the token(s) separating the elements, yielding a left-associative function that
+ * combines two elements into one
+ */
+ def chainl1[T](p: => Parser[T], q: => Parser[(T, T) => T]): Parser[T] =
+ chainl1(p, p, q)
+
+ /** A parser generator that, roughly, generalises the `rep1sep` generator
+ * so that `q`, which parses the separator, produces a left-associative
+ * function that combines the elements it separates.
+ *
+ * @param first a parser that parses the first element
+ * @param p a parser that parses the subsequent elements
+ * @param q a parser that parses the token(s) separating the elements,
+ * yielding a left-associative function that combines two elements
+ * into one
+ */
+ def chainl1[T, U](first: => Parser[T], p: => Parser[U], q: => Parser[(T, U) => T]): Parser[T] =
+ first ~ rep(q ~ p) ^^ {
+ case x ~ xs =>
+ xs.foldLeft(x: T) { case (a, f ~ b) => f(a, b) } // x's type annotation is needed to deal with changed type inference due to SI-5189
+ }
+
+ /** A parser generator that generalises the `rep1sep` generator so that `q`,
+ * which parses the separator, produces a right-associative function that
+ * combines the elements it separates. Additionally, the right-most (last)
+ * element and the left-most combining function have to be supplied.
+ *
+ * rep1sep(p: Parser[T], q) corresponds to chainr1(p, q ^^ cons, cons, Nil) (where val cons = (x: T, y: List[T]) => x :: y)
+ *
+ * @param p a parser that parses the elements
+ * @param q a parser that parses the token(s) separating the elements, yielding a right-associative function that
+ * combines two elements into one
+ * @param combine the "last" (left-most) combination function to be applied
+ * @param first the "first" (right-most) element to be combined
+ */
+ def chainr1[T, U](p: => Parser[T], q: => Parser[(T, U) => U], combine: (T, U) => U, first: U): Parser[U] =
+ p ~ rep(q ~ p) ^^ {
+ case x ~ xs =>
+ (new ~(combine, x) :: xs).foldRight(first) { case (f ~ a, b) => f(a, b) }
+ }
+
+ /** A parser generator for optional sub-phrases.
+ *
+ * `opt(p)` is a parser that returns `Some(x)` if `p` returns `x` and `None` if `p` fails.
+ *
+ * @param p A `Parser` that is tried on the input
+ * @return a `Parser` that always succeeds: either with the result provided by `p` or
+ * with the empty result
+ */
+ def opt[T](p: => Parser[T]): Parser[Option[T]] =
+ p ^^ (x => Some(x)) | success(None)
+
+ /** Wrap a parser so that its failures and errors become success and
+ * vice versa -- it never consumes any input.
+ */
+ def not[T](p: => Parser[T]): Parser[Unit] =
+ Parser(super.not(p), _ => Completions.empty)
+
+ /** A parser generator for guard expressions. The resulting parser will
+ * fail or succeed just like the one given as parameter but it will not
+ * consume any input.
+ *
+ * @param p a `Parser` that is to be applied to the input
+ * @return A parser that returns success if and only if `p` succeeds but
+ * never consumes any input
+ */
+ def guard[T](p: => Parser[T]): Parser[T] =
+ Parser(super.guard(p), p.completions)
+
+ /** `positioned` decorates a parser's result with the start position of the
+ * input it consumed.
+ *
+ * @param p a `Parser` whose result conforms to `Positional`.
+ * @return A parser that has the same behaviour as `p`, but which marks its
+ * result with the start position of the input it consumed,
+ * if it didn't already have a position.
+ */
+ def positioned[T <: Positional](p: => Parser[T]): Parser[T] =
+ Parser(super.positioned(p), p.completions)
+
+ /** A parser generator delimiting whole phrases (i.e. programs).
+ *
+ * `phrase(p)` succeeds if `p` succeeds and no input is left over after `p`.
+ *
+ * @param p the parser that must consume all input for the resulting parser
+ * to succeed.
+ * @return a parser that has the same result as `p`, but that only succeeds
+ * if `p` consumed all the input.
+ */
+ def phrase[T](p: Parser[T]) =
+ Parser(super.phrase(p), p.completions)
+}
diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala
new file mode 100644
index 00000000..10302d14
--- /dev/null
+++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala
@@ -0,0 +1,223 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import scala.util.parsing.input.{NoPosition, Position}
+
+/** Collection of data types allowing definition of structured parser completions.
+ * A `Completions` instance can contain multiple `CompletionSet`s instances. A `CompletionSet` provides a set of
+ * `Completion` entries and is tagged with a `CompletionTag`.
+ *
+ * Sets allow structuring the completion entries into groups, each group tagged with a `label` (plus optional
+ * `description` and `kind`, the latter allowing e.g. encoding visual attributes for the set).
+ * Sets also feature a score, which defines the order between sets within the `Completions` instance.
+ *
+ * Each `Completion` entry within a set has a `value`, a `score` and a `kind`:
+ * the score allows ordering the entries within a set, and the kind can e.g. be used to assign a representation style
+ * for a particular completion entry.
+ *
+ * Note that specifying tags and sets is optional: if no tag is specified upon creation,
+ * `Completions` instances create a unique default set with an empty tag.
+ *
+ * @author Jonas Chapuis
+ */
+trait CompletionTypes {
+ type Elem
+
+ val DefaultCompletionTag = ""
+ val DefaultCompletionScore = 0
+
+ /** Tag defining identification and attributes of a set of completion entries
+ * @param label tag label
+ * @param score tag score (the higher the better, 0 by default)
+ * @param description tag description (optional) - can be used for additional information e.g. for a tooltip
+ * @param kind tag kind (optional) - can be used e.g. to define visual style
+ */
+ case class CompletionTag(label: String, score: Int, description: Option[String], kind: Option[String]) {
+ def update(newTag: Option[String],
+ newScore: Option[Int],
+ newDescription: Option[String],
+ newKind: Option[String]) =
+ copy(
+ label = newTag.getOrElse(label),
+ score = newScore.getOrElse(score),
+ description = newDescription.map(Some(_)).getOrElse(description),
+ kind = newKind.map(Some(_)).getOrElse(kind)
+ )
+
+ override def toString: String = label
+ }
+
+ case object CompletionTag {
+ val Default =
+ CompletionTag(DefaultCompletionTag, DefaultCompletionScore, None, None)
+ def apply(label: String): CompletionTag =
+ CompletionTag(label, DefaultCompletionScore, None, None)
+ def apply(label: String, score: Int): CompletionTag =
+ CompletionTag(label, score, None, None)
+ }
+
+ /** Set of related completion entries
+ * @param tag set tag
+ * @param completions set of unique completion entries
+ */
+ case class CompletionSet(tag: CompletionTag, completions: Set[Completion]) {
+ require(completions.nonEmpty, "empty completions set")
+ def label: String = tag.label
+ def score: Int = tag.score
+ def description: Option[String] = tag.description
+ def kind: Option[String] = tag.kind
+ def completionStrings: Seq[String] =
+ completions.toSeq.sorted.map(_.value.toString)
+ }
+
+ case object CompletionSet {
+ def apply(tag: String, el: Elem): CompletionSet =
+ CompletionSet(CompletionTag(tag), Set(Completion(el)))
+
+ def apply(tag: String, elems: Elems): CompletionSet =
+ CompletionSet(CompletionTag(tag), Set(Completion(elems)))
+
+ def apply(tag: String, completion: Completion): CompletionSet =
+ CompletionSet(CompletionTag(tag), Set(completion))
+
+ def apply(tag: String, completions: Iterable[Completion]): CompletionSet =
+ CompletionSet(CompletionTag(tag), completions.toSet)
+
+ def apply(completions: Iterable[Completion]): CompletionSet =
+ CompletionSet(CompletionTag.Default, completions.toSet)
+
+ def apply(completions: Completion*): CompletionSet =
+ CompletionSet(CompletionTag.Default, completions.toSet)
+
+ def apply(el: Elem): CompletionSet =
+ CompletionSet(CompletionTag.Default, Set(Completion(el)))
+
+ def apply(completions: Traversable[Elems]): CompletionSet =
+ CompletionSet(CompletionTag.Default, completions.map(Completion(_)).toSet)
+ }
+
+ type Elems = Seq[Elem]
+
+ /** Completion entry
+ * @param value entry value (e.g. string literal)
+ * @param score entry score (defines the order of entries within a set, the higher the better)
+ * @param kind entry kind (e.g. visual style)
+ */
+ case class Completion(value: Elems, score: Int = DefaultCompletionScore, kind: Option[String] = None) {
+ require(value.nonEmpty, "empty completion")
+ def updateKind(newKind: Option[String]) =
+ copy(kind = newKind.map(Some(_)).getOrElse(kind))
+ }
+ case object Completion {
+ def apply(el: Elem): Completion = Completion(Seq(el))
+ implicit def orderingByScoreAndThenAlphabetical: Ordering[Completion] =
+ Ordering.by(c => (-c.score, c.value.toString))
+ }
+
+ /** Result of parser completion, listing the possible entry alternatives at a certain input position
+ * @param position position in the input where completion entries apply
+ * @param sets completion entries, grouped per tag
+ */
+ case class Completions(position: Position, sets: Map[String, CompletionSet]) {
+ def isEmpty: Boolean = sets.isEmpty
+ def nonEmpty: Boolean = !isEmpty
+ def setWithTag(tag: String): Option[CompletionSet] = sets.get(tag)
+ def allSets: Iterable[CompletionSet] = sets.values
+ def allCompletions: Iterable[Completion] = allSets.flatMap(_.completions)
+ def defaultSet: Option[CompletionSet] = sets.get("")
+
+ private def unionSets(left: CompletionSet, right: CompletionSet): CompletionSet = {
+ def offsetCompletions(set: CompletionSet) = {
+ val isOffsetRequired =
+ set.completions.map(_.score).exists(_ < set.score)
+ if (isOffsetRequired)
+ set.completions.map(c => Completion(c.value, set.score + c.score, c.kind))
+ else set.completions
+ }
+ CompletionSet(
+ CompletionTag(left.tag.label, left.score.min(right.score), left.description, left.kind.orElse(right.kind)),
+ offsetCompletions(left) ++ offsetCompletions(right)
+ )
+ }
+
+ private def mergeCompletions(other: Completions) = {
+ val overlappingSetTags = sets.keySet.intersect(other.sets.keySet)
+ val unions =
+ overlappingSetTags.map(name => (sets(name), other.sets(name))).map {
+ case (left, right) => unionSets(left, right)
+ }
+ val leftExclusive = sets.keySet.diff(overlappingSetTags).map(sets(_))
+ val rightExclusive =
+ other.sets.keySet.diff(overlappingSetTags).map(other.sets(_))
+ Completions(position,
+ (unions ++ leftExclusive ++ rightExclusive)
+ .map(s => s.tag.label -> s)
+ .toMap)
+ }
+
+ def |(other: Completions): Completions = {
+ other match {
+ case Completions.empty => this
+ case _ =>
+ other.position match {
+ case otherPos if otherPos < position => this
+ case otherPos if otherPos == position => mergeCompletions(other)
+ case _ => other
+ }
+ }
+ }
+
+ def completionStrings: Seq[String] =
+ sets.values.toSeq
+ .sortBy(_.score)
+ .reverse
+ .flatMap(_.completionStrings)
+ .toList
+
+ def takeTop(count: Int): Completions = {
+ val allEntries = allSets
+ .flatMap(s => s.completions.map((_, s.tag)))
+ .toList
+ val sortedEntries =
+ allEntries
+ .sortBy {
+ case (Completion(_, score, kind), CompletionTag(_, tagScore, _, _)) =>
+ (tagScore, score)
+ }
+ .reverse
+ .take(count)
+ val regroupedSets = sortedEntries
+ .groupBy { case (_, tag) => tag }
+ .map {
+ case (groupTag, completions) =>
+ CompletionSet(groupTag, completions.map(_._1).toSet)
+ }
+ copy(sets = regroupedSets.map(s => (s.tag.label, s)).toMap)
+ }
+
+ def setsScoredWithMaxCompletion(): Completions = {
+ Completions(
+ position,
+ sets.mapValues(s => CompletionSet(s.tag.copy(score = s.completions.map(_.score).max), s.completions)))
+ }
+ }
+
+ case object Completions {
+ def apply(position: Position, completionSet: CompletionSet): Completions =
+ Completions(position, Map(completionSet.tag.label -> completionSet))
+ def apply(position: Position, completions: Traversable[Elems]): Completions =
+ Completions(position, CompletionSet(completions))
+ def apply(completionSet: CompletionSet): Completions =
+ Completions(NoPosition, completionSet)
+ def apply(completionSets: Iterable[CompletionSet]): Completions =
+ Completions(NoPosition, completionSets.map(s => s.tag.label -> s).toMap)
+
+ val empty = Completions(NoPosition, Map[String, CompletionSet]())
+ }
+
+}
diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala
new file mode 100644
index 00000000..8fbbc955
--- /dev/null
+++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala
@@ -0,0 +1,83 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+import scala.util.matching.Regex
+import scala.util.parsing.combinator.RegexParsers
+import scala.util.parsing.input.{CharSequenceReader, OffsetPosition, Positional, Reader}
+
+/** This component extends `RegexParsers` with completion capability. In particular,
+ * it provides completions for the `literal` parser.
+ * Note that completions for the `regex` parser are undefined by default and can be specified
+ * with the `%>` operator.
+ *
+ * @author Jonas Chapuis
+ */
+trait RegexCompletionSupport extends RegexParsers with CompletionSupport {
+ protected val areLiteralsCaseSensitive = false
+
+ protected def dropWhiteSpace(input: Input): Input =
+ input.drop(handleWhiteSpace(input.source, input.offset) - input.offset)
+
+ protected def handleWhiteSpace(input: Input): Int =
+ handleWhiteSpace(input.source, input.offset)
+
+ protected def findMatchOffsets(s: String, in: Input): (Int, Int) = {
+ val source = in.source
+ val offset = in.offset
+ val start = handleWhiteSpace(source, offset)
+ var literalPos = 0
+ var sourcePos = start
+ def charsEqual(a: Char, b: Char) =
+ if (areLiteralsCaseSensitive) a == b else a.toLower == b.toLower
+ while (literalPos < s.length && sourcePos < source.length && charsEqual(s.charAt(literalPos),
+ source.charAt(sourcePos))) {
+ literalPos += 1
+ sourcePos += 1
+ }
+ (literalPos, sourcePos)
+ }
+
+ abstract override implicit def literal(s: String): Parser[String] =
+ Parser[String](
+ super.literal(s),
+ (in: Input) => {
+ lazy val literalCompletion =
+ Completions(OffsetPosition(in.source, handleWhiteSpace(in)), CompletionSet(Completion(s)))
+ val (literalOffset, sourceOffset) = findMatchOffsets(s, in)
+ lazy val inputAtEnd = sourceOffset == in.source.length
+ literalOffset match {
+ case 0 if inputAtEnd =>
+ literalCompletion // whitespace, free entry possible
+ case someOffset
+ if inputAtEnd & someOffset > 0 & someOffset < s.length => // partially entered literal, we are at the end
+ literalCompletion
+ case _ => Completions.empty
+ }
+ }
+ )
+
+ abstract override implicit def regex(r: Regex): Parser[String] =
+ Parser(super.regex(r), _ => Completions.empty)
+
+ override def positioned[T <: Positional](p: => Parser[T]): Parser[T] = {
+ lazy val q = p
+ Parser[T](super.positioned(p), in => q.completions(in))
+ }
+
+ /** Returns completions for read `in` with parser `p`. */
+ def complete[T](p: Parser[T], in: Reader[Char]): Completions =
+ p.completions(in)
+
+ /** Returns completions for character sequence `in` with parser `p`. */
+ def complete[T](p: Parser[T], in: CharSequence): Completions =
+ p.completions(new CharSequenceReader(in))
+
+ /** Returns flattened string completions for character sequence `in` with parser `p`. */
+ def completeString[T](p: Parser[T], input: String): Seq[String] =
+ complete(p, input).completionStrings
+
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAcceptAndElemTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAcceptAndElemTest.scala
new file mode 100644
index 00000000..dca1edc0
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAcceptAndElemTest.scala
@@ -0,0 +1,82 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.syntactical.StandardTokenParsers
+
+class CompletionForAcceptAndElemTest {
+
+ object TestParser extends StandardTokenParsers with CompletionSupport
+ import TestParser.lexical._
+
+ @Test
+ def elemCompletesToPassedCompletions(): Unit = {
+ // Arrange
+ val tokens = Set[Token](NumericLit("1"), NumericLit("2"), NumericLit("3"))
+ val parser =
+ TestParser.elem("test", _ => true, completions = tokens)
+
+ // Act
+ val result = parser.completions(new Scanner(""))
+
+ // Assert
+ Assert.assertArrayEquals(tokens.toArray[AnyRef], result.allCompletions.map(_.value.head).toArray[AnyRef])
+ }
+
+ @Test
+ def acceptElemCompletesToElem(): Unit = {
+ // Arrange
+ val elem = NumericLit("1")
+ val parser = TestParser.elem(elem)
+
+ // Act
+ val result = parser.completions(new Scanner(""))
+
+ // Assert
+ Assert.assertEquals(elem, headToken(result.allCompletions))
+ }
+
+ @Test
+ def acceptElemListCompletesToNextInList(): Unit = {
+ // Arrange
+ val one = NumericLit("1")
+ val two = NumericLit("2")
+ val three = NumericLit("3")
+ val seq = List(one, two, three)
+ val parser = TestParser.accept(seq)
+
+ // Act
+ val result1 = parser.completions(new Scanner(""))
+ val result2 = parser.completions(new Scanner("1"))
+ val result3 = parser.completions(new Scanner("1 2"))
+ val emptyResult = parser.completions(new Scanner("1 2 3"))
+
+ // Assert
+ Assert.assertEquals(one, headToken(result1.allCompletions))
+ Assert.assertEquals(two, headToken(result2.allCompletions))
+ Assert.assertEquals(three, headToken(result3.allCompletions))
+ Assert.assertTrue(emptyResult.allCompletions.isEmpty)
+ }
+
+ @Test
+ def acceptWithPartialFunctionCompletesToPassedCompletions(): Unit = {
+ // Arrange
+ case class Number(n: Int)
+ val tokens = Set[Token](NumericLit("1"), NumericLit("2"), NumericLit("3"))
+ val parser = TestParser.accept("number", {case NumericLit(n) => Number(n.toInt)}, tokens)
+
+ // Act
+ val result = parser.completions(new Scanner(""))
+
+ // Assert
+ Assert.assertArrayEquals(tokens.toArray[AnyRef], result.allCompletions.map(_.value.head).toArray[AnyRef])
+ }
+
+ def headToken(completions: Iterable[TestParser.Completion]) = completions.map(_.value).head.head
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala
new file mode 100644
index 00000000..437accf8
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala
@@ -0,0 +1,35 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionForAlternativesTest {
+ val left = "left"
+ val right = "right"
+ val common = "common"
+
+ object TestParser extends Parsers with RegexCompletionSupport {
+ val alternativesWithCommonFirstParser = common ~ left | common ~! right
+ val alternativesWithCommonPrefix = (common+left) | common ~ right
+ }
+
+ @Test
+ def emptyCompletesToCommon =
+ Assert.assertEquals(Seq(common), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, ""))
+
+ @Test
+ def commonCompletesToLeftAndRight =
+ Assert.assertEquals(Seq(left, right), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, common))
+
+ @Test
+ def commonPrefixCompletesToRightSinceCompletionPositionsAreDifferent =
+ Assert.assertEquals(Seq(right), TestParser.completeString(TestParser.alternativesWithCommonPrefix, common))
+
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForChainTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForChainTest.scala
new file mode 100644
index 00000000..953a0447
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForChainTest.scala
@@ -0,0 +1,41 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.RegexParsers
+
+class CompletionForChainTest {
+ val repeated = "rep"
+ val separator = ","
+ object TestParser extends RegexParsers with RegexCompletionSupport {
+ val chainlParser = literal(repeated) * (separator ^^ (_ => (a: String, b: String) => a))
+ val chainrParser =
+ chainr1(literal(repeated), separator ^^ (_ => (a: String, b: String) => a), (a: String, b: String) => a, "")
+ }
+
+ @Test
+ def repeaterCompletesToParserAndSeparatorAlternatively(): Unit = chainTest(TestParser.chainlParser)
+
+ @Test
+ def chainr1CompletesToParserAndSeparatorAlternatively(): Unit =
+ chainTest(TestParser.chainrParser)
+
+ def chainTest[T](parser: TestParser.Parser[T]) = {
+ val resultRep = TestParser.completeString(parser, "")
+ val resultSep = TestParser.completeString(parser, repeated)
+ val resultRep2 = TestParser.completeString(parser, s"$repeated,")
+ val resultSep2 = TestParser.completeString(parser, s"$repeated,$repeated")
+
+ // Assert
+ Assert.assertEquals(resultRep.head, repeated)
+ Assert.assertEquals(resultSep.head, separator)
+ Assert.assertEquals(resultRep2.head, repeated)
+ Assert.assertEquals(resultSep2.head, separator)
+ }
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForIntoTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForIntoTest.scala
new file mode 100644
index 00000000..84388062
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForIntoTest.scala
@@ -0,0 +1,38 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.RegexParsers
+
+class CompletionForIntoTest {
+ val animal = "animal"
+ val machine = "machine"
+ val bear = "bear"
+ val lion = "lion"
+
+ object TestParser extends RegexParsers with RegexCompletionSupport {
+ val animalParser = bear | lion
+ val machineParser = "plane" | "car"
+ val test = (animal | machine) >> { kind: String =>
+ if (kind == animal) animalParser else machineParser
+ }
+ }
+
+ @Test
+ def intoParserWithoutSuccessCompletesToParser(): Unit = {
+ val completions = TestParser.completeString(TestParser.test, "")
+ Assert.assertEquals(Seq(animal, machine), completions)
+ }
+
+ @Test
+ def intoParserWithSuccessCompletesResultingParser(): Unit = {
+ val completions = TestParser.completeString(TestParser.test, animal)
+ Assert.assertEquals(Seq(bear, lion), completions)
+ }
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala
new file mode 100644
index 00000000..84b4b773
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala
@@ -0,0 +1,74 @@
+/* *\
+** scala-parser-combinators completion fork **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+** Author: jonas.chapuis@nexthink.com **
+\* */
+package scala.util.parsing.combinator.completion
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionForLiteralTest {
+ val someLiteral = "literal"
+ val otherLiteralWithSamePrefix = "litOther"
+ val someLiteralPrefix = "lit"
+
+ object Parser extends Parsers with RegexCompletionSupport {
+ val literal: Parser[String] = someLiteral
+
+ val combination = someLiteral | otherLiteralWithSamePrefix
+ }
+
+ @Test
+ def prefixCompletesToLiteral = {
+ val completion = Parser.complete(Parser.literal, " " + someLiteralPrefix)
+ assertEquals(2, completion.position.column)
+ assertEquals(Seq(someLiteral), completion.completionStrings)
+ }
+
+ @Test
+ def prefixCombinationCompletesToBothAlternatives = {
+ val completion =
+ Parser.completeString(Parser.combination, someLiteralPrefix)
+ assertEquals(Seq(otherLiteralWithSamePrefix, someLiteral), completion)
+ }
+
+ @Test
+ def partialOtherCompletesToOther = {
+ val completion = Parser.completeString(
+ Parser.combination,
+ someLiteralPrefix + otherLiteralWithSamePrefix
+ .stripPrefix(someLiteralPrefix)
+ .head)
+ assertEquals(Seq(otherLiteralWithSamePrefix), completion)
+ }
+
+ @Test
+ def whitespaceCompletesToLiteral = {
+ val completion =
+ Parser.complete(Parser.literal, List.fill(2)(" ").mkString)
+ assertEquals(3, completion.position.column)
+ assertEquals(Seq(someLiteral), completion.completionStrings)
+ }
+
+ @Test
+ def emptyCompletesToLiteral = {
+ val completion = Parser.complete(Parser.literal, "")
+ assertEquals(1, completion.position.column)
+ assertEquals(Seq(someLiteral), completion.completionStrings)
+ }
+
+ @Test
+ def otherCompletesToNothing =
+ assertEquals(
+ Map(),
+ Parser.complete(Parser.literal, otherLiteralWithSamePrefix).sets)
+
+ @Test
+ def completeLiteralCompletesToEmpty =
+ assertTrue(Parser.complete(Parser.literal, someLiteral).sets.isEmpty)
+
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala
new file mode 100644
index 00000000..c5023cef
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala
@@ -0,0 +1,41 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+package scala.util.parsing.combinator.completion
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionForLongestMatchTest {
+ val foo = "foo"
+ val bar = "bar"
+
+ object Parsers extends Parsers with RegexCompletionSupport {
+ val samePrefix = foo ||| foo ~ bar
+ val constrainedAndOpenAlternatives = foo ~ bar ||| (".{5,}".r %> Completion("sample string longer than 5 char"))
+ }
+
+ @Test
+ def normallyProblematicallyOrderedAlternativesParseCorrectly = {
+ assertTrue(Parsers.parseAll(Parsers.samePrefix, foo).successful)
+ assertTrue(Parsers.parseAll(Parsers.samePrefix, foo + bar).successful) // would be false with |
+ }
+
+ @Test
+ def emptyCompletesToAlternatives =
+ assertEquals(Seq(foo), Parsers.completeString(Parsers.samePrefix, ""))
+
+ @Test
+ def partialLongerAlternativeCompletesToLongerAlternative =
+ assertEquals(Seq(bar), Parsers.completeString(Parsers.samePrefix, foo))
+
+ @Test
+ def longestParseProvidesCompletion =
+ assertEquals(Seq(bar), Parsers.completeString(Parsers.constrainedAndOpenAlternatives, foo))
+
+
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala
new file mode 100644
index 00000000..d8107127
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala
@@ -0,0 +1,82 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionForRepetitionTest {
+ val repeated = "repeated"
+ val separator = "separator"
+ val n = 5
+
+ object TestParser extends Parsers with RegexCompletionSupport {
+ val repSequence = rep(repeated)
+ val repSepSequence = repsep(repeated, separator)
+ val error = repsep(repeated, err("some error"))
+ val repNSequence = repN(5, repeated)
+
+ val subSeqLeft = "foo" ~ "bar" | "foo"
+ val subSeqRight = "as" ~ "df" | "df" ~ "as"
+ val composedSequence = subSeqLeft ~ subSeqRight
+ val repAlternatives = rep1sep("foo" | composedSequence, "and")
+ val repNAlternatives = repN(5, "foo" | composedSequence)
+ }
+
+ @Test
+ def emptyRepCompletesToRepeated =
+ Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, ""))
+
+ @Test
+ def nInstancesAndPartialRepCompletesToRepeated =
+ Assert.assertEquals(
+ Seq(repeated),
+ TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3)))
+
+ @Test
+ def nInstancesOfRepeatedRepNCompletesToRepeated =
+ Assert.assertEquals(Seq(repeated),
+ TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString))
+
+ @Test
+ def nInstancesPartialCompleteRepNCompletesToRepeated =
+ Assert.assertEquals(
+ Seq(repeated),
+ TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3)))
+
+ @Test
+ def nInstancesFollowedByErrorRepCompletesToNothing =
+ Assert.assertEquals(Nil,
+ TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + "error"))
+
+ @Test
+ def emptyRepSepCompletesToRepeated =
+ Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, ""))
+
+ @Test
+ def repeatedAndSeparatorRepSepCompletesToRepeated =
+ Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, repeated+separator))
+
+ @Test
+ def errorRepSepCompletesToNothing =
+ Assert.assertEquals(Nil, TestParser.completeString(TestParser.error, repeated))
+
+ @Test
+ def emptyRepNCompletesToRepeated =
+ Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, ""))
+
+ @Test
+ def repAlternativesCompletesToAlternatives(): Unit =
+ Assert.assertEquals(Seq("and", "as", "bar", "df"),
+ TestParser.completeString(TestParser.repAlternatives, s"foo and foo"))
+
+ @Test
+ def repNAlternativesCompletesToAlternatives(): Unit =
+ Assert.assertEquals(Seq("as", "bar", "df", "foo"),
+ TestParser.completeString(TestParser.repNAlternatives, s"foo foo"))
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala
new file mode 100644
index 00000000..be816550
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala
@@ -0,0 +1,59 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionForSequenceTest {
+ val left = "left"
+ val foo = "foo"
+ val bar = "bar"
+ val as = "as"
+ val df = "df"
+
+ object TestParser extends Parsers with RegexCompletionSupport {
+ val sequence = left ~> (foo | bar)
+
+ val subSeqLeft = foo ~ bar | foo
+ val subSeqRight = as ~ df | df ~ as
+ val composedSequence = subSeqLeft ~ subSeqRight
+ }
+
+ @Test
+ def emptyCompletesToLeft =
+ Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, ""))
+
+ @Test
+ def partialLeftCompletesToLeft =
+ Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, left.dropRight(2)))
+
+ @Test
+ def completeLeftcompletesToRightAlternatives = {
+ val completion = TestParser.complete(TestParser.sequence, left)
+ Assert.assertEquals(left.length + 1, completion.position.column)
+ Assert.assertEquals(Seq(bar, foo), completion.completionStrings)
+ }
+
+ @Test
+ def completeLeftAndRightCompletesToNothing =
+ Assert.assertEquals(Nil, TestParser.completeString(TestParser.sequence, left + " " + bar))
+
+
+ @Test
+ def emptyComposedCompletesToLeft =
+ Assert.assertEquals(Seq(foo), TestParser.completeString(TestParser.composedSequence, ""))
+
+ @Test
+ def leftComposedCompletesToLeftRemainingAlternativeAndRight =
+ Assert.assertEquals(Seq(as, bar, df), TestParser.completeString(TestParser.composedSequence, foo))
+
+ @Test
+ def completeLeftComposedCompletesToCorrectRightAlternative =
+ Assert.assertEquals(Seq(df), TestParser.completeString(TestParser.composedSequence, foo + " "+ as))
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala
new file mode 100644
index 00000000..ecf4927e
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala
@@ -0,0 +1,69 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.Test
+
+class CompletionForSimpleGrammarTest {
+ import CompletionTestDefinitions._
+
+ object SimpleGrammar extends CompletionTestParser {
+ val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number"
+
+ def expr: Parser[Int] = term | "(" ~> term <~ ")"
+ def term: Parser[Int] = number ^^ {
+ _.toInt
+ }
+ }
+
+ @Test
+ def emptyCompletesToNumberOrParen() =
+ SimpleGrammar.assertHasCompletions(
+ Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")),
+ SimpleGrammar.complete(SimpleGrammar.expr, ""))
+
+ @Test
+ def invalidCompletesToNothing() =
+ SimpleGrammar.assertHasCompletions(
+ Set(),
+ SimpleGrammar.complete(SimpleGrammar.expr, "invalid"))
+
+
+ @Test
+ def leftParenCompletesToNumber() =
+ SimpleGrammar.assertHasCompletions(
+ Set(Tagged("number", Some("any number"), 0, "1", "10", "99")),
+ SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"),
+ "("))
+
+ @Test
+ def leftParenAndNumberCompletesToRightParen() =
+ SimpleGrammar.assertHasCompletions(
+ Set(Default(")")),
+ SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"),
+ "(8"))
+
+ @Test
+ def leftParenAndInvalidCompletesToNothing() =
+ SimpleGrammar.assertHasCompletions(
+ Set(),
+ SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"),
+ "(invalid"))
+
+ @Test
+ def parenNumberCompletesToEmpty() =
+ SimpleGrammar.assertHasCompletions(
+ Set(),
+ SimpleGrammar.complete(SimpleGrammar.expr, "(56) "))
+
+ @Test
+ def numberCompletesToEmpty() =
+ SimpleGrammar.assertHasCompletions(
+ Set(),
+ SimpleGrammar.complete(SimpleGrammar.expr, "28 "))
+
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala
new file mode 100644
index 00000000..6c1c4e5e
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala
@@ -0,0 +1,122 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.{Assert, Test}
+
+import scala.util.parsing.combinator.Parsers
+
+class CompletionOperatorsTest {
+
+ object TestParser extends Parsers with RegexCompletionSupport {
+ val someParser: Parser[String] = "parser"
+ }
+
+ @Test
+ def completionSpecifiedWithBuilderIsCorrect = {
+ // Arrange
+ val completions: Seq[Seq[Char]] = Seq("one", "two", "three")
+ val score = 10
+ val description = "some description"
+ val tag = "some tag"
+ val kind = "some kind"
+
+ assertCompletionsMatch(TestParser.someParser %> (completions: _*) % (tag, score) %? description %% kind,
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ Some(kind))
+
+ assertCompletionsMatch(TestParser.someParser %> (completions: _*) % (tag, score, description),
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ None)
+
+ assertCompletionsMatch(TestParser.someParser %> (completions: _*) % (tag, score, description, kind),
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ Some(kind))
+
+ assertCompletionsMatch(
+ TestParser.someParser %> (completions: _*) % TestParser.CompletionTag(tag, score, Some(description), Some(kind)),
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ Some(kind)
+ )
+
+ assertCompletionsMatch(TestParser.someParser %> (completions: _*) % tag %? description % score %% kind,
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ Some(kind))
+
+ assertCompletionsMatch(TestParser.someParser %> (completions: _*) % tag % score %? description %% kind,
+ completions,
+ Some(tag),
+ Some(score),
+ Some(description),
+ Some(kind))
+ }
+
+ def assertCompletionsMatch[T](sut: TestParser.Parser[T],
+ completions: Seq[Seq[Char]],
+ tag: Option[String],
+ score: Option[Int],
+ description: Option[String],
+ kind: Option[String]) = {
+ // Act
+ val result = TestParser.complete(sut, "")
+
+ // Assert
+ val completionSet: TestParser.CompletionSet =
+ tag.flatMap(n => result.setWithTag(n)).orElse(result.defaultSet).get
+ Assert.assertEquals(tag.getOrElse(""), completionSet.tag.label)
+ Assert.assertEquals(score.getOrElse(0), completionSet.tag.score)
+ Assert.assertEquals(description, completionSet.tag.description)
+ Assert.assertEquals(kind, completionSet.tag.kind)
+ Assert.assertEquals(completions.toSet, completionSet.completions.map(_.value))
+ }
+
+ @Test
+ def unioningCompletionSetsScoresMergedItemsOffsetBySetScore = {
+ // Arrange
+ val a = Seq(TestParser.Completion("one", 10), TestParser.Completion("two"))
+ val b = Seq(TestParser.Completion("three", 5), TestParser.Completion("five"))
+ val c = Seq(TestParser.Completion("four"))
+ val sut = TestParser.someParser %> a % 10 | TestParser.someParser %> b | TestParser.someParser %> c % 3
+
+ // Act
+ val result = TestParser.complete(sut, "")
+
+ // Assert
+ Assert.assertArrayEquals(Seq("one", "two", "three", "four", "five").toArray[AnyRef],
+ result.completionStrings.toArray[AnyRef])
+ }
+
+ @Test
+ def topCompletionsLimitsCompletionsAccordingToScore(): Unit = {
+ // Arrange
+ val completions = Seq("one", "two", "three", "four").zipWithIndex.map {
+ case (c, s) => TestParser.Completion(c, s)
+ }
+ val sut = (TestParser.someParser %> completions).topCompletions(2)
+
+ // Act
+ val result = TestParser.complete(sut, "")
+
+ // Assert
+ Assert.assertArrayEquals(Seq("four", "three").toArray[AnyRef], result.completionStrings.toArray[AnyRef])
+ }
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala
new file mode 100644
index 00000000..89002a1c
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala
@@ -0,0 +1,52 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.Assert._
+
+import scala.util.parsing.combinator.Parsers
+
+object CompletionTestDefinitions {
+ trait AssertionSet{
+ def tag: String
+ }
+ case class Default(strings: String*) extends AssertionSet {
+ def tag: String = ""
+ }
+ case class Tagged(tag: String, desc: Option[String], score: Int, strings: String*)
+ extends AssertionSet
+ case object Tagged {
+ def apply(name: String, strings: String*): Tagged =
+ Tagged(name, None, 0, strings: _*)
+ }
+}
+
+trait CompletionTestParser extends Parsers with RegexCompletionSupport {
+ import CompletionTestDefinitions._
+ def assertSetEquals(expected: AssertionSet, actual: CompletionSet): Unit =
+ expected match {
+ case default @ Default(_ *) => {
+ default.strings.zip(actual.completionStrings).foreach {
+ case (expected, actual) => assertEquals(expected, actual)
+ }
+ }
+ case named @ Tagged(name, desc, score, _ *) => {
+ assertEquals(name, actual.tag.label)
+ assertEquals(score, actual.tag.score)
+ assertEquals(desc, actual.tag.description)
+ named.strings.zip(actual.completionStrings).foreach {
+ case (expected, actual) => assertEquals(expected, actual)
+ }
+ }
+ }
+ def assertHasCompletions(expected: Set[AssertionSet],
+ actual: Completions) = {
+ expected.toList.sortBy(_.tag).zip(actual.allSets.toList.sortBy(_.tag.label)).foreach{
+ case (expected, actual) => assertSetEquals(expected, actual)
+ }
+ }
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala
new file mode 100644
index 00000000..21f492f5
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala
@@ -0,0 +1,40 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.Assert._
+import org.junit.Test
+
+import scala.util.parsing.input.NoPosition
+
+class CompletionTypesTest extends CompletionTypes {
+ override type Elem = Char
+
+ val setA = CompletionSet(CompletionTag("A", 10), Set(Completion("a", 2), Completion("b", 1)))
+ val setB = CompletionSet(CompletionTag("B", 5), Set(Completion("c", 4), Completion("d", 3)))
+ val setC = CompletionSet("C", Completion("e", 10))
+
+ @Test
+ def completionsTakeTopWorks() = {
+ // Arrange
+ val compl = Completions(Seq(setA, setB, setC))
+
+ // Act
+ val lettersInOrder = Seq("a", "b", "c", "d", "e")
+ val letterSets = for (i <- 1 until lettersInOrder.length) yield lettersInOrder.take(i)
+ letterSets.foreach(set => assertEquals(set, compl.takeTop(set.length).completionStrings))
+ }
+
+ @Test
+ def completionsSetsScoredWithMaxCompletionWorks() = {
+ // Arrange
+ val compl = Completions(Seq(setA, setB, setC))
+
+ // Act
+ assertEquals(Seq("e", "c", "d", "a", "b"), compl.setsScoredWithMaxCompletion().completionStrings)
+ }
+}
diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala
new file mode 100644
index 00000000..4bc95799
--- /dev/null
+++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala
@@ -0,0 +1,73 @@
+/* *\
+** scala-parser-combinators completion extensions **
+** Copyright (c) by Nexthink S.A. **
+** Lausanne, Switzerland (http://www.nexthink.com) **
+\* */
+
+package scala.util.parsing.combinator.completion
+
+import org.junit.Assert._
+import org.junit.Test
+
+class RecursiveGrammarTest {
+ import CompletionTestDefinitions._
+
+ object ExprParser extends CompletionTestParser {
+ val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number"
+ lazy val expr: Parser[Int] = term ~ rep(
+ (("+" | "-") % "operators" %? "arithmetic operators" % 10) ~! term ^^ {
+ case "+" ~ t => t
+ case "-" ~ t => -t
+ }) ^^ { case t ~ r => t + r.sum }
+ lazy val multiplicationDivisionOperators = ("*" | "/") % "operators" %? "arithmetic operators" % 10
+ lazy val term: Parser[Int] = factor ~ rep(multiplicationDivisionOperators ~! factor) ^^ {
+ case f ~ Nil => f
+ case f ~ r =>
+ r.foldLeft(f) {
+ case (prev, "*" ~ next) => prev * next
+ case (prev, "/" ~ next) => prev / next
+ }
+ }
+ lazy val factor: Parser[Int] = number ^^ { _.toInt } | "(" ~> expr <~ ")"
+ }
+
+ @Test
+ def expressionsParseCorrectly() = {
+ assertEquals(1 + 2 + 3, ExprParser.parseAll(ExprParser.expr, "1+2+3").get)
+ assertEquals(2 * 3, ExprParser.parseAll(ExprParser.expr, "2*3").get)
+ assertEquals(10 / (3 + 2), ExprParser.parseAll(ExprParser.expr, "(5+5)/(3+2)").get)
+ assertEquals(5 * 2 / 2, ExprParser.parseAll(ExprParser.expr, "(5*2/2)").get)
+ assertEquals(3 - 4 - 5, ExprParser.parseAll(ExprParser.expr, "3-4-5").get)
+ }
+
+ @Test
+ def emptyCompletesToNumberOrParen() =
+ ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")),
+ ExprParser.complete(ExprParser.expr, ""))
+
+ @Test
+ def numberCompletesToOperators() =
+ ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")),
+ ExprParser.complete(ExprParser.expr, "2"))
+
+ @Test
+ def numberAndOperationCompletesToNumberOrParen() =
+ ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")),
+ ExprParser.complete(ExprParser.expr, "2*"))
+
+ @Test
+ def parenCompletesToNumberAndParen() =
+ ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")),
+ ExprParser.complete(ExprParser.expr, "("))
+
+ @Test
+ def recursiveParenAndNumberCompletesToOperatorsOrParen() =
+ ExprParser.assertHasCompletions(
+ Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/"), Default(")")),
+ ExprParser.complete(ExprParser.expr, "(((2"))
+
+ @Test
+ def closedParentCompletesToOperators() =
+ ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")),
+ ExprParser.complete(ExprParser.expr, "(5*2/2)"))
+}