From f0860c75e9d17fee95c9d2e4660ba5da1bdc25c6 Mon Sep 17 00:00:00 2001 From: jchapuis Date: Thu, 9 Feb 2017 10:55:25 +0100 Subject: [PATCH 01/13] most definitions are in --- .scalafmt.conf | 2 + .../completion/CompletionDefinitions.scala | 175 +++++ .../completion/CompletionParsers.scala | 712 ++++++++++++++++++ .../completion/RegexCompletionParsers.scala | 70 ++ 4 files changed, 959 insertions(+) create mode 100644 .scalafmt.conf create mode 100644 shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala create mode 100644 shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala create mode 100644 shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..4068f5d2 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,2 @@ +style = defaultWithAlign +maxColumn = 120 diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala new file mode 100644 index 00000000..9181ae02 --- /dev/null +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala @@ -0,0 +1,175 @@ +package scala.util.parsing.combinator.completion + +import scala.util.parsing.input.{NoPosition, Position} + +trait CompletionDefinitions { + type Elem + + val DefaultCompletionTag = "" + val DefaultCompletionScore = 0 + + 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) + } + + 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] + case class Completion(value: Elems, score: Int = DefaultCompletionScore, kind: Option[String] = None) { + require(value.nonEmpty, "empty completion") + } + case object Completion { + def apply(el: Elem): Completion = Completion(Seq(el)) + implicit def orderingByScoreAndThenAlphabetical: Ordering[Completion] = + Ordering.by(c => (-c.score, c.value.toString)) + } + + 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 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) + + val empty = Completions(NoPosition, Map[String, CompletionSet]()) + } + +} \ No newline at end of file diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala new file mode 100644 index 00000000..8fd2a0cf --- /dev/null +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala @@ -0,0 +1,712 @@ +package scala.util.parsing.combinator.completion + +import scala.annotation.tailrec +import scala.util.parsing.combinator.Parsers +import scala.util.parsing.input.Positional + +/** + * Created by jchapuis on 16.01.2017. + */ +trait CompletionParsers extends Parsers with CompletionDefinitions { + def CompletionParser[T](f: Input => ParseResult[T], c: Input => Completions) = new CompletionParser[T] { + def apply(in: Input) = f(in) + def completions(in: Input) = c(in) + } + + implicit def parser2completion[T](p: => super.Parser[T]): CompletionParser[T] = { + lazy val q = p + CompletionParser(in => q(in), in => Completions.empty) + } + + abstract class CompletionParser[+T] extends super.Parser[T] { + + def append[U >: T](q: => CompletionParser[U]): CompletionParser[U] = { + lazy val p = q // lazy argument + CompletionParser( + in => super.append(q)(in), + in => { + val thisCompletions = this.completions(in) + lazy val combinedCompletions = thisCompletions | q.completions(in) + this(in) match { + case Success(_, rest) => + // only return optional 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 + } + } + ) + } + + def completions(in: Input): Completions + + def %>(completions: Elems*): CompletionParser[T] = + %>(completions.map(el => Completion(el))) + + def %>(completions: Completion): CompletionParser[T] = + %>(Set(completions)) + + def %>(completions: Iterable[Completion]): CompletionParser[T] = + CompletionParser(in => this(in), in => { + this(in) match { + case Failure(_, rest) if rest.atEnd => + Completions(rest.pos, CompletionSet(completions)) + case _ => Completions.empty + } + }) + + def %>(completioner: Input => Completions): CompletionParser[T] = + CompletionParser(in => this(in), completioner) + + def topCompletions(n: Int): CompletionParser[T] = + CompletionParser( + in => this(in), + 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))) + } + ) + + def %(tag: String): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), Some(tag), None, None, None, None)) + + def %(score: Int): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), None, Some(score), None, None, None)) + + def %(tag: String, score: Int): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), Some(tag), Some(score), None, None, None)) + + def %(tag: String, score: Int, description: String): CompletionParser[T] = + CompletionParser( + in => this(in), + in => updateCompletions(this.completions(in), Some(tag), Some(score), Some(description), None, None)) + + def %(tag: String, score: Int, description: String, kind: String): CompletionParser[T] = + CompletionParser( + in => this(in), + in => updateCompletions(this.completions(in), Some(tag), Some(score), Some(description), Some(kind), None)) + + def %?(description: String): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), None, None, Some(description), None, None)) + + def %%(tagKind: String): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), None, None, None, Some(tagKind), None)) + + def %-%(kind: String): CompletionParser[T] = + CompletionParser(in => this(in), + in => updateCompletions(this.completions(in), None, None, None, None, Some(kind))) + + def flatMap[U](f: T => CompletionParser[U]): CompletionParser[U] = + CompletionParser(super.flatMap(f), completions) + + override def map[U](f: T => U): CompletionParser[U] = + CompletionParser(super.map(f), in => completions(in)) + + override def filter(p: T => Boolean): CompletionParser[T] = withFilter(p) + + override def withFilter(p: T => Boolean): CompletionParser[T] = + CompletionParser(super.withFilter(p), completions) + + private def seqCompletions[U](in: Input, other: => CompletionParser[U]): Completions = { + lazy val thisCompletions = this.completions(in) + this(in) match { + case Success(_, rest) => + thisCompletions | other.completions(rest) + case NoSuccess(_, _) => + thisCompletions + } + } + + private def updateCompletions(completions: Completions, + newTag: Option[String], + newScore: Option[Int], + newDescription: Option[String], + newTagKind: Option[String], + newKind: Option[String]) = { + def updateCompletions(completions: Set[Completion], newLinkKind: Option[String]): Set[Completion] = { + newLinkKind match { + case Some(x) => completions.map(e => e.copy(kind = Some(x))) + case None => completions + } + } + + def updateSet(existingSet: CompletionSet) = + CompletionSet(existingSet.tag.update(newTag, newScore, newDescription, newTagKind), + updateCompletions(existingSet.completions, newKind)) + + Completions(completions.position, + completions.sets.values + .map(updateSet) + .map(s => s.tag.label -> s) + .toMap) + } + + /** 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: => CompletionParser[U]): CompletionParser[~[T, U]] = { + lazy val p = q + CompletionParser(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: => CompletionParser[U]): CompletionParser[U] = { + lazy val p = q + CompletionParser(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: => CompletionParser[U]): CompletionParser[T] = { + lazy val p = q + CompletionParser(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 p 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 ~![U](q: => CompletionParser[U]): CompletionParser[~[T, U]] = { + lazy val p = q + CompletionParser(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 ~>![U](q: => CompletionParser[U]): CompletionParser[U] = { + lazy val p = q + CompletionParser(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 <~![U](q: => CompletionParser[U]): CompletionParser[T] = { + lazy val p = q + CompletionParser(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: => CompletionParser[U]): CompletionParser[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 q0 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: => CompletionParser[U]): CompletionParser[U] = { + lazy val p = q + CompletionParser(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): CompletionParser[U] = + CompletionParser(super.^^(f), completions) + + /** 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): CompletionParser[U] = { + CompletionParser(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): CompletionParser[U] = + CompletionParser(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]): CompletionParser[U] = + CompletionParser(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 + */ + override def into[U](fq: T => Parser[U]): CompletionParser[U] = + CompletionParser(super.into(fq), 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) = + CompletionParser(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: => CompletionParser[T]): CompletionParser[T] = + CompletionParser(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: Seq[Elem] = Nil): CompletionParser[Elem] = + acceptIf(p, + if (completions.isEmpty) + None + else + Some(CompletionSet(CompletionTag(kind), completions.map(c => Completion(c)).toSet)))(inEl => + kind + " expected") + + /** 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): CompletionParser[Elem] = + acceptIf(_ == e, Some(CompletionSet(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): CompletionParser[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()): CompletionParser[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: Option[CompletionSet])(err: Elem => String): CompletionParser[Elem] = { + CompletionParser(super.acceptIf(p)(err), + in => completions.map(Completions(in.pos, _)).getOrElse(Completions.empty)) + } + + def acceptMatch[U](expected: String, + f: PartialFunction[Elem, U], + completions: Set[Completion]): CompletionParser[U] = { + lazy val completionSet = + if (completions.nonEmpty) + Some(CompletionSet(CompletionTag(expected), completions)) + else None + CompletionParser(super.acceptMatch(expected, f), + in => completionSet.map(Completions(in.pos, _)).getOrElse(Completions.empty)) + .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): CompletionParser[List[Elem]] = + CompletionParser(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): CompletionParser[Nothing] = + CompletionParser(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): CompletionParser[T] = CompletionParser(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): CompletionParser[Nothing] = CompletionParser(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: => CompletionParser[T])(name: String): CompletionParser[T] = + CompletionParser(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: => CompletionParser[T]): CompletionParser[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: => CompletionParser[T], q: => CompletionParser[Any]): CompletionParser[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: => CompletionParser[T]): CompletionParser[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: => CompletionParser[T], p0: => CompletionParser[T]): CompletionParser[List[T]] = { + lazy val p = p0 // lazy argument + CompletionParser( + super.rep1(first, p0), + in => { + @tailrec def continue(in: Input): Completions = { + p(in) match { + case Success(_, rest) => continue(rest) + case NoSuccess(_, _) => p.completions(in) + } + } + first(in) match { + case Success(_, rest) => continue(rest) + case NoSuccess(_, _) => first.completions(in) + } + } + ) + } + + /** 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: => CompletionParser[T]): CompletionParser[List[T]] = { + lazy val p = p0 // lazy argument + if (num == 0) success(Nil) + else + CompletionParser( + super.repN(num, p0), + in => { + var parsedCount = 0 + @tailrec def completions(in0: Input): Completions = + if (parsedCount == num) Completions.empty + else + p(in0) match { + case Success(_, rest) => parsedCount += 1; completions(rest) + case ns: NoSuccess => p.completions(in0) + } + + 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: => CompletionParser[T], q: => CompletionParser[Any]): CompletionParser[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: => CompletionParser[T], q: => CompletionParser[(T, T) => T]): CompletionParser[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: => CompletionParser[T], + p: => CompletionParser[U], + q: => CompletionParser[(T, U) => T]): CompletionParser[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: => CompletionParser[T], + q: => CompletionParser[(T, U) => U], + combine: (T, U) => U, + first: U): CompletionParser[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: => CompletionParser[T]): CompletionParser[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: => CompletionParser[T]): CompletionParser[Unit] = + CompletionParser(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: => CompletionParser[T]): CompletionParser[T] = + CompletionParser(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: => CompletionParser[T]): CompletionParser[T] = + CompletionParser(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: CompletionParser[T]) = + CompletionParser(super.phrase(p), p.completions) +} diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala new file mode 100644 index 00000000..f441784c --- /dev/null +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala @@ -0,0 +1,70 @@ +package scala.util.parsing.combinator.completion + +import scala.util.parsing.combinator.RegexParsers +import scala.util.parsing.input.{CharSequenceReader, OffsetPosition, Positional, Reader} + +/** + * Created by jchapuis on 30.01.2017. + */ +trait RegexCompletionParsers extends RegexParsers with CompletionParsers { + 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] = + CompletionParser[String]( + super.literal(s), + in => { + 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 + } + } + ) + + override def positioned[T <: Positional](p: => CompletionParser[T]): CompletionParser[T] = { + lazy val q = p + CompletionParser[T](super.positioned(p), q.completions) + } + + /** Returns completions for read `in` with parser `p`. */ + def complete[T](p: CompletionParser[T], in: Reader[Char]): Completions = + p.completions(in) + + /** Returns completions for character sequence `in` with parser `p`. */ + def complete[T](p: CompletionParser[T], in: CharSequence): Completions = + p.completions(new CharSequenceReader(in)) + + /** Returns flattened string completions for character sequence `in` with parser `p`. */ + def completeString[T](p: CompletionParser[T], input: String): Seq[String] = + complete(p, input).completionStrings + +} From 4c3e53bf4874164cb4055a5576b1657e65e6a63b Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 13:45:36 +0100 Subject: [PATCH 02/13] added comments --- .../completion/CompletionDefinitions.scala | 45 +++- .../completion/CompletionParsers.scala | 192 +++++++++++++----- .../completion/RegexCompletionParsers.scala | 7 +- .../CompletionDefinitionsTest.scala | 39 ++++ .../CompletionForAlternativesTest.scala | 32 +++ .../completion/CompletionForLiteralTest.scala | 72 +++++++ .../CompletionForLongestMatchTest.scala | 40 ++++ .../CompletionForRepetitionTest.scala | 58 ++++++ .../CompletionForSequenceTest.scala | 57 ++++++ .../CompletionForSimpleGrammarTest.scala | 69 +++++++ .../completion/CompletionOperatorsTest.scala | 85 ++++++++ .../CompletionTestDefinitions.scala | 50 +++++ .../completion/RecursiveGrammarTest.scala | 73 +++++++ 13 files changed, 760 insertions(+), 59 deletions(-) create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala index 9181ae02..97ff9e55 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala @@ -2,12 +2,33 @@ package scala.util.parsing.combinator.completion import scala.util.parsing.input.{NoPosition, Position} +/** `CompletionDefinitions` provides types to specify structured completions for parsers. + * 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` (+ a `description` and + * a `kind`, the latter allowing e.g. encoding visual attributes of 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 + */ trait CompletionDefinitions { 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], @@ -32,6 +53,10 @@ trait CompletionDefinitions { 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 @@ -69,8 +94,16 @@ trait CompletionDefinitions { } 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)) @@ -78,6 +111,10 @@ trait CompletionDefinitions { 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 @@ -109,9 +146,9 @@ trait CompletionDefinitions { val rightExclusive = other.sets.keySet.diff(overlappingSetTags).map(other.sets(_)) Completions(position, - (unions ++ leftExclusive ++ rightExclusive) - .map(s => s.tag.label -> s) - .toMap) + (unions ++ leftExclusive ++ rightExclusive) + .map(s => s.tag.label -> s) + .toMap) } def |(other: Completions): Completions = { @@ -172,4 +209,4 @@ trait CompletionDefinitions { val empty = Completions(NoPosition, Map[String, CompletionSet]()) } -} \ No newline at end of file +} diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala index 8fd2a0cf..0d9aa40d 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala @@ -4,8 +4,27 @@ import scala.annotation.tailrec import scala.util.parsing.combinator.Parsers import scala.util.parsing.input.Positional -/** - * Created by jchapuis on 16.01.2017. +/** `CompletionParsers` extends `Parsers` with completion capability. + * + * A `CompletionParsers` may define `CompletionParser` instances, + * which are combined to produce the desired parser with completion + * capability. + * + * [[scala.util.parsing.combinator.completion.CompletionParsers.CompletionParser]] extends + * the [[scala.util.parsing.combinator.Parsers.Parser]] type with the abstract method + * [[scala.util.parsing.combinator.completion.CompletionParsers.CompletionParser#completions]] + * which returns a instance of [[scala.util.parsing.combinator.completion.CompletionDefinitions.Completions]] + * for a certain input. + * + * Combinators have been extended to cover the additional completion aspect, so that + * no change is required in the grammar itself. + * + * Using instances of `CompletionParser` instead of `Parser` will allow specifying possible completions + * for a certain parser (for instance the provided subclass [[scala.util.parsing.combinator.completion.RegexCompletionParsers]] + * defines completion behavior for a `literal` parser). + * + * A new set additional operators also allow overriding completions and specifying structural properties of completions + * (tag, score, kind, etc.) for a `CompletionParser`. */ trait CompletionParsers extends Parsers with CompletionDefinitions { def CompletionParser[T](f: Input => ParseResult[T], c: Input => Completions) = new CompletionParser[T] { @@ -13,11 +32,10 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { def completions(in: Input) = c(in) } - implicit def parser2completion[T](p: => super.Parser[T]): CompletionParser[T] = { - lazy val q = p - CompletionParser(in => q(in), in => Completions.empty) - } - + /** The root class of completion parsers, extending 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 CompletionParser[+T] extends super.Parser[T] { def append[U >: T](q: => CompletionParser[U]): CompletionParser[U] = { @@ -40,16 +58,32 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { ) } + /** An unspecified method that defines the possible completions for this parser + * @param in the input + * @return an instance of [[scala.util.parsing.combinator.completion.CompletionDefinitions.Completions]] + */ def completions(in: Input): Completions + /** An operator to specify completions of a parser + * @param completions possible completions for this parser + * @return a `CompletionParser` that upon invocation of the `completions` method returns the passed completions + */ def %>(completions: Elems*): CompletionParser[T] = %>(completions.map(el => Completion(el))) - def %>(completions: Completion): CompletionParser[T] = - %>(Set(completions)) + /** An operator to specify completion of a parser + * @param completion completion for this parser + * @return a `CompletionParser` that upon invocation of the `completions` method returns the passed completion + */ + def %>(completion: Completion): CompletionParser[T] = + %>(Set(completion)) + /** An operator to specify completions of a parser + * @param completions possible completions for this parser + * @return a `CompletionParser` that upon invocation of the `completions` method returns the passed completions + */ def %>(completions: Iterable[Completion]): CompletionParser[T] = - CompletionParser(in => this(in), in => { + CompletionParser(this, in => { this(in) match { case Failure(_, rest) if rest.atEnd => Completions(rest.pos, CompletionSet(completions)) @@ -57,12 +91,20 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { } }) + /** An operator to specify completions of a parser + * @param completioner function of input to completions + * @return a `CompletionParser` that upon invocation of the `completions` method will invoke the passed function + */ def %>(completioner: Input => Completions): CompletionParser[T] = - CompletionParser(in => this(in), completioner) + CompletionParser(this, completioner) + /** Limits completions to the top `n` completions ordered by their score + * @param n the limit + * @return wrapper `CompletionParser` instance limiting the number of completions + */ def topCompletions(n: Int): CompletionParser[T] = CompletionParser( - in => this(in), + this, in => { val completions = this.completions(in) Completions(completions.position, @@ -71,39 +113,81 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { } ) + /** 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 `CompletionParser` instance specifying the completion tag + */ def %(tag: String): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), Some(tag), None, None, None, None)) + CompletionParser(this, in => updateCompletionsTag(this.completions(in), Some(tag), None, None, None)) - def %(score: Int): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), None, Some(score), None, None, None)) - - def %(tag: String, score: Int): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), Some(tag), Some(score), 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 `CompletionParser` instance specifying the completion tag score + */ + def %(tagScore: Int): CompletionParser[T] = + CompletionParser(this, in => updateCompletionsTag(this.completions(in), None, Some(tagScore), None, None)) - def %(tag: String, score: Int, description: String): CompletionParser[T] = + /** 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 `CompletionParser` instance specifying the completion tag + */ + def %(tag: String, tagScore: Int): CompletionParser[T] = + CompletionParser(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 `CompletionParser` instance specifying completion tag + */ + def %(tag: String, tagScore: Int, tagDescription: String): CompletionParser[T] = + CompletionParser( + 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 `CompletionParser` instance specifying completion tag + */ + def %(tag: String, tagScore: Int, tagDescription: String, tagKind: String): CompletionParser[T] = CompletionParser( - in => this(in), - in => updateCompletions(this.completions(in), Some(tag), Some(score), Some(description), None, None)) + this, + in => + updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), Some(tagKind))) - def %(tag: String, score: Int, description: String, kind: String): CompletionParser[T] = + /** An operator to specify the completion tag + * @param tag the completion tag + * @return wrapper `CompletionParser` instance specifying completion tag + */ + def %(tag: CompletionTag): CompletionParser[T] = CompletionParser( - in => this(in), - in => updateCompletions(this.completions(in), Some(tag), Some(score), Some(description), Some(kind), None)) + this, + in => updateCompletionsTag(this.completions(in), Some(tag.label), Some(tag.score), tag.description, tag.kind)) - def %?(description: String): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), None, None, Some(description), None, None)) + /** 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 `CompletionParser` instance specifying the completion description + */ + def %?(tagDescription: String): CompletionParser[T] = + CompletionParser(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 `CompletionParser` instance specifying the completion tag kind + */ def %%(tagKind: String): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), None, None, None, Some(tagKind), None)) + CompletionParser(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 `CompletionParser` instance specifying the completion kind + */ def %-%(kind: String): CompletionParser[T] = - CompletionParser(in => this(in), - in => updateCompletions(this.completions(in), None, None, None, None, Some(kind))) + CompletionParser(this, in => updateCompletions(this.completions(in), Some(kind))) def flatMap[U](f: T => CompletionParser[U]): CompletionParser[U] = CompletionParser(super.flatMap(f), completions) @@ -126,23 +210,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { } } - private def updateCompletions(completions: Completions, - newTag: Option[String], - newScore: Option[Int], - newDescription: Option[String], - newTagKind: Option[String], - newKind: Option[String]) = { - def updateCompletions(completions: Set[Completion], newLinkKind: Option[String]): Set[Completion] = { - newLinkKind match { - case Some(x) => completions.map(e => e.copy(kind = Some(x))) - case None => completions - } - } - - def updateSet(existingSet: CompletionSet) = - CompletionSet(existingSet.tag.update(newTag, newScore, newDescription, newTagKind), - updateCompletions(existingSet.completions, newKind)) - + private def updateCompletionsSets(completions: Completions, updateSet: CompletionSet => CompletionSet) = { Completions(completions.position, completions.sets.values .map(updateSet) @@ -150,6 +218,24 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { .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`. @@ -198,7 +284,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * `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 p a parser that will be executed after `p` (this parser) succeeds + * @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. @@ -254,7 +340,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * `p ||| q` succeeds if `p` succeeds or `q` succeeds. * If `p` and `q` both succeed, the parser that consumed the most characters accepts. * - * @param q0 a parser that accepts if p consumes less characters. -- evaluated at most once, and only when necessary + * @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: => CompletionParser[U]): CompletionParser[U] = { diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala index f441784c..a4612195 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala @@ -1,5 +1,5 @@ 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} @@ -31,7 +31,7 @@ trait RegexCompletionParsers extends RegexParsers with CompletionParsers { (literalPos, sourcePos) } - abstract override implicit def literal(s: String): Parser[String] = + abstract override implicit def literal(s: String): CompletionParser[String] = CompletionParser[String]( super.literal(s), in => { @@ -50,6 +50,9 @@ trait RegexCompletionParsers extends RegexParsers with CompletionParsers { } ) + abstract override implicit def regex(r: Regex): CompletionParser[String] = + CompletionParser(super.regex(r), _ => Completions.empty) + override def positioned[T <: Positional](p: => CompletionParser[T]): CompletionParser[T] = { lazy val q = p CompletionParser[T](super.positioned(p), q.completions) diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala new file mode 100644 index 00000000..64fbd4cd --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala @@ -0,0 +1,39 @@ +package scala.util.parsing.combinator.completion + +import org.junit.Assert._ +import org.junit.Test + +import scala.util.parsing.input.NoPosition + +/** + * Copyright (c) by Nexthink S.A. + * Lausanne, Switzerland (http://www.nexthink.com) + * Created by Jonas on 09.01.2017. + */ +class CompletionDefinitionsTest extends CompletionDefinitions { + 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 completions_takeTop_works() = { + // Arrange + val compl = Completions(NoPosition, Seq(setA, setB, setC).map(s => (s.tag.label, s)).toMap) + + // 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 completions_setsScoredWithMaxCompletion_works() = { + // Arrange + val compl = Completions(NoPosition, Seq(setA, setB, setC).map(s => (s.tag.label, s)).toMap) + + // Act + assertEquals(Seq("e", "c", "d", "a", "b"), compl.setsScoredWithMaxCompletion().completionStrings) + } +} 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..70e861af --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala @@ -0,0 +1,32 @@ +package scala.util.parsing.combinator.completion + +import org.junit.{Assert, Test} + +/** + * Copyright (c) by Nexthink S.A. + * Lausanne, Switzerland (http://www.nexthink.com) + * Created by Jonas on 13.01.2017. + */ +class CompletionForAlternativesTest { + val left = "left" + val right = "right" + val common = "common" + + object TestParser extends RegexCompletionParsers { + val alternativesWithCommonFirstParser = common ~ left | common ~! right + val alternativesWithCommonPrefix = (common+left) | common ~ right + } + + @Test + def empty_completes_toCommon = + Assert.assertEquals(Seq(common), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, "")) + + @Test + def common_completes_toLeftAndRight = + Assert.assertEquals(Seq(left, right), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, common)) + + @Test + def commonPrefix_completes_toRightSinceCompletionPositionsAreDifferent = + Assert.assertEquals(Seq(right), TestParser.completeString(TestParser.alternativesWithCommonPrefix, common)) + +} 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..a5bdf9db --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala @@ -0,0 +1,72 @@ +/* *\ +** 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 + +class CompletionForLiteralTest { + val someLiteral = "literal" + val otherLiteralWithSamePrefix = "litOther" + val someLiteralPrefix = "lit" + + object Parser extends RegexCompletionParsers { + val literal: CompletionParser[String] = someLiteral + + val combination = someLiteral | otherLiteralWithSamePrefix + } + + @Test + def prefix_completes_toLiteral = { + val completion = Parser.complete(Parser.literal, " " + someLiteralPrefix) + assertEquals(2, completion.position.column) + assertEquals(Seq(someLiteral), completion.completionStrings) + } + + @Test + def prefix_combinationCompletes_toBothAlternatives = { + val completion = + Parser.completeString(Parser.combination, someLiteralPrefix) + assertEquals(Seq(otherLiteralWithSamePrefix, someLiteral), completion) + } + + @Test + def partialOther_completes_toOther = { + val completion = Parser.completeString( + Parser.combination, + someLiteralPrefix + otherLiteralWithSamePrefix + .stripPrefix(someLiteralPrefix) + .head) + assertEquals(Seq(otherLiteralWithSamePrefix), completion) + } + + @Test + def whitespace_completes_toLiteral = { + val completion = + Parser.complete(Parser.literal, List.fill(2)(" ").mkString) + assertEquals(3, completion.position.column) + assertEquals(Seq(someLiteral), completion.completionStrings) + } + + @Test + def empty_completes_toLiteral = { + val completion = Parser.complete(Parser.literal, "") + assertEquals(1, completion.position.column) + assertEquals(Seq(someLiteral), completion.completionStrings) + } + + @Test + def other_completes_toNothing = + assertEquals( + Map(), + Parser.complete(Parser.literal, otherLiteralWithSamePrefix).sets) + + @Test + def completeLiteral_completes_toEmpty = + 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..31ae225d --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala @@ -0,0 +1,40 @@ +/* *\ +** 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 + +class CompletionForLongestMatchTest { + val foo = "foo" + val bar = "bar" + + object Parsers extends RegexCompletionParsers { + val samePrefix = foo ||| foo ~ bar + val constrainedAndOpenAlternatives = foo ~ bar ||| (".{5,}".r %> Completion("sample string longer than 5 char")) + } + + @Test + def normallyProblematicallyOrderedAlternatives_parse_correctly = { + assertTrue(Parsers.parseAll(Parsers.samePrefix, foo).successful) + assertTrue(Parsers.parseAll(Parsers.samePrefix, foo + bar).successful) // would be false with | + } + + @Test + def empty_completesTo_alternatives = + assertEquals(Seq(foo), Parsers.completeString(Parsers.samePrefix, "")) + + @Test + def partialLongerAlternative_completesTo_LongerAlternative = + assertEquals(Seq(bar), Parsers.completeString(Parsers.samePrefix, foo)) + + @Test + def longestParse_providesCompletion = + 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..1813a8b2 --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala @@ -0,0 +1,58 @@ +/* *\ +** 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, Test} + +class CompletionForRepetitionTest { + val repeated = "repeated" + val separator = "separator" + val n = 5 + + object TestParser extends RegexCompletionParsers { + val repSequence = rep(repeated) + val repSepSequence = repsep(repeated, separator) + val error = repsep(repeated, err("some error")) + val repNSequence = repN(5, repeated) + } + + @Test + def empty_repCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, "")) + + @Test + def nInstancesAndPartial_repCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3))) + + @Test + def nInstancesOfRepeated_repNCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString)) + + @Test + def nInstancesPartialComplete_repNCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3))) + + @Test + def nInstancesFollowedByError_repCompletes_toNothing = + Assert.assertEquals(Nil, TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + "error")) + + @Test + def empty_repSepCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, "")) + + @Test + def repeatedAndSeparator_repSepCompletes_toRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, repeated+separator)) + + @Test + def error_repSepCompletes_ToNothing = + Assert.assertEquals(Nil, TestParser.completeString(TestParser.error, repeated)) + + @Test + def empty_repNCompletes_ToRepeated = + Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, "")) +} 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..4406c47b --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala @@ -0,0 +1,57 @@ +/* *\ +** 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, Test} + +class CompletionForSequenceTest { + val left = "left" + val foo = "foo" + val bar = "bar" + val as = "as" + val df = "df" + + object TestParser extends RegexCompletionParsers { + val sequence = left ~> (foo | bar) + + val subSeqLeft = foo | foo ~ bar + val subSeqRight = as ~ df | df ~ as + val composedSequence = subSeqLeft ~ subSeqRight + } + + @Test + def empty_completes_toLeft = + Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, "")) + + @Test + def partialLeft_completes_toLeft = + Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, left.dropRight(2))) + + @Test + def completeLeft_completes_toRightAlternatives = { + val completion = TestParser.complete(TestParser.sequence, left) + Assert.assertEquals(left.length + 1, completion.position.column) + Assert.assertEquals(Seq(bar, foo), completion.completionStrings) + } + + @Test + def completeLeftAndRight_completes_toNothing = + Assert.assertEquals(Nil, TestParser.completeString(TestParser.sequence, left + " " + bar)) + + + @Test + def empty_composedCompletes_toLeft = + Assert.assertEquals(Seq(foo), TestParser.completeString(TestParser.composedSequence, "")) + + @Test + def left_composedCompletes_toLeftRemainingAlternativeAndRight = + Assert.assertEquals(Seq(as, bar, df), TestParser.completeString(TestParser.composedSequence, foo)) + + @Test + def completeLeft_composedCompletes_ToCorrectRightAlternative = + 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..e2750ae8 --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala @@ -0,0 +1,69 @@ +/* *\ +** 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.Test + +class CompletionForSimpleGrammarTest { + import CompletionTestDefinitions._ + + object SimpleGrammar extends CompletionTestParser { + val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number" + + def expr: CompletionParser[Int] = term | "(" ~> term <~ ")" + def term: CompletionParser[Int] = number ^^ { + _.toInt + } + } + + @Test + def empty_completes_toNumberOrParen() = + SimpleGrammar.assertHasCompletions( + Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), + SimpleGrammar.complete(SimpleGrammar.expr, "")) + + @Test + def invalid_completes_toNothing() = + SimpleGrammar.assertHasCompletions( + Set(), + SimpleGrammar.complete(SimpleGrammar.expr, "invalid")) + + + @Test + def leftParen_completes_toNumber() = + SimpleGrammar.assertHasCompletions( + Set(Tagged("number", Some("any number"), 0, "1", "10", "99")), + SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), + "(")) + + @Test + def leftParenAndNumber_completes_toRightParen() = + SimpleGrammar.assertHasCompletions( + Set(Default(")")), + SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), + "(8")) + + @Test + def leftParenAndInvalid_completes_toNothing() = + SimpleGrammar.assertHasCompletions( + Set(), + SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), + "(invalid")) + + @Test + def parenNumber_completes_toEmpty() = + SimpleGrammar.assertHasCompletions( + Set(), + SimpleGrammar.complete(SimpleGrammar.expr, "(56) ")) + + @Test + def number_completes_toEmpty() = + 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..f7e17bee --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala @@ -0,0 +1,85 @@ +/* *\ +** 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, Test} + +class CompletionOperatorsTest { + + object TestParser extends RegexCompletionParsers { + val someParser: CompletionParser[String] = "parser" + } + + @Test + def completion_specifiedWithBuilder_isCorrect = { + // 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 %? 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.CompletionParser[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 unioning_completionSets_scoresMergedItemsOffsetBySetScore = { + // 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]) + } + +} 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..0c0227c4 --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala @@ -0,0 +1,50 @@ +/* *\ +** 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._ + +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 RegexCompletionParsers { + 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/RecursiveGrammarTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala new file mode 100644 index 00000000..3e3c9e53 --- /dev/null +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala @@ -0,0 +1,73 @@ +/* *\ +** 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 + +class RecursiveGrammarTest { + import CompletionTestDefinitions._ + + object ExprParser extends CompletionTestParser { + val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number" + lazy val expr: CompletionParser[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: CompletionParser[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: CompletionParser[Int] = number ^^ { _.toInt } | "(" ~> expr <~ ")" + } + + @Test + def expressions_parse_correctly() = { + 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 empty_completes_toNumberOrParen() = + ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), + ExprParser.complete(ExprParser.expr, "")) + + @Test + def number_completes_toOperators() = + ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")), + ExprParser.complete(ExprParser.expr, "2")) + + @Test + def numberAndOperation_completes_toNumberOrParen() = + ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), + ExprParser.complete(ExprParser.expr, "2*")) + + @Test + def paren_completes_toNumberAndParen() = + ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), + ExprParser.complete(ExprParser.expr, "(")) + + @Test + def recursiveParenAndNumber_completes_toOperatorsOrParen() = + ExprParser.assertHasCompletions( + Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/"), Default(")")), + ExprParser.complete(ExprParser.expr, "(((2")) + + @Test + def closedParent_completes_toOperators() = + ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")), + ExprParser.complete(ExprParser.expr, "(5*2/2)")) +} From 4c0fd5516194ce864ddc9cf1cb44e444f827cbc8 Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 13:54:26 +0100 Subject: [PATCH 03/13] added helper constructor for completions --- .../parsing/combinator/completion/CompletionDefinitions.scala | 2 ++ .../combinator/completion/CompletionDefinitionsTest.scala | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala index 97ff9e55..1977500a 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala @@ -205,6 +205,8 @@ trait CompletionDefinitions { 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/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala index 64fbd4cd..c7bdb050 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala @@ -20,7 +20,7 @@ class CompletionDefinitionsTest extends CompletionDefinitions { @Test def completions_takeTop_works() = { // Arrange - val compl = Completions(NoPosition, Seq(setA, setB, setC).map(s => (s.tag.label, s)).toMap) + val compl = Completions(Seq(setA, setB, setC)) // Act val lettersInOrder = Seq("a", "b", "c", "d", "e") @@ -31,7 +31,7 @@ class CompletionDefinitionsTest extends CompletionDefinitions { @Test def completions_setsScoredWithMaxCompletion_works() = { // Arrange - val compl = Completions(NoPosition, Seq(setA, setB, setC).map(s => (s.tag.label, s)).toMap) + val compl = Completions(Seq(setA, setB, setC)) // Act assertEquals(Seq("e", "c", "d", "a", "b"), compl.setsScoredWithMaxCompletion().completionStrings) From ed3b375e527b8361e15cfd5314d201a31c0575cf Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 14:10:10 +0100 Subject: [PATCH 04/13] added copyright notices and @author mentions --- .../completion/CompletionDefinitions.scala | 8 ++++++++ .../combinator/completion/CompletionParsers.scala | 8 ++++++++ .../completion/RegexCompletionParsers.scala | 14 ++++++++++++-- .../completion/CompletionDefinitionsTest.scala | 11 ++++++----- .../completion/CompletionForAlternativesTest.scala | 11 ++++++----- .../completion/CompletionForLongestMatchTest.scala | 3 +-- .../completion/CompletionForRepetitionTest.scala | 4 ++-- .../completion/CompletionForSequenceTest.scala | 4 ++-- .../CompletionForSimpleGrammarTest.scala | 4 ++-- .../completion/CompletionOperatorsTest.scala | 4 ++-- .../completion/CompletionTestDefinitions.scala | 4 ++-- .../completion/RecursiveGrammarTest.scala | 4 ++-- 12 files changed, 53 insertions(+), 26 deletions(-) diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala index 1977500a..420c0acf 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala @@ -1,3 +1,9 @@ +/* *\ +** 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} @@ -16,6 +22,8 @@ import scala.util.parsing.input.{NoPosition, Position} * * 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 CompletionDefinitions { type Elem diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala index 0d9aa40d..3e103240 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala @@ -1,3 +1,9 @@ +/* *\ +** 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 @@ -25,6 +31,8 @@ import scala.util.parsing.input.Positional * * A new set additional operators also allow overriding completions and specifying structural properties of completions * (tag, score, kind, etc.) for a `CompletionParser`. + * + * @author Jonas Chapuis */ trait CompletionParsers extends Parsers with CompletionDefinitions { def CompletionParser[T](f: Input => ParseResult[T], c: Input => Completions) = new CompletionParser[T] { diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala index a4612195..9b932a4b 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala @@ -1,10 +1,20 @@ +/* *\ +** 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} -/** - * Created by jchapuis on 30.01.2017. +/** 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 and can be specified + * with the `%>` operator. + * + * @author Jonas Chapuis */ trait RegexCompletionParsers extends RegexParsers with CompletionParsers { protected val areLiteralsCaseSensitive = false diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala index c7bdb050..2fe2b3f8 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala @@ -1,3 +1,9 @@ +/* *\ +** 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._ @@ -5,11 +11,6 @@ import org.junit.Test import scala.util.parsing.input.NoPosition -/** - * Copyright (c) by Nexthink S.A. - * Lausanne, Switzerland (http://www.nexthink.com) - * Created by Jonas on 09.01.2017. - */ class CompletionDefinitionsTest extends CompletionDefinitions { override type Elem = Char 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 index 70e861af..f2e31974 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala @@ -1,12 +1,13 @@ +/* *\ +** 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} -/** - * Copyright (c) by Nexthink S.A. - * Lausanne, Switzerland (http://www.nexthink.com) - * Created by Jonas on 13.01.2017. - */ class CompletionForAlternativesTest { val left = "left" val right = "right" 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 index 31ae225d..1d78d43b 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala @@ -1,8 +1,7 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** Copyright (c) by Nexthink S.A. ** ** Lausanne, Switzerland (http://www.nexthink.com) ** -** Author: jonas.chapuis@nexthink.com ** \* */ package scala.util.parsing.combinator.completion 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 index 1813a8b2..7872d1ee 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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, Test} 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 index 4406c47b..ad7f69b6 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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, Test} 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 index e2750ae8..d04fe9d6 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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.Test 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 index f7e17bee..a23058be 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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, Test} 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 index 0c0227c4..4eaf48eb 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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._ 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 index 3e3c9e53..eee1d181 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala @@ -1,9 +1,9 @@ /* *\ -** scala-parser-combinators completion fork ** +** scala-parser-combinators completion extensions ** ** 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._ From 32ad2ec14ffb872547255653e93f6f042e2cef27 Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 14:46:35 +0100 Subject: [PATCH 05/13] added a mention in the README --- README.md | 3 +++ .../parsing/combinator/completion/CompletionDefinitions.scala | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df187b4d..3ca19eb6 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ 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 +Deriving from the `CompletionParsers` trait enables completion support for a grammar, i.e. 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. + ## 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/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala index 420c0acf..33a4b0f9 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala @@ -8,12 +8,12 @@ package scala.util.parsing.combinator.completion import scala.util.parsing.input.{NoPosition, Position} -/** `CompletionDefinitions` provides types to specify structured completions for parsers. +/** `CompletionDefinitions` provides types to define structured completions for parsers. * 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` (+ a `description` and - * a `kind`, the latter allowing e.g. encoding visual attributes of the set). + * a `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`: From 2bc95acd13020d14b594ab9d45dd6010f563b860 Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 14:57:33 +0100 Subject: [PATCH 06/13] deleted .scalafmt.conf file --- .scalafmt.conf | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .scalafmt.conf diff --git a/.scalafmt.conf b/.scalafmt.conf deleted file mode 100644 index 4068f5d2..00000000 --- a/.scalafmt.conf +++ /dev/null @@ -1,2 +0,0 @@ -style = defaultWithAlign -maxColumn = 120 From 983dc9915b09cd3ec80788869edd0d96911f9793 Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 16:26:58 +0100 Subject: [PATCH 07/13] refactored the trait to be used as a 'CompletionSupport' mix-in --- .../util/parsing/combinator/Parsers.scala | 2 +- ...nParsers.scala => CompletionSupport.scala} | 312 +++++++++--------- ...efinitions.scala => CompletionTypes.scala} | 10 +- ...ers.scala => RegexCompletionSupport.scala} | 22 +- .../CompletionForAlternativesTest.scala | 4 +- .../completion/CompletionForLiteralTest.scala | 6 +- .../CompletionForLongestMatchTest.scala | 4 +- .../CompletionForRepetitionTest.scala | 4 +- .../CompletionForSequenceTest.scala | 4 +- .../CompletionForSimpleGrammarTest.scala | 4 +- .../completion/CompletionOperatorsTest.scala | 8 +- .../CompletionTestDefinitions.scala | 4 +- ...nsTest.scala => CompletionTypesTest.scala} | 2 +- .../completion/RecursiveGrammarTest.scala | 6 +- 14 files changed, 202 insertions(+), 190 deletions(-) rename shared/src/main/scala/scala/util/parsing/combinator/completion/{CompletionParsers.scala => CompletionSupport.scala} (73%) rename shared/src/main/scala/scala/util/parsing/combinator/completion/{CompletionDefinitions.scala => CompletionTypes.scala} (97%) rename shared/src/main/scala/scala/util/parsing/combinator/completion/{RegexCompletionParsers.scala => RegexCompletionSupport.scala} (77%) rename shared/src/test/scala/scala/util/parsing/combinator/completion/{CompletionDefinitionsTest.scala => CompletionTypesTest.scala} (95%) diff --git a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala index 882b8970..538a8961 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala @@ -218,7 +218,7 @@ trait Parsers { def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = this } - def Parser[T](f: Input => ParseResult[T]): Parser[T] + def Parser[T, P >: Parser[T]](f: Input => ParseResult[T]): P = new Parser[T]{ def apply(in: Input) = f(in) } def OnceParser[T](f: Input => ParseResult[T]): Parser[T] with OnceParser[T] diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala similarity index 73% rename from shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala rename to shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala index 3e103240..d3c41b95 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala @@ -10,49 +10,46 @@ import scala.annotation.tailrec import scala.util.parsing.combinator.Parsers import scala.util.parsing.input.Positional -/** `CompletionParsers` extends `Parsers` with completion capability. +/** `CompletionSupport` adds completion capability to parsers. * - * A `CompletionParsers` may define `CompletionParser` instances, - * which are combined to produce the desired parser with completion - * capability. - * - * [[scala.util.parsing.combinator.completion.CompletionParsers.CompletionParser]] extends + * When mixed-in, this trait extends * the [[scala.util.parsing.combinator.Parsers.Parser]] type with the abstract method - * [[scala.util.parsing.combinator.completion.CompletionParsers.CompletionParser#completions]] - * which returns a instance of [[scala.util.parsing.combinator.completion.CompletionDefinitions.Completions]] + * [[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 have been extended to cover the additional completion aspect, so that - * no change is required in the grammar itself. + * Combinators are overloaded to cover the additional completion aspect, so that no change is required in the grammar. * - * Using instances of `CompletionParser` instead of `Parser` will allow specifying possible completions - * for a certain parser (for instance the provided subclass [[scala.util.parsing.combinator.completion.RegexCompletionParsers]] - * defines completion behavior for a `literal` parser). + * 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 new set additional operators also allow overriding completions and specifying structural properties of completions - * (tag, score, kind, etc.) for a `CompletionParser`. + * 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 CompletionParsers extends Parsers with CompletionDefinitions { - def CompletionParser[T](f: Input => ParseResult[T], c: Input => Completions) = new CompletionParser[T] { +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, extending the `Parser` class. + override def Parser[T, P >: Parser[T]](f: (Input) => ParseResult[T]): P = + Parser(f, _ => Completions.empty) + + /** 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 CompletionParser[+T] extends super.Parser[T] { + abstract class Parser[+T] extends super.Parser[T] { - def append[U >: T](q: => CompletionParser[U]): CompletionParser[U] = { - lazy val p = q // lazy argument - CompletionParser( - in => super.append(q)(in), + 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 | q.completions(in) + lazy val combinedCompletions = thisCompletions | p.completions(in) this(in) match { case Success(_, rest) => // only return optional completions if they start at the last position, otherwise it can behave badly e.g. with fuzzy matching @@ -67,31 +64,32 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { } /** An unspecified method that defines the possible completions for this parser + * * @param in the input - * @return an instance of [[scala.util.parsing.combinator.completion.CompletionDefinitions.Completions]] + * @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 `CompletionParser` that upon invocation of the `completions` method returns the passed completions + * @return a `Parser` that upon invocation of the `completions` method returns the passed completions */ - def %>(completions: Elems*): CompletionParser[T] = + 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 `CompletionParser` that upon invocation of the `completions` method returns the passed completion + * @return a `Parser` that upon invocation of the `completions` method returns the passed completion */ - def %>(completion: Completion): CompletionParser[T] = + def %>(completion: Completion): Parser[T] = %>(Set(completion)) /** An operator to specify completions of a parser * @param completions possible completions for this parser - * @return a `CompletionParser` that upon invocation of the `completions` method returns the passed completions + * @return a `Parser` that upon invocation of the `completions` method returns the passed completions */ - def %>(completions: Iterable[Completion]): CompletionParser[T] = - CompletionParser(this, in => { + def %>(completions: Iterable[Completion]): Parser[T] = + Parser(this, in => { this(in) match { case Failure(_, rest) if rest.atEnd => Completions(rest.pos, CompletionSet(completions)) @@ -101,17 +99,17 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { /** An operator to specify completions of a parser * @param completioner function of input to completions - * @return a `CompletionParser` that upon invocation of the `completions` method will invoke the passed function + * @return a `Parser` that upon invocation of the `completions` method will invoke the passed function */ - def %>(completioner: Input => Completions): CompletionParser[T] = - CompletionParser(this, completioner) + 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 `CompletionParser` instance limiting the number of completions + * @return wrapper `Parser` instance limiting the number of completions */ - def topCompletions(n: Int): CompletionParser[T] = - CompletionParser( + def topCompletions(n: Int): Parser[T] = + Parser( this, in => { val completions = this.completions(in) @@ -123,92 +121,91 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { /** 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 `CompletionParser` instance specifying the completion tag + * @return wrapper `Parser` instance specifying the completion tag */ - def %(tag: String): CompletionParser[T] = - CompletionParser(this, in => updateCompletionsTag(this.completions(in), Some(tag), None, None, None)) + 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 `CompletionParser` instance specifying the completion tag score + * @return wrapper `Parser` instance specifying the completion tag score */ - def %(tagScore: Int): CompletionParser[T] = - CompletionParser(this, in => updateCompletionsTag(this.completions(in), None, Some(tagScore), None, None)) + 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 `CompletionParser` instance specifying the completion tag + * @return wrapper `Parser` instance specifying the completion tag */ - def %(tag: String, tagScore: Int): CompletionParser[T] = - CompletionParser(this, in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), None, None)) + 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 `CompletionParser` instance specifying completion tag + * @return wrapper `Parser` instance specifying completion tag */ - def %(tag: String, tagScore: Int, tagDescription: String): CompletionParser[T] = - CompletionParser( - this, - in => updateCompletionsTag(this.completions(in), Some(tag), Some(tagScore), Some(tagDescription), None)) + 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 `CompletionParser` instance specifying completion tag + * @return wrapper `Parser` instance specifying completion tag */ - def %(tag: String, tagScore: Int, tagDescription: String, tagKind: String): CompletionParser[T] = - CompletionParser( + 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 `CompletionParser` instance specifying completion tag + * @return wrapper `Parser` instance specifying completion tag */ - def %(tag: CompletionTag): CompletionParser[T] = - CompletionParser( + 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 `CompletionParser` instance specifying the completion description + * @return wrapper `Parser` instance specifying the completion description */ - def %?(tagDescription: String): CompletionParser[T] = - CompletionParser(this, in => updateCompletionsTag(this.completions(in), None, None, Some(tagDescription), None)) + 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 `CompletionParser` instance specifying the completion tag kind + * @return wrapper `Parser` instance specifying the completion tag kind */ - def %%(tagKind: String): CompletionParser[T] = - CompletionParser(this, in => updateCompletionsTag(this.completions(in), None, None, None, Some(tagKind))) + 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 `CompletionParser` instance specifying the completion kind + * @return wrapper `Parser` instance specifying the completion kind */ - def %-%(kind: String): CompletionParser[T] = - CompletionParser(this, in => updateCompletions(this.completions(in), Some(kind))) + def %-%(kind: String): Parser[T] = + Parser(this, in => updateCompletions(this.completions(in), Some(kind))) - def flatMap[U](f: T => CompletionParser[U]): CompletionParser[U] = - CompletionParser(super.flatMap(f), completions) + def flatMap[U](f: T => Parser[U]): Parser[U] = + Parser(super.flatMap(f), completions) - override def map[U](f: T => U): CompletionParser[U] = - CompletionParser(super.map(f), in => completions(in)) + override def map[U](f: T => U): Parser[U] = + Parser(super.map(f), completions) - override def filter(p: T => Boolean): CompletionParser[T] = withFilter(p) + override def filter(p: T => Boolean): Parser[T] = withFilter(p) - override def withFilter(p: T => Boolean): CompletionParser[T] = - CompletionParser(super.withFilter(p), completions) + override def withFilter(p: T => Boolean): Parser[T] = + Parser(super.withFilter(p), completions) - private def seqCompletions[U](in: Input, other: => CompletionParser[U]): Completions = { + private def seqCompletions[U](in: Input, other: => Parser[U]): Completions = { lazy val thisCompletions = this.completions(in) this(in) match { case Success(_, rest) => @@ -254,9 +251,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * 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: => CompletionParser[U]): CompletionParser[~[T, U]] = { + def ~[U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q - CompletionParser(super.~(q), in => seqCompletions(in, p)) + Parser(super.~(q), in => seqCompletions(in, p)) }.named("~") /** A parser combinator for sequential composition which keeps only the right result. @@ -267,9 +264,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * succeeds -- evaluated at most once, and only when necessary. * @return a `Parser` that -- on success -- returns the result of `q`. */ - def ~>[U](q: => CompletionParser[U]): CompletionParser[U] = { + def ~>[U](q: => Parser[U]): Parser[U] = { lazy val p = q - CompletionParser(super.~>(q), in => seqCompletions(in, p)) + Parser(super.~>(q), in => seqCompletions(in, p)) }.named("~>") /** A parser combinator for sequential composition which keeps only the left result. @@ -282,9 +279,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[U]): CompletionParser[T] = { + def <~[U](q: => Parser[U]): Parser[T] = { lazy val p = q - CompletionParser(super.<~(q), in => seqCompletions(in, p)) + Parser(super.<~(q), in => seqCompletions(in, p)) }.named("<~") /** A parser combinator for non-back-tracking sequential composition. @@ -297,9 +294,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * 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 ~![U](q: => CompletionParser[U]): CompletionParser[~[T, U]] = { + def ~![U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q - CompletionParser(super.~!(q), in => seqCompletions(in, p)) + Parser(super.~!(q), in => seqCompletions(in, p)) }.named("<~") /** A parser combinator for non-back-tracking sequential composition which only keeps the right result. @@ -311,9 +308,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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 ~>![U](q: => CompletionParser[U]): CompletionParser[U] = { + def ~>![U](q: => Parser[U]): Parser[U] = { lazy val p = q - CompletionParser(super.~>!(q), in => seqCompletions(in, p)) + Parser(super.~>!(q), in => seqCompletions(in, p)) }.named("~>!") /** A parser combinator for non-back-tracking sequential composition which only keeps the left result. @@ -325,9 +322,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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 <~![U](q: => CompletionParser[U]): CompletionParser[T] = { + def <~![U](q: => Parser[U]): Parser[T] = { lazy val p = q - CompletionParser(super.<~!(q), in => seqCompletions(in, p)) + Parser(super.<~!(q), in => seqCompletions(in, p)) }.named("<~!") /** A parser combinator for alternative composition. @@ -341,7 +338,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * - `p` succeeds, ''or'' * - if `p` fails allowing back-tracking and `q` succeeds. */ - def |[U >: T](q: => CompletionParser[U]): CompletionParser[U] = append(q).named("|") + def |[U >: T](q: => Parser[U]): Parser[U] = + append(q).named("|") /** A parser combinator for alternative with longest match composition. * @@ -351,9 +349,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[U]): CompletionParser[U] = { + def |||[U >: T](q: => Parser[U]): Parser[U] = { lazy val p = q - CompletionParser(super.|||(q), in => this.completions(in) | p.completions(in)) + Parser(super.|||(q), in => this.completions(in) | p.completions(in)) } /** A parser combinator for function application. @@ -364,8 +362,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[U] = - CompletionParser(super.^^(f), completions) + override def ^^[U](f: T => U): Parser[U] = + Parser(super.^^(f), completions) /** A parser combinator that changes a successful result into the specified value. * @@ -374,8 +372,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[U] = { - CompletionParser(super.^^^(v), completions) + override def ^^^[U](v: => U): Parser[U] = { + Parser(super.^^^(v), completions) }.named(toString + "^^^") /** A parser combinator for partial function application. @@ -391,8 +389,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[U] = - CompletionParser(super.^?(f, error), completions).named(toString + "^?") + 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. * @@ -404,8 +402,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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]): CompletionParser[U] = - CompletionParser(super.^?(f), completions) + 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. @@ -429,8 +427,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @return a parser that succeeds if this parser succeeds (with result `x`) * and if then `fq(x)` succeeds */ - override def into[U](fq: T => Parser[U]): CompletionParser[U] = - CompletionParser(super.into(fq), completions) + def into[U](fq: T => Parser[U]): Parser[U] = + Parser(super.into(fq), completions) /** Changes the failure message produced by a parser. * @@ -454,7 +452,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @return A parser with the same properties and different failure message. */ override def withErrorMessage(msg: String) = - CompletionParser(super.withErrorMessage(msg), completions) + Parser(super.withErrorMessage(msg), completions) } @@ -462,8 +460,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * will give up as soon as it encounters an error, on failure it simply * tries the next alternative). */ - def commit[T](p: => CompletionParser[T]): CompletionParser[T] = - CompletionParser(super.commit(p), p.completions) + def commit[T](p: => Parser[T]): Parser[T] = + Parser(super.commit(p), p.completions) /** A parser matching input elements that satisfy a given predicate. * @@ -474,7 +472,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @param completions Possible alternatives (for completion) * @return */ - def elem(kind: String, p: Elem => Boolean, completions: Seq[Elem] = Nil): CompletionParser[Elem] = + def elem(kind: String, p: Elem => Boolean, completions: Seq[Elem] = Nil): Parser[Elem] = acceptIf(p, if (completions.isEmpty) None @@ -491,7 +489,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[Elem] = + override implicit def accept(e: Elem): Parser[Elem] = acceptIf(_ == e, Some(CompletionSet(e)))("'" + e + "' expected but " + _ + " found") /** A parser that matches only the given list of element `es`. @@ -501,7 +499,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[List[Elem]] = acceptSeq(es) + 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`. * @@ -517,7 +516,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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()): CompletionParser[U] = + 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. @@ -529,20 +528,17 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @param completions Possible completions * @return A parser for elements satisfying p(e). */ - def acceptIf(p: Elem => Boolean, completions: Option[CompletionSet])(err: Elem => String): CompletionParser[Elem] = { - CompletionParser(super.acceptIf(p)(err), - in => completions.map(Completions(in.pos, _)).getOrElse(Completions.empty)) + def acceptIf(p: Elem => Boolean, completions: Option[CompletionSet])(err: Elem => String): Parser[Elem] = { + Parser(super.acceptIf(p)(err), in => completions.map(Completions(in.pos, _)).getOrElse(Completions.empty)) } - def acceptMatch[U](expected: String, - f: PartialFunction[Elem, U], - completions: Set[Completion]): CompletionParser[U] = { + 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 - CompletionParser(super.acceptMatch(expected, f), - in => completionSet.map(Completions(in.pos, _)).getOrElse(Completions.empty)) + Parser(super.acceptMatch(expected, f), + in => completionSet.map(Completions(in.pos, _)).getOrElse(Completions.empty)) .named(expected) } @@ -553,41 +549,43 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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): CompletionParser[List[Elem]] = - CompletionParser(super.acceptSeq(es), - in => - es.tail - .foldLeft(accept(es.head))((a, b) => a ~> accept(b)) - .completions(in)) + 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): CompletionParser[Nothing] = - CompletionParser(super.failure(msg), _ => Completions.empty) + 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): CompletionParser[T] = CompletionParser(super.success(v), _ => Completions.empty) + 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): CompletionParser[Nothing] = CompletionParser(super.err(msg), _ => Completions.empty) + 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: => CompletionParser[T])(name: String): CompletionParser[T] = - CompletionParser(super.log(p)(name), p.completions) + def log[T](p: => Parser[T])(name: String): Parser[T] = + Parser(super.log(p)(name), p.completions) /** A parser generator for repetitions. * @@ -597,7 +595,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T]): CompletionParser[List[T]] = rep1(p) | success(List()) + def rep[T](p: => Parser[T]): Parser[List[T]] = + rep1(p) | success(List()) /** A parser generator for interleaved repetitions. * @@ -611,7 +610,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T], q: => CompletionParser[Any]): CompletionParser[List[T]] = + def repsep[T](p: => Parser[T], q: => Parser[Any]): Parser[List[T]] = rep1sep(p, q) | success(List()) /** A parser generator for non-empty repetitions. @@ -623,7 +622,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T]): CompletionParser[List[T]] = rep1(p, p) + def rep1[T](p: => Parser[T]): Parser[List[T]] = + rep1(p, p) /** A parser generator for non-empty repetitions. * @@ -636,9 +636,9 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T], p0: => CompletionParser[T]): CompletionParser[List[T]] = { + def rep1[T](first: => Parser[T], p0: => Parser[T]): Parser[List[T]] = { lazy val p = p0 // lazy argument - CompletionParser( + Parser( super.rep1(first, p0), in => { @tailrec def continue(in: Input): Completions = { @@ -665,11 +665,11 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T]): CompletionParser[List[T]] = { + def repN[T](num: Int, p0: => Parser[T]): Parser[List[T]] = { lazy val p = p0 // lazy argument if (num == 0) success(Nil) else - CompletionParser( + Parser( super.repN(num, p0), in => { var parsedCount = 0 @@ -699,7 +699,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * (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: => CompletionParser[T], q: => CompletionParser[Any]): CompletionParser[List[T]] = + 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 @@ -713,7 +713,7 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T], q: => CompletionParser[(T, T) => T]): CompletionParser[T] = + 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 @@ -726,12 +726,11 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * yielding a left-associative function that combines two elements * into one */ - def chainl1[T, U](first: => CompletionParser[T], - p: => CompletionParser[U], - q: => CompletionParser[(T, U) => T]): CompletionParser[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 - } + 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 @@ -746,12 +745,11 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @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: => CompletionParser[T], - q: => CompletionParser[(T, U) => U], - combine: (T, U) => U, - first: U): CompletionParser[U] = p ~ rep(q ~ p) ^^ { - case x ~ xs => (new ~(combine, x) :: xs).foldRight(first) { case (f ~ a, b) => f(a, b) } - } + 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. * @@ -761,14 +759,14 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @return a `Parser` that always succeeds: either with the result provided by `p` or * with the empty result */ - def opt[T](p: => CompletionParser[T]): CompletionParser[Option[T]] = + 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: => CompletionParser[T]): CompletionParser[Unit] = - CompletionParser(super.not(p), _ => Completions.empty) + 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 @@ -778,8 +776,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @return A parser that returns success if and only if `p` succeeds but * never consumes any input */ - def guard[T](p: => CompletionParser[T]): CompletionParser[T] = - CompletionParser(super.guard(p), p.completions) + 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. @@ -789,8 +787,8 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * result with the start position of the input it consumed, * if it didn't already have a position. */ - def positioned[T <: Positional](p: => CompletionParser[T]): CompletionParser[T] = - CompletionParser(super.positioned(p), p.completions) + def positioned[T <: Positional](p: => Parser[T]): Parser[T] = + Parser(super.positioned(p), p.completions) /** A parser generator delimiting whole phrases (i.e. programs). * @@ -801,6 +799,6 @@ trait CompletionParsers extends Parsers with CompletionDefinitions { * @return a parser that has the same result as `p`, but that only succeeds * if `p` consumed all the input. */ - def phrase[T](p: CompletionParser[T]) = - CompletionParser(super.phrase(p), p.completions) + def phrase[T](p: Parser[T]) = + Parser(super.phrase(p), p.completions) } diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala similarity index 97% rename from shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala rename to shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala index 33a4b0f9..883e050e 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionDefinitions.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala @@ -8,12 +8,12 @@ package scala.util.parsing.combinator.completion import scala.util.parsing.input.{NoPosition, Position} -/** `CompletionDefinitions` provides types to define structured completions for parsers. +/** 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` (+ a `description` and - * a `kind`, the latter allowing e.g. encoding visual attributes for the set). + * 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`: @@ -21,11 +21,11 @@ import scala.util.parsing.input.{NoPosition, Position} * 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 + * `Completions` instances create a unique default set with an empty tag. * * @author Jonas Chapuis */ -trait CompletionDefinitions { +trait CompletionTypes { type Elem val DefaultCompletionTag = "" diff --git a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala similarity index 77% rename from shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala rename to shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala index 9b932a4b..39fe62da 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionParsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala @@ -11,12 +11,12 @@ import scala.util.parsing.input.{CharSequenceReader, OffsetPosition, Positional, /** 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 and can be specified + * Note that completions for the `regex` parser are undefined by default and can be specified * with the `%>` operator. * * @author Jonas Chapuis */ -trait RegexCompletionParsers extends RegexParsers with CompletionParsers { +trait RegexCompletionSupport extends RegexParsers with CompletionSupport { protected val areLiteralsCaseSensitive = false protected def dropWhiteSpace(input: Input): Input = @@ -41,8 +41,8 @@ trait RegexCompletionParsers extends RegexParsers with CompletionParsers { (literalPos, sourcePos) } - abstract override implicit def literal(s: String): CompletionParser[String] = - CompletionParser[String]( + abstract override implicit def literal(s: String): Parser[String] = + Parser[String]( super.literal(s), in => { lazy val literalCompletion = @@ -60,24 +60,24 @@ trait RegexCompletionParsers extends RegexParsers with CompletionParsers { } ) - abstract override implicit def regex(r: Regex): CompletionParser[String] = - CompletionParser(super.regex(r), _ => Completions.empty) + abstract override implicit def regex(r: Regex): Parser[String] = + Parser(super.regex(r), _ => Completions.empty) - override def positioned[T <: Positional](p: => CompletionParser[T]): CompletionParser[T] = { + override def positioned[T <: Positional](p: => Parser[T]): Parser[T] = { lazy val q = p - CompletionParser[T](super.positioned(p), q.completions) + Parser[T](super.positioned(p), q.completions) } /** Returns completions for read `in` with parser `p`. */ - def complete[T](p: CompletionParser[T], in: Reader[Char]): Completions = + 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: CompletionParser[T], in: CharSequence): Completions = + 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: CompletionParser[T], input: String): Seq[String] = + 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/CompletionForAlternativesTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala index f2e31974..050ee6be 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala @@ -8,12 +8,14 @@ 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 RegexCompletionParsers { + object TestParser extends Parsers with RegexCompletionSupport { val alternativesWithCommonFirstParser = common ~ left | common ~! right val alternativesWithCommonPrefix = (common+left) | common ~ right } 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 index a5bdf9db..71c4e873 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala @@ -9,13 +9,15 @@ 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 RegexCompletionParsers { - val literal: CompletionParser[String] = someLiteral + object Parser extends Parsers with RegexCompletionSupport { + val literal: Parser[String] = someLiteral val combination = someLiteral | otherLiteralWithSamePrefix } 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 index 1d78d43b..a32d2066 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala @@ -8,11 +8,13 @@ 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 RegexCompletionParsers { + object Parsers extends Parsers with RegexCompletionSupport { val samePrefix = foo ||| foo ~ bar val constrainedAndOpenAlternatives = foo ~ bar ||| (".{5,}".r %> Completion("sample string longer than 5 char")) } 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 index 7872d1ee..18bb0e1a 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala @@ -8,12 +8,14 @@ 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 RegexCompletionParsers { + object TestParser extends Parsers with RegexCompletionSupport { val repSequence = rep(repeated) val repSepSequence = repsep(repeated, separator) val error = repsep(repeated, err("some error")) 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 index ad7f69b6..ed3b264e 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala @@ -8,6 +8,8 @@ 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" @@ -15,7 +17,7 @@ class CompletionForSequenceTest { val as = "as" val df = "df" - object TestParser extends RegexCompletionParsers { + object TestParser extends Parsers with RegexCompletionSupport { val sequence = left ~> (foo | bar) val subSeqLeft = foo | foo ~ bar 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 index d04fe9d6..9a8f3141 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala @@ -14,8 +14,8 @@ class CompletionForSimpleGrammarTest { object SimpleGrammar extends CompletionTestParser { val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number" - def expr: CompletionParser[Int] = term | "(" ~> term <~ ")" - def term: CompletionParser[Int] = number ^^ { + def expr: Parser[Int] = term | "(" ~> term <~ ")" + def term: Parser[Int] = number ^^ { _.toInt } } 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 index a23058be..41a7060b 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala @@ -8,10 +8,12 @@ package scala.util.parsing.combinator.completion import org.junit.{Assert, Test} +import scala.util.parsing.combinator.Parsers + class CompletionOperatorsTest { - object TestParser extends RegexCompletionParsers { - val someParser: CompletionParser[String] = "parser" + object TestParser extends Parsers with RegexCompletionSupport { + val someParser: Parser[String] = "parser" } @Test @@ -48,7 +50,7 @@ class CompletionOperatorsTest { Some(kind)) } - def assertCompletionsMatch[T](sut: TestParser.CompletionParser[T], + def assertCompletionsMatch[T](sut: TestParser.Parser[T], completions: Seq[Seq[Char]], tag: Option[String], score: Option[Int], 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 index 4eaf48eb..89002a1c 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTestDefinitions.scala @@ -8,6 +8,8 @@ package scala.util.parsing.combinator.completion import org.junit.Assert._ +import scala.util.parsing.combinator.Parsers + object CompletionTestDefinitions { trait AssertionSet{ def tag: String @@ -23,7 +25,7 @@ object CompletionTestDefinitions { } } -trait CompletionTestParser extends RegexCompletionParsers { +trait CompletionTestParser extends Parsers with RegexCompletionSupport { import CompletionTestDefinitions._ def assertSetEquals(expected: AssertionSet, actual: CompletionSet): Unit = expected match { diff --git a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala similarity index 95% rename from shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala rename to shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala index 2fe2b3f8..9e83ec8d 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionDefinitionsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala @@ -11,7 +11,7 @@ import org.junit.Test import scala.util.parsing.input.NoPosition -class CompletionDefinitionsTest extends CompletionDefinitions { +class CompletionTypesTest extends CompletionTypes { override type Elem = Char val setA = CompletionSet(CompletionTag("A", 10), Set(Completion("a", 2), Completion("b", 1))) 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 index eee1d181..d624389b 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala @@ -14,13 +14,13 @@ class RecursiveGrammarTest { object ExprParser extends CompletionTestParser { val number = "[0-9]+".r %> ("1", "10", "99") % "number" %? "any number" - lazy val expr: CompletionParser[Int] = term ~ rep( + 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: CompletionParser[Int] = factor ~ rep(multiplicationDivisionOperators ~! factor) ^^ { + lazy val term: Parser[Int] = factor ~ rep(multiplicationDivisionOperators ~! factor) ^^ { case f ~ Nil => f case f ~ r => r.foldLeft(f) { @@ -28,7 +28,7 @@ class RecursiveGrammarTest { case (prev, "/" ~ next) => prev / next } } - lazy val factor: CompletionParser[Int] = number ^^ { _.toInt } | "(" ~> expr <~ ")" + lazy val factor: Parser[Int] = number ^^ { _.toInt } | "(" ~> expr <~ ")" } @Test From 11f930c8b1b58a087db3306776497d36924fb2ba Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 16:34:52 +0100 Subject: [PATCH 08/13] updated README file to the newest mix-in design --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ca19eb6..256e6646 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,14 @@ 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 -Deriving from the `CompletionParsers` trait enables completion support for a grammar, i.e. 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. +Mix-in the `CompletionSupport` trait enables completion support for a grammar (`RegexCompletionSupport` for `RegexParsers`), like so: + +```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 From edad55e30f6bfde2abeb96dc16ae1ee7f19d87e9 Mon Sep 17 00:00:00 2001 From: Jonas Chapuis Date: Fri, 10 Feb 2017 16:36:48 +0100 Subject: [PATCH 09/13] minor documentation correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 256e6646..054ed7ac 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 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 -Mix-in the `CompletionSupport` trait enables completion support for a grammar (`RegexCompletionSupport` for `RegexParsers`), like so: +Mixing-in the `CompletionSupport` trait enables completion support for a grammar (use `RegexCompletionSupport` for `RegexParsers`): ```scala object MyParsers extends RegexParsers with RegexCompletionSupport From 53f9b0848a5a7cf250ec29be9463d6d48937e969 Mon Sep 17 00:00:00 2001 From: jchapuis Date: Mon, 13 Feb 2017 10:16:44 +0100 Subject: [PATCH 10/13] change in Parsers.scala is no longer needed --- .../main/scala/scala/util/parsing/combinator/Parsers.scala | 2 +- .../parsing/combinator/completion/CompletionSupport.scala | 3 --- .../combinator/completion/RegexCompletionSupport.scala | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala index 538a8961..882b8970 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala @@ -218,7 +218,7 @@ trait Parsers { def append[U >: Nothing](a: => ParseResult[U]): ParseResult[U] = this } - def Parser[T, P >: Parser[T]](f: Input => ParseResult[T]): P + def Parser[T](f: Input => ParseResult[T]): Parser[T] = new Parser[T]{ def apply(in: Input) = f(in) } def OnceParser[T](f: Input => ParseResult[T]): Parser[T] with OnceParser[T] 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 index d3c41b95..47661c66 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala @@ -34,9 +34,6 @@ trait CompletionSupport extends Parsers with CompletionTypes { def completions(in: Input) = c(in) } - override def Parser[T, P >: Parser[T]](f: (Input) => ParseResult[T]): P = - Parser(f, _ => Completions.empty) - /** 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` 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 index 39fe62da..8fbbc955 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/RegexCompletionSupport.scala @@ -44,7 +44,7 @@ trait RegexCompletionSupport extends RegexParsers with CompletionSupport { abstract override implicit def literal(s: String): Parser[String] = Parser[String]( super.literal(s), - in => { + (in: Input) => { lazy val literalCompletion = Completions(OffsetPosition(in.source, handleWhiteSpace(in)), CompletionSet(Completion(s))) val (literalOffset, sourceOffset) = findMatchOffsets(s, in) @@ -65,7 +65,7 @@ trait RegexCompletionSupport extends RegexParsers with CompletionSupport { override def positioned[T <: Positional](p: => Parser[T]): Parser[T] = { lazy val q = p - Parser[T](super.positioned(p), q.completions) + Parser[T](super.positioned(p), in => q.completions(in)) } /** Returns completions for read `in` with parser `p`. */ From d794b27d3927eed4882f01334b706a675fad1518 Mon Sep 17 00:00:00 2001 From: jchapuis Date: Tue, 14 Feb 2017 16:28:36 +0100 Subject: [PATCH 11/13] improved coverage, added some missing overloads in CompletionSupport and minor corrections --- .../completion/CompletionSupport.scala | 110 +++++++++++++++--- .../completion/CompletionTypes.scala | 1 + .../CompletionForAcceptAndElemTest.scala | 82 +++++++++++++ .../CompletionForAlternativesTest.scala | 6 +- .../completion/CompletionForChainTest.scala | 41 +++++++ .../completion/CompletionForLiteralTest.scala | 14 +-- .../CompletionForLongestMatchTest.scala | 8 +- .../CompletionForRepetitionTest.scala | 18 +-- .../CompletionForSequenceTest.scala | 14 +-- .../CompletionForSimpleGrammarTest.scala | 14 +-- .../completion/CompletionOperatorsTest.scala | 87 +++++++++----- .../completion/CompletionTypesTest.scala | 4 +- .../completion/RecursiveGrammarTest.scala | 14 +-- 13 files changed, 327 insertions(+), 86 deletions(-) create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAcceptAndElemTest.scala create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForChainTest.scala 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 index 47661c66..dd9e407d 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala @@ -360,7 +360,7 @@ trait CompletionSupport extends Parsers with CompletionTypes { * transformed by `f`. */ override def ^^[U](f: T => U): Parser[U] = - Parser(super.^^(f), completions) + Parser(super.^^(f), completions).named(toString + "^^") /** A parser combinator that changes a successful result into the specified value. * @@ -427,6 +427,59 @@ trait CompletionSupport extends Parsers with CompletionTypes { def into[U](fq: T => Parser[U]): Parser[U] = Parser(super.into(fq), completions) + /** 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 @@ -469,13 +522,17 @@ trait CompletionSupport extends Parsers with CompletionTypes { * @param completions Possible alternatives (for completion) * @return */ - def elem(kind: String, p: Elem => Boolean, completions: Seq[Elem] = Nil): Parser[Elem] = - acceptIf(p, - if (completions.isEmpty) - None - else - Some(CompletionSet(CompletionTag(kind), completions.map(c => Completion(c)).toSet)))(inEl => - kind + " expected") + 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`. * @@ -487,7 +544,7 @@ trait CompletionSupport extends Parsers with CompletionTypes { * @return a `tParser` that succeeds if `e` is the next available input. */ override implicit def accept(e: Elem): Parser[Elem] = - acceptIf(_ == e, Some(CompletionSet(e)))("'" + e + "' expected but " + _ + " found") + acceptIf(_ == e, Set(e))("'" + e + "' expected but " + _ + " found") /** A parser that matches only the given list of element `es`. * @@ -525,8 +582,24 @@ trait CompletionSupport extends Parsers with CompletionTypes { * @param completions Possible completions * @return A parser for elements satisfying p(e). */ - def acceptIf(p: Elem => Boolean, completions: Option[CompletionSet])(err: Elem => String): Parser[Elem] = { - Parser(super.acceptIf(p)(err), in => completions.map(Completions(in.pos, _)).getOrElse(Completions.empty)) + 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] = { @@ -534,9 +607,18 @@ trait CompletionSupport extends Parsers with CompletionTypes { if (completions.nonEmpty) Some(CompletionSet(CompletionTag(expected), completions)) else None - Parser(super.acceptMatch(expected, f), - in => completionSet.map(Completions(in.pos, _)).getOrElse(Completions.empty)) - .named(expected) + 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`. 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 index 883e050e..10302d14 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionTypes.scala @@ -128,6 +128,7 @@ trait CompletionTypes { 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 = { 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 index 050ee6be..437accf8 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForAlternativesTest.scala @@ -21,15 +21,15 @@ class CompletionForAlternativesTest { } @Test - def empty_completes_toCommon = + def emptyCompletesToCommon = Assert.assertEquals(Seq(common), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, "")) @Test - def common_completes_toLeftAndRight = + def commonCompletesToLeftAndRight = Assert.assertEquals(Seq(left, right), TestParser.completeString(TestParser.alternativesWithCommonFirstParser, common)) @Test - def commonPrefix_completes_toRightSinceCompletionPositionsAreDifferent = + 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/CompletionForLiteralTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala index 71c4e873..84b4b773 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLiteralTest.scala @@ -23,21 +23,21 @@ class CompletionForLiteralTest { } @Test - def prefix_completes_toLiteral = { + def prefixCompletesToLiteral = { val completion = Parser.complete(Parser.literal, " " + someLiteralPrefix) assertEquals(2, completion.position.column) assertEquals(Seq(someLiteral), completion.completionStrings) } @Test - def prefix_combinationCompletes_toBothAlternatives = { + def prefixCombinationCompletesToBothAlternatives = { val completion = Parser.completeString(Parser.combination, someLiteralPrefix) assertEquals(Seq(otherLiteralWithSamePrefix, someLiteral), completion) } @Test - def partialOther_completes_toOther = { + def partialOtherCompletesToOther = { val completion = Parser.completeString( Parser.combination, someLiteralPrefix + otherLiteralWithSamePrefix @@ -47,7 +47,7 @@ class CompletionForLiteralTest { } @Test - def whitespace_completes_toLiteral = { + def whitespaceCompletesToLiteral = { val completion = Parser.complete(Parser.literal, List.fill(2)(" ").mkString) assertEquals(3, completion.position.column) @@ -55,20 +55,20 @@ class CompletionForLiteralTest { } @Test - def empty_completes_toLiteral = { + def emptyCompletesToLiteral = { val completion = Parser.complete(Parser.literal, "") assertEquals(1, completion.position.column) assertEquals(Seq(someLiteral), completion.completionStrings) } @Test - def other_completes_toNothing = + def otherCompletesToNothing = assertEquals( Map(), Parser.complete(Parser.literal, otherLiteralWithSamePrefix).sets) @Test - def completeLiteral_completes_toEmpty = + 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 index a32d2066..c5023cef 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForLongestMatchTest.scala @@ -20,21 +20,21 @@ class CompletionForLongestMatchTest { } @Test - def normallyProblematicallyOrderedAlternatives_parse_correctly = { + def normallyProblematicallyOrderedAlternativesParseCorrectly = { assertTrue(Parsers.parseAll(Parsers.samePrefix, foo).successful) assertTrue(Parsers.parseAll(Parsers.samePrefix, foo + bar).successful) // would be false with | } @Test - def empty_completesTo_alternatives = + def emptyCompletesToAlternatives = assertEquals(Seq(foo), Parsers.completeString(Parsers.samePrefix, "")) @Test - def partialLongerAlternative_completesTo_LongerAlternative = + def partialLongerAlternativeCompletesToLongerAlternative = assertEquals(Seq(bar), Parsers.completeString(Parsers.samePrefix, foo)) @Test - def longestParse_providesCompletion = + 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 index 18bb0e1a..a795cf79 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala @@ -23,38 +23,38 @@ class CompletionForRepetitionTest { } @Test - def empty_repCompletes_toRepeated = + def emptyRepCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, "")) @Test - def nInstancesAndPartial_repCompletes_toRepeated = + def nInstancesAndPartialRepCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3))) @Test - def nInstancesOfRepeated_repNCompletes_toRepeated = + def nInstancesOfRepeatedRepNCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString)) @Test - def nInstancesPartialComplete_repNCompletes_toRepeated = + def nInstancesPartialCompleteRepNCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3))) @Test - def nInstancesFollowedByError_repCompletes_toNothing = + def nInstancesFollowedByErrorRepCompletesToNothing = Assert.assertEquals(Nil, TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + "error")) @Test - def empty_repSepCompletes_toRepeated = + def emptyRepSepCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, "")) @Test - def repeatedAndSeparator_repSepCompletes_toRepeated = + def repeatedAndSeparatorRepSepCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSepSequence, repeated+separator)) @Test - def error_repSepCompletes_ToNothing = + def errorRepSepCompletesToNothing = Assert.assertEquals(Nil, TestParser.completeString(TestParser.error, repeated)) @Test - def empty_repNCompletes_ToRepeated = + def emptyRepNCompletesToRepeated = Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repNSequence, "")) } 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 index ed3b264e..b6d53439 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala @@ -26,34 +26,34 @@ class CompletionForSequenceTest { } @Test - def empty_completes_toLeft = + def emptyCompletesToLeft = Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, "")) @Test - def partialLeft_completes_toLeft = + def partialLeftCompletesToLeft = Assert.assertEquals(Seq(left), TestParser.completeString(TestParser.sequence, left.dropRight(2))) @Test - def completeLeft_completes_toRightAlternatives = { + 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 completeLeftAndRight_completes_toNothing = + def completeLeftAndRightCompletesToNothing = Assert.assertEquals(Nil, TestParser.completeString(TestParser.sequence, left + " " + bar)) @Test - def empty_composedCompletes_toLeft = + def emptyComposedCompletesToLeft = Assert.assertEquals(Seq(foo), TestParser.completeString(TestParser.composedSequence, "")) @Test - def left_composedCompletes_toLeftRemainingAlternativeAndRight = + def leftComposedCompletesToLeftRemainingAlternativeAndRight = Assert.assertEquals(Seq(as, bar, df), TestParser.completeString(TestParser.composedSequence, foo)) @Test - def completeLeft_composedCompletes_ToCorrectRightAlternative = + 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 index 9a8f3141..ecf4927e 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSimpleGrammarTest.scala @@ -21,47 +21,47 @@ class CompletionForSimpleGrammarTest { } @Test - def empty_completes_toNumberOrParen() = + def emptyCompletesToNumberOrParen() = SimpleGrammar.assertHasCompletions( Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), SimpleGrammar.complete(SimpleGrammar.expr, "")) @Test - def invalid_completes_toNothing() = + def invalidCompletesToNothing() = SimpleGrammar.assertHasCompletions( Set(), SimpleGrammar.complete(SimpleGrammar.expr, "invalid")) @Test - def leftParen_completes_toNumber() = + def leftParenCompletesToNumber() = SimpleGrammar.assertHasCompletions( Set(Tagged("number", Some("any number"), 0, "1", "10", "99")), SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), "(")) @Test - def leftParenAndNumber_completes_toRightParen() = + def leftParenAndNumberCompletesToRightParen() = SimpleGrammar.assertHasCompletions( Set(Default(")")), SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), "(8")) @Test - def leftParenAndInvalid_completes_toNothing() = + def leftParenAndInvalidCompletesToNothing() = SimpleGrammar.assertHasCompletions( Set(), SimpleGrammar.complete(SimpleGrammar.log(SimpleGrammar.expr)("expr"), "(invalid")) @Test - def parenNumber_completes_toEmpty() = + def parenNumberCompletesToEmpty() = SimpleGrammar.assertHasCompletions( Set(), SimpleGrammar.complete(SimpleGrammar.expr, "(56) ")) @Test - def number_completes_toEmpty() = + 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 index 41a7060b..6c1c4e5e 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionOperatorsTest.scala @@ -17,37 +17,57 @@ class CompletionOperatorsTest { } @Test - def completion_specifiedWithBuilder_isCorrect = { + 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" + 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 %% 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), + 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: _*) % tag % score %? description %% kind, + TestParser.someParser %> (completions: _*) % TestParser.CompletionTag(tag, score, Some(description), Some(kind)), completions, Some(tag), Some(score), Some(description), - Some(kind)) + 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], @@ -70,18 +90,33 @@ class CompletionOperatorsTest { } @Test - def unioning_completionSets_scoresMergedItemsOffsetBySetScore = { + 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 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]) + 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/CompletionTypesTest.scala b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala index 9e83ec8d..21f492f5 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionTypesTest.scala @@ -19,7 +19,7 @@ class CompletionTypesTest extends CompletionTypes { val setC = CompletionSet("C", Completion("e", 10)) @Test - def completions_takeTop_works() = { + def completionsTakeTopWorks() = { // Arrange val compl = Completions(Seq(setA, setB, setC)) @@ -30,7 +30,7 @@ class CompletionTypesTest extends CompletionTypes { } @Test - def completions_setsScoredWithMaxCompletion_works() = { + def completionsSetsScoredWithMaxCompletionWorks() = { // Arrange val compl = Completions(Seq(setA, setB, setC)) 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 index d624389b..4bc95799 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/RecursiveGrammarTest.scala @@ -32,7 +32,7 @@ class RecursiveGrammarTest { } @Test - def expressions_parse_correctly() = { + 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) @@ -41,33 +41,33 @@ class RecursiveGrammarTest { } @Test - def empty_completes_toNumberOrParen() = + def emptyCompletesToNumberOrParen() = ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), ExprParser.complete(ExprParser.expr, "")) @Test - def number_completes_toOperators() = + def numberCompletesToOperators() = ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")), ExprParser.complete(ExprParser.expr, "2")) @Test - def numberAndOperation_completes_toNumberOrParen() = + def numberAndOperationCompletesToNumberOrParen() = ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), ExprParser.complete(ExprParser.expr, "2*")) @Test - def paren_completes_toNumberAndParen() = + def parenCompletesToNumberAndParen() = ExprParser.assertHasCompletions(Set(Tagged("number", Some("any number"), 0, "1", "10", "99"), Default("(")), ExprParser.complete(ExprParser.expr, "(")) @Test - def recursiveParenAndNumber_completes_toOperatorsOrParen() = + def recursiveParenAndNumberCompletesToOperatorsOrParen() = ExprParser.assertHasCompletions( Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/"), Default(")")), ExprParser.complete(ExprParser.expr, "(((2")) @Test - def closedParent_completes_toOperators() = + def closedParentCompletesToOperators() = ExprParser.assertHasCompletions(Set(Tagged("operators", Some("arithmetic operators"), 10, "*", "+", "-", "/")), ExprParser.complete(ExprParser.expr, "(5*2/2)")) } From 01d690fe9152406f2fe4a0addac03e6255839c1e Mon Sep 17 00:00:00 2001 From: jchapuis Date: Wed, 1 Mar 2017 17:30:57 +0100 Subject: [PATCH 12/13] rep combinators were omitting the current possible completions which was causing missing completions with repeated parsers on nested alternatives with common prefixes, added a test to examplify the case --- .../completion/CompletionSupport.scala | 31 ++++++++------ .../CompletionForRepetitionTest.scala | 40 ++++++++++++++----- .../CompletionForSequenceTest.scala | 2 +- 3 files changed, 50 insertions(+), 23 deletions(-) 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 index dd9e407d..58c1292d 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala @@ -49,7 +49,7 @@ trait CompletionSupport extends Parsers with CompletionTypes { lazy val combinedCompletions = thisCompletions | p.completions(in) this(in) match { case Success(_, rest) => - // only return optional completions if they start at the last position, otherwise it can behave badly e.g. with fuzzy matching + // 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 @@ -720,15 +720,17 @@ trait CompletionSupport extends Parsers with CompletionTypes { Parser( super.rep1(first, p0), in => { - @tailrec def continue(in: Input): Completions = { + def continue(in: Input): Completions = { + val currentCompletions = p.completions(in) p(in) match { - case Success(_, rest) => continue(rest) - case NoSuccess(_, _) => p.completions(in) + case Success(_, rest) => currentCompletions | continue(rest) + case NoSuccess(_, _) => currentCompletions } } + val firstCompletions = first.completions(in) first(in) match { - case Success(_, rest) => continue(rest) - case NoSuccess(_, _) => first.completions(in) + case Success(_, rest) => firstCompletions | continue(rest) + case NoSuccess(_, _) => firstCompletions } } ) @@ -746,24 +748,27 @@ trait CompletionSupport extends Parsers with CompletionTypes { */ def repN[T](num: Int, p0: => Parser[T]): Parser[List[T]] = { lazy val p = p0 // lazy argument - if (num == 0) success(Nil) - else + if (num == 0) { success(Nil) } else { Parser( super.repN(num, p0), in => { var parsedCount = 0 - @tailrec def completions(in0: Input): Completions = - if (parsedCount == num) Completions.empty - else + def completions(in0: Input): Completions = + if (parsedCount == num) { + Completions.empty + } else { + val currentCompletions = p.completions(in0) p(in0) match { - case Success(_, rest) => parsedCount += 1; completions(rest) - case ns: NoSuccess => p.completions(in0) + 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. 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 index a795cf79..d8107127 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForRepetitionTest.scala @@ -11,15 +11,21 @@ import org.junit.{Assert, Test} import scala.util.parsing.combinator.Parsers class CompletionForRepetitionTest { - val repeated = "repeated" + val repeated = "repeated" val separator = "separator" - val n = 5 + val n = 5 object TestParser extends Parsers with RegexCompletionSupport { - val repSequence = rep(repeated) + val repSequence = rep(repeated) val repSepSequence = repsep(repeated, separator) - val error = repsep(repeated, err("some error")) - val repNSequence = repN(5, repeated) + 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 @@ -28,19 +34,25 @@ class CompletionForRepetitionTest { @Test def nInstancesAndPartialRepCompletesToRepeated = - Assert.assertEquals(Seq(repeated), TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + repeated.dropRight(3))) + 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)) + 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))) + 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")) + Assert.assertEquals(Nil, + TestParser.completeString(TestParser.repSequence, List.fill(3)(repeated).mkString + "error")) @Test def emptyRepSepCompletesToRepeated = @@ -57,4 +69,14 @@ class CompletionForRepetitionTest { @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 index b6d53439..be816550 100644 --- a/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala +++ b/shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForSequenceTest.scala @@ -20,7 +20,7 @@ class CompletionForSequenceTest { object TestParser extends Parsers with RegexCompletionSupport { val sequence = left ~> (foo | bar) - val subSeqLeft = foo | foo ~ bar + val subSeqLeft = foo ~ bar | foo val subSeqRight = as ~ df | df ~ as val composedSequence = subSeqLeft ~ subSeqRight } From a644f3925e0b94947df52b4d4d25dc3ea2ae4e64 Mon Sep 17 00:00:00 2001 From: jchapuis Date: Mon, 6 Mar 2017 16:43:53 +0100 Subject: [PATCH 13/13] corrected completions for 'into' combinator --- .../completion/CompletionSupport.scala | 7 +++- .../completion/CompletionForIntoTest.scala | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 shared/src/test/scala/scala/util/parsing/combinator/completion/CompletionForIntoTest.scala 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 index 58c1292d..45419165 100644 --- a/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala +++ b/shared/src/main/scala/scala/util/parsing/combinator/completion/CompletionSupport.scala @@ -425,7 +425,12 @@ trait CompletionSupport extends Parsers with CompletionTypes { * and if then `fq(x)` succeeds */ def into[U](fq: T => Parser[U]): Parser[U] = - Parser(super.into(fq), completions) + 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) 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) + } +}