redo Command to use Parser

nested commands still need work
This commit is contained in:
Mark Harrah 2011-01-22 14:01:59 -05:00
parent bcc8c37f4d
commit d49706b297
8 changed files with 282 additions and 158 deletions

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah * Copyright 2010 Mark Harrah
*/ */
package sbt.parse package sbt.complete
/** /**
* Represents a set of completions. * Represents a set of completions.
@ -72,9 +72,8 @@ sealed trait Completion
override final lazy val hashCode = Completion.hashCode(this) override final lazy val hashCode = Completion.hashCode(this)
override final def equals(o: Any) = o match { case c: Completion => Completion.equal(this, c); case _ => false } override final def equals(o: Any) = o match { case c: Completion => Completion.equal(this, c); case _ => false }
} }
final class DisplayOnly(display0: String) extends Completion final class DisplayOnly(val display: String) extends Completion
{ {
lazy val display = display0
def isEmpty = display.isEmpty def isEmpty = display.isEmpty
def append = "" def append = ""
override def toString = "{" + display + "}" override def toString = "{" + display + "}"

View File

@ -83,4 +83,19 @@ object HistoryCommands
else else
history ! s history ! s
} }
/*
import parse.{Parser,Parsers}
import Parser._
import Parsers._
val historyParser: Parser[complete.History => Option[String]] =
{
Start ~> Specific)
}
!! Execute the last command again
!: Show all previous commands
!:n Show the last n commands
!n Execute the command with index n, as shown by the !: command
!-n Execute the nth command before this one
!string Execute the most recent command starting with 'string'
!?string*/
} }

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah * Copyright 2011 Mark Harrah
*/ */
package sbt.parse package sbt.complete
import jline.{CandidateListCompletionHandler,Completor,CompletionHandler,ConsoleReader} import jline.{CandidateListCompletionHandler,Completor,CompletionHandler,ConsoleReader}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -41,14 +41,17 @@ object JLineCompletion
customCompletor(str => convertCompletions(Parser.completions(p, str))) customCompletor(str => convertCompletions(Parser.completions(p, str)))
def convertCompletions(c: Completions): (Seq[String], Seq[String]) = def convertCompletions(c: Completions): (Seq[String], Seq[String]) =
{ {
( (Seq[String](), Seq[String]()) /: c.get) { case ( t @ (insert,display), comp) => val (insert, display) =
if(comp.isEmpty) t else (insert :+ comp.append, insert :+ comp.display) ( (Set.empty[String], Set.empty[String]) /: c.get) { case ( t @ (insert,display), comp) =>
} if(comp.isEmpty) t else (insert + comp.append, appendNonEmpty(display, comp.display.trim))
}
(insert.toSeq, display.toSeq.sorted)
} }
def appendNonEmpty(set: Set[String], add: String) = if(add.isEmpty) set else set + add
def customCompletor(f: String => (Seq[String], Seq[String])): ConsoleReader => Boolean = def customCompletor(f: String => (Seq[String], Seq[String])): ConsoleReader => Boolean =
reader => { reader => {
val success = complete(beforeCursor(reader), f, reader, false) val success = complete(beforeCursor(reader), f, reader)
reader.flushConsole() reader.flushConsole()
success success
} }
@ -59,29 +62,34 @@ object JLineCompletion
b.getBuffer.substring(0, b.cursor) b.getBuffer.substring(0, b.cursor)
} }
def complete(beforeCursor: String, completions: String => (Seq[String],Seq[String]), reader: ConsoleReader, inserted: Boolean): Boolean = // returns false if there was nothing to insert and nothing to display
def complete(beforeCursor: String, completions: String => (Seq[String],Seq[String]), reader: ConsoleReader): Boolean =
{ {
val (insert,display) = completions(beforeCursor) val (insert,display) = completions(beforeCursor)
if(insert.isEmpty) val common = commonPrefix(insert)
inserted if(common.isEmpty)
else if(display.isEmpty)
{ ()
lazy val common = commonPrefix(insert)
if(inserted || common.isEmpty)
{
showCompletions(display, reader)
reader.drawLine()
true
}
else else
{ showCompletions(display, reader)
reader.getCursorBuffer.write(common) else
reader.redrawLine() appendCompletion(common, reader)
complete(beforeCursor + common, completions, reader, true)
} !(common.isEmpty && display.isEmpty)
}
} }
def showCompletions(cs: Seq[String], reader: ConsoleReader): Unit =
def appendCompletion(common: String, reader: ConsoleReader)
{
reader.getCursorBuffer.write(common)
reader.redrawLine()
}
def showCompletions(display: Seq[String], reader: ConsoleReader)
{
printCompletions(display, reader)
reader.drawLine()
}
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit =
if(cs.isEmpty) () else CandidateListCompletionHandler.printCandidates(reader, JavaConversions.asJavaList(cs), true) if(cs.isEmpty) () else CandidateListCompletionHandler.printCandidates(reader, JavaConversions.asJavaList(cs), true)
def commonPrefix(s: Seq[String]): String = if(s.isEmpty) "" else s reduceLeft commonPrefix def commonPrefix(s: Seq[String]): String = if(s.isEmpty) "" else s reduceLeft commonPrefix

View File

@ -5,6 +5,7 @@ package sbt
import jline.{Completor, ConsoleReader} import jline.{Completor, ConsoleReader}
import java.io.File import java.io.File
import complete.Parser
abstract class JLine extends LineReader abstract class JLine extends LineReader
{ {
@ -56,27 +57,21 @@ private object JLine
val MaxHistorySize = 500 val MaxHistorySize = 500
} }
trait LineReader extends NotNull trait LineReader
{ {
def readLine(prompt: String): Option[String] def readLine(prompt: String): Option[String]
} }
private[sbt] final class LazyJLineReader(historyPath: Option[File] /*, completor: => Completor*/) extends JLine final class FullReader(historyPath: Option[File], complete: Parser[_]) extends JLine
{ {
protected[this] val reader = protected[this] val reader =
{ {
val cr = new ConsoleReader val cr = new ConsoleReader
cr.setBellEnabled(false) cr.setBellEnabled(false)
JLine.initializeHistory(cr, historyPath) JLine.initializeHistory(cr, historyPath)
// cr.addCompletor(new LazyCompletor(completor)) sbt.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr cr
} }
} }
private class LazyCompletor(delegate0: => Completor) extends Completor
{
private lazy val delegate = delegate0
def complete(buffer: String, cursor: Int, candidates: java.util.List[_]): Int =
delegate.complete(buffer, cursor, candidates)
}
class SimpleReader private[sbt] (historyPath: Option[File]) extends JLine class SimpleReader private[sbt] (historyPath: Option[File]) extends JLine
{ {

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2008, 2010 Mark Harrah * Copyright 2008, 2010, 2011 Mark Harrah
*/ */
package sbt.parse package sbt.complete
import Parser._ import Parser._
@ -11,7 +11,7 @@ sealed trait Parser[+T]
def resultEmpty: Option[T] def resultEmpty: Option[T]
def result: Option[T] = None def result: Option[T] = None
def completions: Completions def completions: Completions
def valid: Boolean = true def valid: Boolean
def isTokenStart = false def isTokenStart = false
} }
sealed trait RichParser[A] sealed trait RichParser[A]
@ -54,56 +54,8 @@ sealed trait RichParser[A]
def flatMap[B](f: A => Parser[B]): Parser[B] def flatMap[B](f: A => Parser[B]): Parser[B]
} }
object Parser object Parser extends ParserMain
{ {
def apply[T](p: Parser[T])(s: String): Parser[T] =
(p /: s)(derive1)
def derive1[T](p: Parser[T], c: Char): Parser[T] =
if(p.valid) p.derive(c) else p
def completions(p: Parser[_], s: String): Completions = completions( apply(p)(s) )
def completions(p: Parser[_]): Completions = p.completions
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A]
{
def ~[B](b: Parser[B]) = seqParser(a, b)
def ||[B](b: Parser[B]) = choiceParser(a,b)
def |[B >: A](b: Parser[B]) = homParser(a,b)
def ? = opt(a)
def * = zeroOrMore(a)
def + = oneOrMore(a)
def map[B](f: A => B) = mapParser(a, f)
def id = a
def ^^^[B](value: B): Parser[B] = a map { _ => value }
def ??[B >: A](alt: B): Parser[B] = a.? map { _ getOrElse alt }
def <~[B](b: Parser[B]): Parser[A] = (a ~ b) map { case av ~ _ => av }
def ~>[B](b: Parser[B]): Parser[B] = (a ~ b) map { case _ ~ bv => bv }
def unary_- = not(a)
def & (o: Parser[_]) = and(a, o)
def - (o: Parser[_]) = sub(a, o)
def examples(s: String*): Parser[A] = examples(s.toSet)
def examples(s: Set[String]): Parser[A] = Parser.examples(a, s, check = true)
def filter(f: A => Boolean): Parser[A] = filterParser(a, f)
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
}
implicit def literalRichParser(c: Char): RichParser[Char] = richParser(c)
implicit def literalRichParser(s: String): RichParser[String] = richParser(s)
def examples[A](a: Parser[A], completions: Set[String], check: Boolean = false): Parser[A] =
if(a.valid) {
a.result match
{
case Some(av) => success( av )
case None =>
if(check) checkMatches(a, completions.toSeq)
new Examples(a, completions)
}
}
else Invalid
def checkMatches(a: Parser[_], completions: Seq[String]) def checkMatches(a: Parser[_], completions: Seq[String])
{ {
@ -135,37 +87,22 @@ object Parser
if(a.valid) { if(a.valid) {
a.result match a.result match
{ {
case Some(av) => if( f(av) ) success( av ) else Invalid case Some(av) => if( f(av) ) successStrict( av ) else Invalid
case None => new Filter(a, f) case None => new Filter(a, f)
} }
} }
else Invalid else Invalid
def seqParser[A,B](a: Parser[A], b: Parser[B]): Parser[(A,B)] = def seqParser[A,B](a: Parser[A], b: Parser[B]): Parser[(A,B)] =
if(a.valid && b.valid) { if(a.valid && b.valid)
(a.result, b.result) match { (a.result, b.result) match {
case (Some(av), Some(bv)) => success( (av, bv) ) case (Some(av), Some(bv)) => successStrict( (av, bv) )
case (Some(av), None) => b map { bv => (av, bv) } case (Some(av), None) => b map { bv => (av, bv) }
case (None, Some(bv)) => a map { av => (av, bv) } case (None, Some(bv)) => a map { av => (av, bv) }
case (None, None) => new SeqParser(a,b) case (None, None) => new SeqParser(a,b)
} }
}
else Invalid else Invalid
def token[T](t: Parser[T]): Parser[T] = token(t, "", true)
def token[T](t: Parser[T], description: String): Parser[T] = token(t, description, false)
def token[T](t: Parser[T], seen: String, track: Boolean): Parser[T] =
if(t.valid && !t.isTokenStart)
if(t.result.isEmpty) new TokenStart(t, seen, track) else t
else
t
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] =
if(a.valid)
if(b.valid) new HomParser(a, b) else a
else
b
def choiceParser[A,B](a: Parser[A], b: Parser[B]): Parser[Either[A,B]] = def choiceParser[A,B](a: Parser[A], b: Parser[B]): Parser[Either[A,B]] =
if(a.valid) if(a.valid)
if(b.valid) new HetParser(a,b) else a.map( Left(_) ) if(b.valid) new HetParser(a,b) else a.map( Left(_) )
@ -173,14 +110,14 @@ object Parser
b.map( Right(_) ) b.map( Right(_) )
def opt[T](a: Parser[T]): Parser[Option[T]] = def opt[T](a: Parser[T]): Parser[Option[T]] =
if(a.valid) new Optional(a) else success(None) if(a.valid) new Optional(a) else successStrict(None)
def zeroOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 0, Infinite) def zeroOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 0, Infinite)
def oneOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 1, Infinite) def oneOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 1, Infinite)
def repeat[T](p: Parser[T], min: Int = 0, max: UpperBound = Infinite): Parser[Seq[T]] = def repeat[T](p: Parser[T], min: Int = 0, max: UpperBound = Infinite): Parser[Seq[T]] =
repeat(None, p, min, max, Nil) repeat(None, p, min, max, Nil)
private[parse] def repeat[T](partial: Option[Parser[T]], repeated: Parser[T], min: Int, max: UpperBound, revAcc: List[T]): Parser[Seq[T]] = private[complete] def repeat[T](partial: Option[Parser[T]], repeated: Parser[T], min: Int, max: UpperBound, revAcc: List[T]): Parser[Seq[T]] =
{ {
assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")") assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")")
assume(max >= min, "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")") assume(max >= min, "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")")
@ -189,8 +126,8 @@ object Parser
if(repeated.valid) if(repeated.valid)
repeated.result match repeated.result match
{ {
case Some(value) => success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here case Some(value) => successStrict(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc) case None => if(max.isZero) successStrict(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
} }
else if(min == 0) else if(min == 0)
invalidButOptional invalidButOptional
@ -207,27 +144,55 @@ object Parser
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse)) case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
} }
else Invalid else Invalid
case None => checkRepeated(success(Nil)) case None => checkRepeated(successStrict(Nil))
} }
} }
def success[T](value: T): Parser[T] = new Parser[T] { def sub[T](a: Parser[T], b: Parser[_]): Parser[T] = and(a, not(b))
override def result = Some(value)
def and[T](a: Parser[T], b: Parser[_]): Parser[T] = if(a.valid && b.valid) new And(a, b) else Invalid
}
trait ParserMain
{
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A]
{
def ~[B](b: Parser[B]) = seqParser(a, b)
def ||[B](b: Parser[B]) = choiceParser(a,b)
def |[B >: A](b: Parser[B]) = homParser(a,b)
def ? = opt(a)
def * = zeroOrMore(a)
def + = oneOrMore(a)
def map[B](f: A => B) = mapParser(a, f)
def id = a
def ^^^[B](value: B): Parser[B] = a map { _ => value }
def ??[B >: A](alt: B): Parser[B] = a.? map { _ getOrElse alt }
def <~[B](b: Parser[B]): Parser[A] = (a ~ b) map { case av ~ _ => av }
def ~>[B](b: Parser[B]): Parser[B] = (a ~ b) map { case _ ~ bv => bv }
def unary_- = not(a)
def & (o: Parser[_]) = and(a, o)
def - (o: Parser[_]) = sub(a, o)
def examples(s: String*): Parser[A] = examples(s.toSet)
def examples(s: Set[String]): Parser[A] = Parser.examples(a, s, check = true)
def filter(f: A => Boolean): Parser[A] = filterParser(a, f)
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
}
implicit def literalRichParser(c: Char): RichParser[Char] = richParser(c)
implicit def literalRichParser(s: String): RichParser[String] = richParser(s)
def failure[T](msg: String): Parser[T] = Invalid(msg)
def successStrict[T](value: T): Parser[T] = success(value)
def success[T](value: => T): Parser[T] = new ValidParser[T] {
private[this] lazy val v = value
override def result = Some(v)
def resultEmpty = result def resultEmpty = result
def derive(c: Char) = Invalid def derive(c: Char) = Invalid
def completions = Completions.empty def completions = Completions.empty
override def toString = "success(" + value + ")" override def toString = "success(" + v + ")"
} }
val any: Parser[Char] = charClass(_ => true)
def sub[T](a: Parser[T], b: Parser[_]): Parser[T] = and(a, not(b))
def and[T](a: Parser[T], b: Parser[_]): Parser[T] =
if(a.valid && b.valid) new And(a, b) else Invalid
def not(p: Parser[_]): Parser[Unit] = new Not(p)
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] =
new CharacterClass(r contains _).examples(r.map(_.toString) : _*) new CharacterClass(r contains _).examples(r.map(_.toString) : _*)
def chars(legal: String): Parser[Char] = def chars(legal: String): Parser[Char] =
@ -237,29 +202,97 @@ object Parser
} }
def charClass(f: Char => Boolean): Parser[Char] = new CharacterClass(f) def charClass(f: Char => Boolean): Parser[Char] = new CharacterClass(f)
implicit def literal(ch: Char): Parser[Char] = new Parser[Char] { implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
def resultEmpty = None def resultEmpty = None
def derive(c: Char) = if(c == ch) success(ch) else Invalid def derive(c: Char) = if(c == ch) successStrict(ch) else Invalid
def completions = Completions.single(Completion.suggestStrict(ch.toString)) def completions = Completions.single(Completion.suggestStrict(ch.toString))
override def toString = "'" + ch + "'" override def toString = "'" + ch + "'"
} }
implicit def literal(s: String): Parser[String] = stringLiteral(s, s.toList) implicit def literal(s: String): Parser[String] = stringLiteral(s, s.toList)
def stringLiteral(s: String, remaining: List[Char]): Parser[String] =
if(s.isEmpty) error("String literal cannot be empty") else if(remaining.isEmpty) success(s) else new StringLiteral(s, remaining)
object ~ { object ~ {
def unapply[A,B](t: (A,B)): Some[(A,B)] = Some(t) def unapply[A,B](t: (A,B)): Some[(A,B)] = Some(t)
} }
// intended to be temporary pending proper error feedback
def result[T](p: Parser[T], s: String): Either[(String,Int), T] =
{
/* def loop(i: Int, a: Parser[T]): Either[(String,Int), T] =
a.err match
{
case Some(msg) => Left((msg, i))
case None =>
val ci = i+1
if(ci >= s.length)
a.resultEmpty.toRight(("", i))
else
loop(ci, a derive s(ci))
}
loop(-1, p)*/
apply(p)(s).resultEmpty.toRight(("Parse error", 0))
}
def apply[T](p: Parser[T])(s: String): Parser[T] =
(p /: s)(derive1)
def derive1[T](p: Parser[T], c: Char): Parser[T] =
if(p.valid) p.derive(c) else p
// The x Completions.empty removes any trailing token completions where append.isEmpty
def completions(p: Parser[_], s: String): Completions = apply(p)(s).completions x Completions.empty
def examples[A](a: Parser[A], completions: Set[String], check: Boolean = false): Parser[A] =
if(a.valid) {
a.result match
{
case Some(av) => successStrict( av )
case None =>
if(check) checkMatches(a, completions.toSeq)
new Examples(a, completions)
}
}
else a
def matched(t: Parser[_], seenReverse: List[Char] = Nil, partial: Boolean = false): Parser[String] =
if(!t.valid)
if(partial && !seenReverse.isEmpty) successStrict(seenReverse.reverse.mkString) else Invalid
else if(t.result.isEmpty)
new MatchedString(t, seenReverse, partial)
else
successStrict(seenReverse.reverse.mkString)
def token[T](t: Parser[T]): Parser[T] = token(t, "", true)
def token[T](t: Parser[T], description: String): Parser[T] = token(t, description, false)
def token[T](t: Parser[T], seen: String, track: Boolean): Parser[T] =
if(t.valid && !t.isTokenStart)
if(t.result.isEmpty) new TokenStart(t, seen, track) else t
else
t
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] =
if(a.valid)
if(b.valid) new HomParser(a, b) else a
else
b
def not(p: Parser[_]): Parser[Unit] = new Not(p)
def stringLiteral(s: String, remaining: List[Char]): Parser[String] =
if(s.isEmpty) error("String literal cannot be empty") else if(remaining.isEmpty) success(s) else new StringLiteral(s, remaining)
} }
private final object Invalid extends Parser[Nothing] sealed trait ValidParser[T] extends Parser[T]
{
final def valid = true
}
private object Invalid extends Invalid("inv")
private sealed case class Invalid(val message: String) extends Parser[Nothing]
{ {
def resultEmpty = None def resultEmpty = None
def derive(c: Char) = error("Invalid.") def derive(c: Char) = error("Invalid.")
override def valid = false override def valid = false
def completions = Completions.nil def completions = Completions.nil
override def toString = "inv" override def toString = message
} }
private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends Parser[(A,B)] private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends ValidParser[(A,B)]
{ {
def cross(ao: Option[A], bo: Option[B]): Option[(A,B)] = for(av <- ao; bv <- bo) yield (av,bv) def cross(ao: Option[A], bo: Option[B]): Option[(A,B)] = for(av <- ao; bv <- bo) yield (av,bv)
lazy val resultEmpty = cross(a.resultEmpty, b.resultEmpty) lazy val resultEmpty = cross(a.resultEmpty, b.resultEmpty)
@ -276,44 +309,44 @@ private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends Parser[(A
override def toString = "(" + a + " ~ " + b + ")" override def toString = "(" + a + " ~ " + b + ")"
} }
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends Parser[A] private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A]
{ {
def derive(c: Char) = (a derive c) | (b derive c) def derive(c: Char) = (a derive c) | (b derive c)
lazy val resultEmpty = a.resultEmpty orElse b.resultEmpty lazy val resultEmpty = a.resultEmpty orElse b.resultEmpty
lazy val completions = a.completions ++ b.completions lazy val completions = a.completions ++ b.completions
override def toString = "(" + a + " | " + b + ")" override def toString = "(" + a + " | " + b + ")"
} }
private final class HetParser[A,B](a: Parser[A], b: Parser[B]) extends Parser[Either[A,B]] private final class HetParser[A,B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A,B]]
{ {
def derive(c: Char) = (a derive c) || (b derive c) def derive(c: Char) = (a derive c) || (b derive c)
lazy val resultEmpty = a.resultEmpty.map(Left(_)) orElse b.resultEmpty.map(Right(_)) lazy val resultEmpty = a.resultEmpty.map(Left(_)) orElse b.resultEmpty.map(Right(_))
lazy val completions = a.completions ++ b.completions lazy val completions = a.completions ++ b.completions
override def toString = "(" + a + " || " + b + ")" override def toString = "(" + a + " || " + b + ")"
} }
private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends Parser[B] private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B]
{ {
lazy val resultEmpty = a.resultEmpty match { case None => None; case Some(av) => f(av).resultEmpty } lazy val resultEmpty = a.resultEmpty match { case None => None; case Some(av) => f(av).resultEmpty }
lazy val completions = { lazy val completions =
a.completions flatMap { c => a.completions flatMap { c =>
apply(a)(c.append).resultEmpty match { apply(a)(c.append).resultEmpty match {
case None => Completions.strict(Set.empty + c) case None => Completions.strict(Set.empty + c)
case Some(av) => c x f(av).completions case Some(av) => c x f(av).completions
} }
} }
}
def derive(c: Char) = def derive(c: Char) =
{ {
val common = a derive c flatMap f val common = a derive c flatMap f
a.resultEmpty match a.resultEmpty match
{ {
case Some(av) => common | f(av).derive(c) case Some(av) => common | derive1(f(av), c)
case None => common case None => common
} }
} }
override def isTokenStart = a.isTokenStart
override def toString = "bind(" + a + ")" override def toString = "bind(" + a + ")"
} }
private final class MapParser[A,B](a: Parser[A], f: A => B) extends Parser[B] private final class MapParser[A,B](a: Parser[A], f: A => B) extends ValidParser[B]
{ {
lazy val resultEmpty = a.resultEmpty map f lazy val resultEmpty = a.resultEmpty map f
def derive(c: Char) = (a derive c) map f def derive(c: Char) = (a derive c) map f
@ -321,14 +354,24 @@ private final class MapParser[A,B](a: Parser[A], f: A => B) extends Parser[B]
override def isTokenStart = a.isTokenStart override def isTokenStart = a.isTokenStart
override def toString = "map(" + a + ")" override def toString = "map(" + a + ")"
} }
private final class Filter[T](p: Parser[T], f: T => Boolean) extends Parser[T] private final class Filter[T](p: Parser[T], f: T => Boolean) extends ValidParser[T]
{ {
lazy val resultEmpty = p.resultEmpty filter f lazy val resultEmpty = p.resultEmpty filter f
def derive(c: Char) = (p derive c) filter f def derive(c: Char) = (p derive c) filter f
lazy val completions = p.completions filterS { s => apply(p)(s).resultEmpty.filter(f).isDefined } lazy val completions = p.completions filterS { s => apply(p)(s).resultEmpty.filter(f).isDefined }
override def toString = "filter(" + p + ")" override def toString = "filter(" + p + ")"
override def isTokenStart = p.isTokenStart
} }
private final class TokenStart[T](delegate: Parser[T], seen: String, track: Boolean) extends Parser[T] private final class MatchedString(delegate: Parser[_], seenReverse: List[Char], partial: Boolean) extends ValidParser[String]
{
lazy val seen = seenReverse.reverse.mkString
def derive(c: Char) = matched(delegate derive c, c :: seenReverse, partial)
def completions = delegate.completions
def resultEmpty = if(delegate.resultEmpty.isDefined) Some(seen) else if(partial) Some(seen) else None
override def isTokenStart = delegate.isTokenStart
override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")"
}
private final class TokenStart[T](delegate: Parser[T], seen: String, track: Boolean) extends ValidParser[T]
{ {
def derive(c: Char) = token( delegate derive c, if(track) seen + c else seen, track) def derive(c: Char) = token( delegate derive c, if(track) seen + c else seen, track)
lazy val completions = lazy val completions =
@ -344,27 +387,31 @@ private final class TokenStart[T](delegate: Parser[T], seen: String, track: Bool
override def isTokenStart = true override def isTokenStart = true
override def toString = "token('" + seen + "', " + track + ", " + delegate + ")" override def toString = "token('" + seen + "', " + track + ", " + delegate + ")"
} }
private final class And[T](a: Parser[T], b: Parser[_]) extends Parser[T] private final class And[T](a: Parser[T], b: Parser[_]) extends ValidParser[T]
{ {
def derive(c: Char) = (a derive c) & (b derive c) def derive(c: Char) = (a derive c) & (b derive c)
lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isDefined ) lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isDefined )
lazy val resultEmpty = if(b.resultEmpty.isDefined) a.resultEmpty else None lazy val resultEmpty = if(b.resultEmpty.isDefined) a.resultEmpty else None
} }
private final class Not(delegate: Parser[_]) extends Parser[Unit] private final class Not(delegate: Parser[_]) extends ValidParser[Unit]
{ {
def derive(c: Char) = if(delegate.valid) not(delegate derive c) else this def derive(c: Char) = if(delegate.valid) not(delegate derive c) else this
def completions = Completions.empty def completions = Completions.empty
lazy val resultEmpty = if(delegate.resultEmpty.isDefined) None else Some(()) lazy val resultEmpty = if(delegate.resultEmpty.isDefined) None else Some(())
} }
private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends Parser[T] private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends ValidParser[T]
{ {
def derive(c: Char) = examples(delegate derive c, fixed.collect { case x if x.length > 0 && x(0) == c => x.tail }) def derive(c: Char) = examples(delegate derive c, fixed.collect { case x if x.length > 0 && x(0) == c => x substring 1 })
def resultEmpty = delegate.resultEmpty lazy val resultEmpty = delegate.resultEmpty
lazy val completions = if(fixed.isEmpty) Completions.empty else Completions(fixed map(f => Completion.suggestion(f)) ) lazy val completions =
if(fixed.isEmpty)
if(resultEmpty.isEmpty) Completions.nil else Completions.empty
else
Completions(fixed map(f => Completion.suggestion(f)) )
override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")" override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")"
} }
private final class StringLiteral(str: String, remaining: List[Char]) extends Parser[String] private final class StringLiteral(str: String, remaining: List[Char]) extends ValidParser[String]
{ {
assert(str.length > 0 && !remaining.isEmpty) assert(str.length > 0 && !remaining.isEmpty)
def resultEmpty = None def resultEmpty = None
@ -372,21 +419,21 @@ private final class StringLiteral(str: String, remaining: List[Char]) extends Pa
lazy val completions = Completions.single(Completion.suggestion(remaining.mkString)) lazy val completions = Completions.single(Completion.suggestion(remaining.mkString))
override def toString = '"' + str + '"' override def toString = '"' + str + '"'
} }
private final class CharacterClass(f: Char => Boolean) extends Parser[Char] private final class CharacterClass(f: Char => Boolean) extends ValidParser[Char]
{ {
def resultEmpty = None def resultEmpty = None
def derive(c: Char) = if( f(c) ) success(c) else Invalid def derive(c: Char) = if( f(c) ) successStrict(c) else Invalid
def completions = Completions.empty def completions = Completions.empty
override def toString = "class()" override def toString = "class()"
} }
private final class Optional[T](delegate: Parser[T]) extends Parser[Option[T]] private final class Optional[T](delegate: Parser[T]) extends ValidParser[Option[T]]
{ {
def resultEmpty = Some(None) def resultEmpty = Some(None)
def derive(c: Char) = (delegate derive c).map(Some(_)) def derive(c: Char) = (delegate derive c).map(Some(_))
lazy val completions = Completion.empty +: delegate.completions lazy val completions = Completion.empty +: delegate.completions
override def toString = delegate.toString + "?" override def toString = delegate.toString + "?"
} }
private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], min: Int, max: UpperBound, accumulatedReverse: List[T]) extends Parser[Seq[T]] private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], min: Int, max: UpperBound, accumulatedReverse: List[T]) extends ValidParser[Seq[T]]
{ {
assume(0 <= min, "Minimum occurences must be non-negative") assume(0 <= min, "Minimum occurences must be non-negative")
assume(max >= min, "Minimum occurences must be less than the maximum occurences") assume(max >= min, "Minimum occurences must be less than the maximum occurences")

View File

@ -0,0 +1,53 @@
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah
*/
package sbt.complete
import Parser._
import java.io.File
import java.net.URI
import java.lang.Character.{getType, MATH_SYMBOL, OTHER_SYMBOL, DASH_PUNCTUATION, OTHER_PUNCTUATION, MODIFIER_SYMBOL, CURRENCY_SYMBOL}
// Some predefined parsers
trait Parsers
{
lazy val any: Parser[Char] = charClass(_ => true)
lazy val DigitSet = Set("0","1","2","3","4","5","6","7","8","9")
lazy val Digit = charClass(_.isDigit) examples DigitSet
lazy val Letter = charClass(_.isLetter)
def IDStart = Letter
lazy val IDChar = charClass(isIDChar)
lazy val ID = IDStart ~ IDChar.* map { case x ~ xs => (x +: xs).mkString }
lazy val OpChar = charClass(isOpChar)
lazy val Op = OpChar.+.string
lazy val OpOrID = ID | Op
def isOpChar(c: Char) = !isDelimiter(c) && isOpType(getType(c))
def isOpType(cat: Int) = cat match { case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL | CURRENCY_SYMBOL => true; case _ => false }
def isIDChar(c: Char) = c.isLetterOrDigit || c == '-' || c == '_'
def isDelimiter(c: Char) = c match { case '`' | '\'' | '\"' | /*';' | */',' | '.' => true ; case _ => false }
lazy val NotSpaceClass = charClass(!_.isWhitespace)
lazy val SpaceClass = charClass(_.isWhitespace)
lazy val NotSpace = NotSpaceClass.+.string
lazy val Space = SpaceClass.+.examples(" ")
lazy val OptSpace = SpaceClass.*.examples(" ")
// TODO: implement
def fileParser(base: File): Parser[File] = token(mapOrFail(NotSpace)(s => new File(s.mkString)), "<file>")
lazy val Port = token(IntBasic, "<port>")
lazy val IntBasic = mapOrFail( '-'.? ~ Digit.+ )( Function.tupled(toInt) )
private[this] def toInt(neg: Option[Char], digits: Seq[Char]): Int =
(neg.toSeq ++ digits).mkString.toInt
def mapOrFail[S,T](p: Parser[S])(f: S => T): Parser[T] =
p flatMap { s => try { successStrict(f(s)) } catch { case e: Exception => failure(e.toString) } }
def spaceDelimited(display: String): Parser[Seq[String]] = (token(Space) ~> token(NotSpace, display)).*
def Uri(ex: Set[URI]) = NotSpace map { uri => new URI(uri) } examples(ex.map(_.toString))
}
object Parsers extends Parsers
object DefaultParsers extends Parsers with ParserMain

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2008,2010 Mark Harrah * Copyright 2008,2010 Mark Harrah
*/ */
package sbt.parse package sbt.complete
sealed trait UpperBound sealed trait UpperBound
{ {

View File

@ -1,4 +1,4 @@
package sbt.parse package sbt.complete
import Parser._ import Parser._
import org.scalacheck._ import org.scalacheck._
@ -25,14 +25,14 @@ object JLineTest
} }
object ParserTest extends Properties("Completing Parser") object ParserTest extends Properties("Completing Parser")
{ {
val wsc = charClass(_.isWhitespace) import Parsers._
val ws = ( wsc + ) examples(" ")
val optWs = ( wsc * ) examples("")
val nested = (token("a1") ~ token("b2")) ~ "c3" val nested = (token("a1") ~ token("b2")) ~ "c3"
val nestedDisplay = (token("a1", "<a1>") ~ token("b2", "<b2>")) ~ "c3" val nestedDisplay = (token("a1", "<a1>") ~ token("b2", "<b2>")) ~ "c3"
def p[T](f: T): T = { /*println(f);*/ f } val spacePort = (token(Space) ~> Port)
def p[T](f: T): T = { println(f); f }
def checkSingle(in: String, expect: Completion)(expectDisplay: Completion = expect) = def checkSingle(in: String, expect: Completion)(expectDisplay: Completion = expect) =
( ("token '" + in + "'") |: checkOne(in, nested, expect)) && ( ("token '" + in + "'") |: checkOne(in, nested, expect)) &&
@ -56,6 +56,13 @@ object ParserTest extends Properties("Completing Parser")
property("nested tokens c") = checkSingle("a1b2", Completion.suggestStrict("c3") )() property("nested tokens c") = checkSingle("a1b2", Completion.suggestStrict("c3") )()
property("nested tokens c3") = checkSingle("a1b2c", Completion.suggestStrict("3"))() property("nested tokens c3") = checkSingle("a1b2c", Completion.suggestStrict("3"))()
property("nested tokens c inv") = checkInvalid("a1b2a") property("nested tokens c inv") = checkInvalid("a1b2a")
property("suggest space") = checkOne("", spacePort, Completion.tokenStrict("", " "))
property("suggest port") = checkOne(" ", spacePort, Completion.displayStrict("<port>") )
property("no suggest at end") = checkOne("asdf", "asdf", Completion.suggestStrict(""))
property("no suggest at token end") = checkOne("asdf", token("asdf"), Completion.suggestStrict(""))
property("empty suggest for examples") = checkOne("asdf", any.+.examples("asdf", "qwer"), Completion.suggestStrict(""))
property("empty suggest for examples token") = checkOne("asdf", token(any.+.examples("asdf", "qwer")), Completion.suggestStrict(""))
} }
object ParserExample object ParserExample
{ {