mirror of https://github.com/sbt/sbt.git
redo Command to use Parser
nested commands still need work
This commit is contained in:
parent
21dabc0fab
commit
c3a265dbd3
|
|
@ -3,22 +3,113 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import Execute.NodeView
|
||||
import java.io.File
|
||||
import Function.untupled
|
||||
import parse.Parser
|
||||
import complete.{DefaultParsers, Parser}
|
||||
import CommandSupport.logger
|
||||
|
||||
trait NewCommand // to replace Command
|
||||
{
|
||||
type T
|
||||
def parser: State => Option[Parser[T]]
|
||||
def run: (T, State) => State
|
||||
sealed trait Command {
|
||||
def help: Seq[Help]
|
||||
def parser: State => Parser[State]
|
||||
def tags: AttributeMap
|
||||
def tag[T](key: AttributeKey[T], value: T): Command
|
||||
}
|
||||
trait Command
|
||||
{
|
||||
def help: State => Seq[Help]
|
||||
def run: (Input, State) => Option[State]
|
||||
private[sbt] final class SimpleCommand(val name: String, val help: Seq[Help], val parser: State => Parser[State], val tags: AttributeMap) extends Command {
|
||||
assert(Command validID name, "'" + name + "' is not a valid command name." )
|
||||
def tag[T](key: AttributeKey[T], value: T): SimpleCommand = new SimpleCommand(name, help, parser, tags.put(key, value))
|
||||
}
|
||||
private[sbt] final class ArbitraryCommand(val parser: State => Parser[State], val help: Seq[Help], val tags: AttributeMap) extends Command
|
||||
{
|
||||
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand = new ArbitraryCommand(parser, help, tags.put(key, value))
|
||||
}
|
||||
|
||||
object Command
|
||||
{
|
||||
def pointer(s: String, i: Int): String = (s take i) map { case '\t' => '\t'; case _ => ' ' } mkString;
|
||||
|
||||
import DefaultParsers._
|
||||
|
||||
val Logged = AttributeKey[Logger]("log")
|
||||
val HistoryPath = SettingKey[Option[File]]("history")
|
||||
val Analysis = AttributeKey[inc.Analysis]("analysis")
|
||||
val Watch = SettingKey[Watched]("continuous-watch")
|
||||
|
||||
def command(name: String)(f: State => State): Command = command(name, Nil)(f)
|
||||
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command = command(name, Help(name, (name, briefHelp), detail) :: Nil)(f)
|
||||
def command(name: String, help: Seq[Help])(f: State => State): Command = apply(name, help : _*)(state => success(f(state)))
|
||||
|
||||
def apply(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[State]): Command =
|
||||
apply(name, Help(name, briefHelp, detail) )(parser)
|
||||
def apply(name: String, help: Help*)(parser: State => Parser[State]): Command = new SimpleCommand(name, help, parser, AttributeMap.empty)
|
||||
|
||||
def args(name: String, briefHelp: (String, String), detail: String, display: String)(f: (State, Seq[String]) => State): Command =
|
||||
args(name, display, Help(name, briefHelp, detail) )(f)
|
||||
|
||||
def args(name: String, display: String, help: Help*)(f: (State, Seq[String]) => State): Command =
|
||||
apply(name, help : _*)( state => spaceDelimited(display) map f.curried(state) )
|
||||
|
||||
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
|
||||
single(name, Help(name, briefHelp, detail) )(f)
|
||||
def single(name: String, help: Help*)(f: (State, String) => State): Command =
|
||||
apply(name, help : _*)( state => token(any.+.string map f.curried(state)) )
|
||||
|
||||
def custom(parser: State => Parser[State], help: Seq[Help]): Command = new ArbitraryCommand(parser, help, AttributeMap.empty)
|
||||
|
||||
def validID(name: String) =
|
||||
Parser(OpOrID)(name).resultEmpty.isDefined
|
||||
|
||||
def combine(cmds: Seq[Command]): State => Parser[State] =
|
||||
{
|
||||
val (simple, arbs) = separateCommands(cmds)
|
||||
state => (simpleParser(simple)(state) /: arbs.map(_ parser state) ){ _ | _ }
|
||||
}
|
||||
private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
|
||||
Collections.separate(cmds){ case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) }
|
||||
|
||||
def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[State] =
|
||||
simpleParser(cmds.map(sc => (sc.name, sc.parser)).toMap )
|
||||
|
||||
def simpleParser(commandMap: Map[String, State => Parser[State]]): State => Parser[State] =
|
||||
(state: State) => token(OpOrID examples commandMap.keys.toSet) flatMap { id =>
|
||||
(commandMap get id) match { case None => failure("No command named '" + id + "'"); case Some(c) => c(state) }
|
||||
}
|
||||
|
||||
def process(command: String, state: State): State =
|
||||
{
|
||||
val parser = combine(state.processors)
|
||||
Parser.result(parser(state), command) match
|
||||
{
|
||||
case Right(s) => s
|
||||
case Left((msg,pos)) =>
|
||||
val errMsg = commandError(command, msg, pos)
|
||||
logger(state).info(errMsg)
|
||||
state.fail
|
||||
}
|
||||
}
|
||||
def commandError(command: String, msg: String, index: Int): String =
|
||||
{
|
||||
val (line, modIndex) = extractLine(command, index)
|
||||
msg + "\n" + line + "\n" + pointer(msg, modIndex)
|
||||
}
|
||||
def extractLine(s: String, i: Int): (String, Int) =
|
||||
{
|
||||
val notNewline = (c: Char) => c != '\n' && c != '\r'
|
||||
val left = takeRightWhile( s.substring(0, i) )( notNewline )
|
||||
val right = s substring i takeWhile notNewline
|
||||
(left + right, left.length)
|
||||
}
|
||||
def takeRightWhile(s: String)(pred: Char => Boolean): String =
|
||||
{
|
||||
def loop(i: Int): String =
|
||||
if(i < 0)
|
||||
s
|
||||
else if( pred(s(i)) )
|
||||
loop(i-1)
|
||||
else
|
||||
s.substring(i+1)
|
||||
loop(s.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
trait Help
|
||||
{
|
||||
def detail: (Set[String], String)
|
||||
|
|
@ -26,43 +117,11 @@ trait Help
|
|||
}
|
||||
object Help
|
||||
{
|
||||
def apply(name: String, briefHelp: (String, String), detail: String): Help = apply(briefHelp, (Set(name), detail))
|
||||
|
||||
def apply(briefHelp: (String, String), detailedHelp: (Set[String], String) = (Set.empty, "") ): Help =
|
||||
new Help { def detail = detailedHelp; def brief = briefHelp }
|
||||
}
|
||||
object Command
|
||||
{
|
||||
val Logged = AttributeKey[Logger]("log")
|
||||
val HistoryPath = SettingKey[Option[File]]("history")
|
||||
val Analysis = AttributeKey[inc.Analysis]("analysis")
|
||||
val Watch = SettingKey[Watched]("continuous-watch")
|
||||
|
||||
def direct(h: Help*)(r: (Input, State) => Option[State]): Command =
|
||||
new Command { def help = _ => h; def run = r }
|
||||
|
||||
def apply(h: Help*)(r: PartialFunction[(Input, State), State]): Command =
|
||||
direct(h : _*)(untupled(r.lift))
|
||||
|
||||
def simple(name: String, brief: (String, String), detail: String)(f: (Input, State) => State): Command =
|
||||
{
|
||||
val h = Help(brief, (Set(name), detail) )
|
||||
simple(name, h)(f)
|
||||
}
|
||||
def simple(name: String, help: Help*)(f: (Input, State) => State): Command =
|
||||
Command( help: _* ){ case (in, s) if name == in.name => f( in, s) }
|
||||
}
|
||||
final case class Input(line: String, cursor: Option[Int])
|
||||
{
|
||||
lazy val (name, arguments) = line match { case Input.NameRegex(n, a) => (n, a); case _ => (line, "") }
|
||||
lazy val splitArgs: Seq[String] = if(arguments.isEmpty) Nil else (arguments split """\s+""").toSeq
|
||||
}
|
||||
object Input
|
||||
{
|
||||
val NameRegex = """\s*(\p{Punct}+|[\w-]+)\s*(.*)""".r
|
||||
}
|
||||
|
||||
object Next extends Enumeration {
|
||||
val Reload, Fail, Done, Continue = Value
|
||||
}
|
||||
trait CommandDefinitions
|
||||
{
|
||||
def commands: Seq[Command]
|
||||
|
|
|
|||
|
|
@ -34,9 +34,8 @@ object CommandSupport
|
|||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
|
||||
/** The list of command names that may be used to terminate the program.*/
|
||||
val TerminateActions: Seq[String] = Seq(Exit, Quit)
|
||||
|
||||
/** The command name to terminate the program.*/
|
||||
val TerminateAction: String = Exit
|
||||
|
||||
def continuousBriefHelp = (ContinuousExecutePrefix + " <action>", "Executes the specified command whenever source files change.")
|
||||
|
||||
|
|
@ -62,12 +61,12 @@ ProjectCommand +
|
|||
For example, 'project ....' is equivalent to three consecutive 'project ..' commands.
|
||||
"""
|
||||
|
||||
def projectsBrief = (ProjectsCommand, projectsDetailed)
|
||||
def projectsBrief = projectsDetailed
|
||||
def projectsDetailed = "Displays the names of available projects."
|
||||
|
||||
def historyHelp = HistoryCommands.descriptions.map( d => Help(d) )
|
||||
|
||||
def exitBrief = (TerminateActions.mkString(", "), "Terminates the build.")
|
||||
def exitBrief = (TerminateAction, "Terminates the build.")
|
||||
|
||||
def sbtrc = ".sbtrc"
|
||||
|
||||
|
|
@ -92,7 +91,7 @@ ProjectCommand +
|
|||
def DefaultsDetailed = "Registers default built-in commands"
|
||||
|
||||
def ReloadCommand = "reload"
|
||||
def ReloadBrief = (ReloadCommand, "Reloads the session and then executes the remaining commands.")
|
||||
def ReloadBrief = "Reloads the session and then executes the remaining commands."
|
||||
def ReloadDetailed =
|
||||
ReloadCommand + """
|
||||
This command is equivalent to exiting, restarting, and running the
|
||||
|
|
@ -180,11 +179,11 @@ CompileSyntax + """
|
|||
def LoadCommandLabel = "commands"
|
||||
|
||||
def LoadProject = "loadp"
|
||||
def LoadProjectBrief = (LoadProject, LoadProjectDetailed)
|
||||
def LoadProjectBrief = LoadProjectDetailed
|
||||
def LoadProjectDetailed = "Loads the project in the current directory"
|
||||
|
||||
def Shell = "shell"
|
||||
def ShellBrief = (Shell, ShellDetailed)
|
||||
def ShellBrief = ShellDetailed
|
||||
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
|
||||
|
||||
def OnFailure = "-"
|
||||
|
|
|
|||
269
main/Main.scala
269
main/Main.scala
|
|
@ -1,19 +1,22 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010 Mark Harrah
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import Execute.NodeView
|
||||
import complete.HistoryCommands
|
||||
import HistoryCommands.{Start => HistoryPrefix}
|
||||
import Project.{SessionKey, StructureKey}
|
||||
import sbt.build.{AggressiveCompile, Auto, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad}
|
||||
import Command.{Analysis,HistoryPath,Logged,Watch}
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Path._
|
||||
import Execute.NodeView
|
||||
import complete.HistoryCommands
|
||||
import HistoryCommands.{Start => HistoryPrefix}
|
||||
import Project.{SessionKey, StructureKey}
|
||||
import sbt.build.{AggressiveCompile, Auto, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad}
|
||||
import sbt.complete.{DefaultParsers, Parser}
|
||||
|
||||
import java.io.File
|
||||
import Command.{Analysis,HistoryPath,Logged,Watch}
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Function.tupled
|
||||
import Path._
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** This class is the entry point for sbt.*/
|
||||
class xMain extends xsbti.AppMain
|
||||
|
|
@ -43,64 +46,53 @@ class xMain extends xsbti.AppMain
|
|||
}
|
||||
}
|
||||
def next(state: State): State =
|
||||
ErrorHandling.wideConvert { state.process(process) } match
|
||||
ErrorHandling.wideConvert { state.process(Command.process) } match
|
||||
{
|
||||
case Right(s) => s
|
||||
case Left(t) => Commands.handleException(t, state)
|
||||
}
|
||||
def process(command: String, state: State): State =
|
||||
{
|
||||
val in = Input(command, None)
|
||||
Commands.applicable(state).flatMap( _.run(in, state) ).headOption.getOrElse {
|
||||
if(command.isEmpty) state
|
||||
else {
|
||||
System.err.println("Unknown command '" + command + "'")
|
||||
state.fail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import CommandSupport._
|
||||
import DefaultParsers._
|
||||
import CommandSupport._
|
||||
object Commands
|
||||
{
|
||||
def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover,
|
||||
projects, project, setOnFailure, ifLast, multi, shell, alias, append)
|
||||
projects, project, setOnFailure, ifLast, multi, shell, alias, append, nop)
|
||||
|
||||
def ignore = nothing(Set(FailureWall))
|
||||
|
||||
def nothing(ignore: Set[String]) = Command(){ case (in, s) if ignore(in.line) => s }
|
||||
|
||||
def applicable(state: State): Stream[Command] = state.processors.toStream
|
||||
def nop = Command.custom(successStrict, Nil)
|
||||
def ignore = Command.command(FailureWall)(identity)
|
||||
|
||||
def detail(selected: Iterable[String])(h: Help): Option[String] =
|
||||
h.detail match { case (commands, value) => if( selected exists commands ) Some(value) else None }
|
||||
|
||||
def help = Command.simple(HelpCommand, helpBrief, helpDetailed) { (in, s) =>
|
||||
// TODO: tab complete on command names
|
||||
def help = Command.args(HelpCommand, helpBrief, helpDetailed, "<command>") { (s, args) =>
|
||||
|
||||
val h = applicable(s).flatMap(_.help(s))
|
||||
val argStr = (in.line stripPrefix HelpCommand).trim
|
||||
|
||||
val h = s.processors.flatMap(_.help)
|
||||
val message =
|
||||
if(argStr.isEmpty)
|
||||
if(args.isEmpty)
|
||||
h.map( _.brief match { case (a,b) => a + " : " + b } ).mkString("\n", "\n", "\n")
|
||||
else
|
||||
h flatMap detail( argStr.split("""\s+""", 0) ) mkString("\n", "\n\n", "\n")
|
||||
h flatMap detail(args) mkString("\n", "\n\n", "\n")
|
||||
System.out.println(message)
|
||||
s
|
||||
}
|
||||
|
||||
def alias = Command.simple(AliasCommand, AliasBrief, AliasDetailed) { (in, s) =>
|
||||
in.arguments.split("""\s*=\s*""",2).toSeq match {
|
||||
case Seq(name, value) => addAlias(s, name.trim, value.trim)
|
||||
case Seq(x) if !x.isEmpty=> printAlias(s, x.trim); s
|
||||
case _ => printAliases(s); s
|
||||
def alias = Command(AliasCommand, AliasBrief, AliasDetailed) { s =>
|
||||
val name = token(OpOrID.examples( aliasNames(s) : _*) )
|
||||
val assign = token(Space ~ '=' ~ Space) ~> matched(Command.combine(s.processors)(s), partial = true)
|
||||
(OptSpace ~> (name ~ assign.?).?) map {
|
||||
case Some((name, Some(value))) => addAlias(s, name.trim, value.trim)
|
||||
case Some((x, None)) if !x.isEmpty=> printAlias(s, x.trim); s
|
||||
case None => printAliases(s); s
|
||||
}
|
||||
}
|
||||
|
||||
def shell = Command.simple(Shell, ShellBrief, ShellDetailed) { (in, s) =>
|
||||
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
|
||||
val historyPath = (s get HistoryPath.key) getOrElse Some((s.baseDir / ".history").asFile)
|
||||
val reader = new LazyJLineReader(historyPath)
|
||||
val parser = Command.combine(s.processors)
|
||||
val reader = new FullReader(historyPath, parser(s))
|
||||
val line = reader.readLine("> ")
|
||||
line match {
|
||||
case Some(line) => s.copy(onFailure = Some(Shell), commands = line +: Shell +: s.commands)
|
||||
|
|
@ -108,35 +100,48 @@ object Commands
|
|||
}
|
||||
}
|
||||
|
||||
def multi = Command.simple(Multi, MultiBrief, MultiDetailed) { (in, s) =>
|
||||
in.arguments.split(";").toSeq ::: s
|
||||
// TODO: this should nest Parsers for other commands
|
||||
def multi = Command.single(Multi, MultiBrief, MultiDetailed) { (s,arg) =>
|
||||
arg.split(";").toSeq ::: s
|
||||
}
|
||||
|
||||
def ifLast = Command.simple(IfLast, IfLastBrief, IfLastDetailed) { (in, s) =>
|
||||
if(s.commands.isEmpty) in.arguments :: s else s
|
||||
// TODO: nest
|
||||
def ifLast = Command.single(IfLast, IfLastBrief, IfLastDetailed) { (s, arg) =>
|
||||
if(s.commands.isEmpty) arg :: s else s
|
||||
}
|
||||
def append = Command.simple(Append, AppendLastBrief, AppendLastDetailed) { (in, s) =>
|
||||
s.copy(commands = s.commands :+ in.arguments)
|
||||
// TODO: nest
|
||||
def append = Command.single(Append, AppendLastBrief, AppendLastDetailed) { (s, arg) =>
|
||||
s.copy(commands = s.commands :+ arg)
|
||||
}
|
||||
|
||||
def setOnFailure = Command.simple(OnFailure, OnFailureBrief, OnFailureDetailed) { (in, s) =>
|
||||
s.copy(onFailure = Some(in.arguments))
|
||||
// TODO: nest
|
||||
def setOnFailure = Command.single(OnFailure, OnFailureBrief, OnFailureDetailed) { (s, arg) =>
|
||||
s.copy(onFailure = Some(arg))
|
||||
}
|
||||
|
||||
def reload = Command.simple(ReloadCommand, ReloadBrief, ReloadDetailed) { (in, s) =>
|
||||
def reload = Command.command(ReloadCommand, ReloadBrief, ReloadDetailed) { s =>
|
||||
runExitHooks(s).reload
|
||||
}
|
||||
|
||||
def defaults = Command.simple(DefaultsCommand) { (in, s) =>
|
||||
def defaults = Command.command(DefaultsCommand) { s =>
|
||||
s ++ DefaultCommands
|
||||
}
|
||||
|
||||
def initialize = Command.simple(InitCommand) { (in, s) =>
|
||||
def initialize = Command.command(InitCommand) { s =>
|
||||
/*"load-commands -base ~/.sbt/commands" :: */readLines( readable( sbtRCs(s) ) ) ::: s
|
||||
}
|
||||
|
||||
def read = Command.simple(ReadCommand, ReadBrief, ReadDetailed) { (in, s) =>
|
||||
getSource(in, s.baseDir) match
|
||||
def readParser(s: State) =
|
||||
{
|
||||
val files = (token(Space) ~> fileParser(s.baseDir)).+
|
||||
val portAndSuccess = token(OptSpace) ~> Port
|
||||
portAndSuccess || files
|
||||
}
|
||||
|
||||
def read = Command(ReadCommand, ReadBrief, ReadDetailed)(s => readParser(s) map doRead(s))
|
||||
|
||||
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
|
||||
arg match
|
||||
{
|
||||
case Left(portAndSuccess) =>
|
||||
val port = math.abs(portAndSuccess)
|
||||
|
|
@ -157,12 +162,6 @@ object Commands
|
|||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
private def getSource(in: Input, baseDirectory: File) =
|
||||
{
|
||||
try { Left(in.line.stripPrefix(ReadCommand).trim.toInt) }
|
||||
catch { case _: NumberFormatException => Right(in.splitArgs map { p => new File(baseDirectory, p) }) }
|
||||
}
|
||||
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
|
||||
{
|
||||
// split into two connections because this first connection ends the previous communication
|
||||
|
|
@ -173,15 +172,19 @@ object Commands
|
|||
if(message eq null) None else Some(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: nest
|
||||
def continuous =
|
||||
Command( Help(continuousBriefHelp) ) { case (in, s) if in.line startsWith ContinuousExecutePrefix =>
|
||||
Command.single(ContinuousExecutePrefix, Help(continuousBriefHelp) ) { (s, arg) =>
|
||||
withAttribute(s, Watch.key, "Continuous execution not configured.") { w =>
|
||||
Watched.executeContinuously(w, s, in)
|
||||
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
|
||||
Watched.executeContinuously(w, s, arg, repeat)
|
||||
}
|
||||
}
|
||||
|
||||
def history = Command( historyHelp: _* ) { case (in, s) if in.line startsWith "!" =>
|
||||
def history = Command.command("!!")(s => s)
|
||||
//TODO: convert
|
||||
/*def history = Command( historyHelp: _* ) { case (in, s) if in.line startsWith "!" =>
|
||||
val logError = (msg: String) => CommandSupport.logger(s).error(msg)
|
||||
HistoryCommands(in.line.substring(HistoryPrefix.length).trim, (s get HistoryPath.key) getOrElse None, 500/*JLine.MaxHistorySize*/, logError) match
|
||||
{
|
||||
|
|
@ -190,13 +193,13 @@ object Commands
|
|||
(commands ::: s).continue
|
||||
case None => s.fail
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
def indent(withStar: Boolean) = if(withStar) "\t*" else "\t "
|
||||
def listProject(name: String, current: Boolean, log: Logger) = log.info( indent(current) + name )
|
||||
|
||||
def act = error("TODO")
|
||||
def projects = Command.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) =>
|
||||
def projects = Command.command(ProjectsCommand, projectsBrief, projectsDetailed ) { s =>
|
||||
val log = logger(s)
|
||||
val session = Project.session(s)
|
||||
val structure = Project.structure(s)
|
||||
|
|
@ -214,96 +217,36 @@ object Commands
|
|||
case Some(nav) => f(nav)
|
||||
}
|
||||
|
||||
def project = Command.simple(ProjectCommand, projectBrief, projectDetailed ) { (in,s) =>
|
||||
val to = in.arguments
|
||||
val session = Project.session(s)
|
||||
val structure = Project.structure(s)
|
||||
val uri = session.currentBuild
|
||||
def setProject(id: String) = updateCurrent(s.put(SessionKey, session.setCurrent(uri, id)))
|
||||
if(to.isEmpty)
|
||||
{
|
||||
logger(s).info(session.currentProject(uri) + " (in build " + uri + ")")
|
||||
s
|
||||
}
|
||||
else if(to == "/")
|
||||
{
|
||||
val id = Load.getRootProject(structure.units)(uri)
|
||||
setProject(id)
|
||||
}
|
||||
else if(to.startsWith("^"))
|
||||
{
|
||||
val newBuild = (new java.net.URI(to substring 1)).normalize
|
||||
if(structure.units contains newBuild)
|
||||
updateCurrent(s.put(SessionKey, session.setCurrent(uri, session currentProject uri)))
|
||||
else
|
||||
{
|
||||
logger(s).error("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).")
|
||||
s
|
||||
}
|
||||
}
|
||||
/* else if(to.forall(_ == '.'))
|
||||
if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined
|
||||
else if( structure.units(uri).defined.contains(to) )
|
||||
setProject(to)
|
||||
else
|
||||
{
|
||||
logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects).")
|
||||
s.fail
|
||||
}
|
||||
}
|
||||
def project = Command(ProjectCommand, projectBrief, projectDetailed)(ProjectNavigation.command)
|
||||
|
||||
def exit = Command( Help(exitBrief) ) {
|
||||
case (in, s) if TerminateActions contains in.line =>
|
||||
runExitHooks(s).exit(true)
|
||||
}
|
||||
def exit = Command.command(TerminateAction, Help(exitBrief) :: Nil ) ( doExit )
|
||||
|
||||
def discover = Command.simple(Discover, DiscoverBrief, DiscoverDetailed) { (in, s) =>
|
||||
def doExit(s: State): State = runExitHooks(s).exit(true)
|
||||
|
||||
// TODO: tab completion, low priority
|
||||
def discover = Command.single(Discover, DiscoverBrief, DiscoverDetailed) { (s, arg) =>
|
||||
withAttribute(s, Analysis, "No analysis to process.") { analysis =>
|
||||
val command = Parse.discover(in.arguments)
|
||||
val command = Parse.discover(arg)
|
||||
val discovered = build.Build.discover(analysis, command)
|
||||
println(discovered.mkString("\n"))
|
||||
s
|
||||
}
|
||||
}
|
||||
def compile = Command.simple(CompileName, CompileBrief, CompileDetailed ) { (in, s) =>
|
||||
val command = Parse.compile(in.arguments)(s.baseDir)
|
||||
// TODO: tab completion, low priority
|
||||
def compile = Command.single(CompileName, CompileBrief, CompileDetailed ) { (s, arg) =>
|
||||
val command = Parse.compile(arg)(s.baseDir)
|
||||
try {
|
||||
val analysis = build.Build.compile(command, s.configuration)
|
||||
s.put(Analysis, analysis)
|
||||
} catch { case e: xsbti.CompileFailed => s.fail /* already logged */ }
|
||||
}
|
||||
|
||||
def loadProject = Command.simple(LoadProject, LoadProjectBrief, LoadProjectDetailed) { (in, s) =>
|
||||
def loadProject = Command.command(LoadProject, LoadProjectBrief, LoadProjectDetailed) { s =>
|
||||
val structure = Load.defaultLoad(s, logger(s))
|
||||
val session = Load.initialSession(structure)
|
||||
val newAttrs = s.attributes.put(StructureKey, structure).put(SessionKey, session)
|
||||
val newState = s.copy(attributes = newAttrs)
|
||||
updateCurrent(runExitHooks(newState))
|
||||
}
|
||||
|
||||
def updateCurrent(s: State): State =
|
||||
{
|
||||
val structure = Project.structure(s)
|
||||
val (uri, id) = Project.current(s)
|
||||
val ref = ProjectRef(uri, id)
|
||||
val project = Load.getProject(structure.units, uri, id)
|
||||
logger(s).info("Set current project to " + id + " (in build " + uri +")")
|
||||
|
||||
val data = structure.data
|
||||
val historyPath = HistoryPath(ref).get(data).flatMap(identity)
|
||||
val newAttrs = s.attributes.put(Watch.key, makeWatched(data, ref, project)).put(HistoryPath.key, historyPath)
|
||||
s.copy(attributes = newAttrs)
|
||||
}
|
||||
def makeWatched(data: Settings[Scope], ref: ProjectRef, project: Project): Watched =
|
||||
{
|
||||
def getWatch(ref: ProjectRef) = Watch(ref).get(data)
|
||||
getWatch(ref) match
|
||||
{
|
||||
case Some(currentWatch) =>
|
||||
val subWatches = project.uses flatMap { p => getWatch(p) }
|
||||
Watched.multi(currentWatch, subWatches)
|
||||
case None => Watched.empty
|
||||
}
|
||||
Project.updateCurrent(runExitHooks(newState))
|
||||
}
|
||||
|
||||
def handleException(e: Throwable, s: State, trace: Boolean = true): State = {
|
||||
|
|
@ -318,8 +261,9 @@ object Commands
|
|||
s.copy(exitHooks = Set.empty)
|
||||
}
|
||||
|
||||
def loadCommands = Command.simple(LoadCommand, Parse.helpBrief(LoadCommand, LoadCommandLabel), Parse.helpDetail(LoadCommand, LoadCommandLabel, true) ) { (in, s) =>
|
||||
applyCommands(s, buildCommands(in.arguments, s.configuration))
|
||||
// TODO: tab completion, low priority
|
||||
def loadCommands = Command.single(LoadCommand, Parse.helpBrief(LoadCommand, LoadCommandLabel), Parse.helpDetail(LoadCommand, LoadCommandLabel, true) ) { (s, arg) =>
|
||||
applyCommands(s, buildCommands(arg, s.configuration))
|
||||
}
|
||||
|
||||
def buildCommands(arguments: String, configuration: xsbti.AppConfiguration): Either[Throwable, Seq[Any]] =
|
||||
|
|
@ -370,33 +314,34 @@ object Commands
|
|||
|
||||
def addAlias(s: State, name: String, value: String): State =
|
||||
{
|
||||
val in = Input(name, None)
|
||||
if(in.name == name) {
|
||||
if(Command validID name) {
|
||||
val removed = removeAlias(s, name)
|
||||
if(value.isEmpty) removed else removed.copy(processors = new Alias(name, value) +: removed.processors)
|
||||
if(value.isEmpty) removed else removed.copy(processors = newAlias(name, value) +: removed.processors)
|
||||
} else {
|
||||
System.err.println("Invalid alias name '" + name + "'.")
|
||||
s.fail
|
||||
}
|
||||
}
|
||||
def removeAlias(s: State, name: String): State =
|
||||
s.copy(processors = s.processors.filter { case a: Alias if a.name == name => false; case _ => true } )
|
||||
def removeAlias(s: State, name: String): State = s.copy(processors = s.processors.filter(c => !isAliasNamed(name, c)) )
|
||||
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
|
||||
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((alias,_)) => name != alias }
|
||||
|
||||
def printAliases(s: State): Unit = {
|
||||
val strings = aliasStrings(s)
|
||||
if(!strings.isEmpty) println( strings.mkString("\t", "\n\t","") )
|
||||
}
|
||||
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
|
||||
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
|
||||
def printAliases(s: State): Unit = printAliases(allAliases(s))
|
||||
def printAliases(as: Seq[(String,String)]): Unit =
|
||||
for( (name,value) <- as)
|
||||
println("\t" + name + " = " + value)
|
||||
|
||||
def printAlias(s: State, name: String): Unit =
|
||||
for(a <- aliases(s)) if (a.name == name) println("\t" + name + " = " + a.value)
|
||||
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
|
||||
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
|
||||
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
|
||||
s.processors.flatMap(c => getAlias(c).filter(tupled(pred)))
|
||||
|
||||
def aliasStrings(s: State) = aliases(s).map(a => a.name + " = " + a.value)
|
||||
def aliases(s: State) = s.processors collect { case a: Alias => a }
|
||||
|
||||
final class Alias(val name: String, val value: String) extends Command {
|
||||
assert(name.length > 0)
|
||||
assert(value.length > 0)
|
||||
def help = _ => Nil
|
||||
def run = (in, s) => if(in.name == name) Some((value + " " + in.arguments) :: s) else None
|
||||
}
|
||||
def newAlias(name: String, value: String): Command =
|
||||
Command(name, (name, "<alias>"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
|
||||
def aliasBody(name: String, value: String)(state: State): Parser[State] =
|
||||
Parser(Command.combine(removeAlias(state,name).processors)(state))(value)
|
||||
|
||||
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias")
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ package sbt
|
|||
import java.io.File
|
||||
import java.net.URI
|
||||
import Project._
|
||||
import Command.{HistoryPath,Watch}
|
||||
import CommandSupport.logger
|
||||
|
||||
final case class Project(id: String, base: File, aggregate: Seq[ProjectRef] = Nil, dependencies: Seq[Project.ClasspathDependency] = Nil, inherits: Seq[ProjectRef] = Nil,
|
||||
settings: Seq[Project.Setting[_]] = Project.defaultSettings, configurations: Seq[Configuration] = Configurations.default)
|
||||
|
|
@ -37,6 +39,31 @@ object Project extends Init[Scope]
|
|||
val (unit, it) = current(state)
|
||||
ProjectRef(Some(unit), Some(it))
|
||||
}
|
||||
def updateCurrent(s: State): State =
|
||||
{
|
||||
val structure = Project.structure(s)
|
||||
val (uri, id) = Project.current(s)
|
||||
val ref = ProjectRef(uri, id)
|
||||
val project = Load.getProject(structure.units, uri, id)
|
||||
logger(s).info("Set current project to " + id + " (in build " + uri +")")
|
||||
|
||||
val data = structure.data
|
||||
val historyPath = HistoryPath(ref).get(data).flatMap(identity)
|
||||
val newAttrs = s.attributes.put(Watch.key, makeWatched(data, ref, project)).put(HistoryPath.key, historyPath)
|
||||
s.copy(attributes = newAttrs)
|
||||
}
|
||||
|
||||
def makeWatched(data: Settings[Scope], ref: ProjectRef, project: Project): Watched =
|
||||
{
|
||||
def getWatch(ref: ProjectRef) = Watch(ref).get(data)
|
||||
getWatch(ref) match
|
||||
{
|
||||
case Some(currentWatch) =>
|
||||
val subWatches = project.uses flatMap { p => getWatch(p) }
|
||||
Watched.multi(currentWatch, subWatches)
|
||||
case None => Watched.empty
|
||||
}
|
||||
}
|
||||
def display(scoped: ScopedKey[_]): String = Scope.display(scoped.scope, scoped.key.label)
|
||||
|
||||
def mapScope(f: Scope => Scope) = new (ScopedKey ~> ScopedKey) { def apply[T](key: ScopedKey[T]) =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009, 2010, 2011 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import ProjectNavigation._
|
||||
import Project.{SessionKey, updateCurrent}
|
||||
import CommandSupport.logger
|
||||
import complete.{DefaultParsers, Parser}
|
||||
import DefaultParsers._
|
||||
import java.net.URI
|
||||
|
||||
object ProjectNavigation
|
||||
{
|
||||
sealed trait Navigate
|
||||
final object ShowCurrent extends Navigate
|
||||
final object Root extends Navigate
|
||||
final class ChangeBuild(val base: URI) extends Navigate
|
||||
final class ChangeProject(val id: String) extends Navigate
|
||||
|
||||
def command(s: State): Parser[State] =
|
||||
if(s get Project.SessionKey isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command
|
||||
}
|
||||
final class ProjectNavigation(s: State)
|
||||
{
|
||||
val session = Project session s
|
||||
val structure = Project structure s
|
||||
val builds = structure.units.keys.toSet
|
||||
val (uri, pid) = session.current
|
||||
val projects = Load.getBuild(structure.units, uri).defined.keys
|
||||
|
||||
def setProject(uri: URI, id: String) = updateCurrent(s.put(SessionKey, session.setCurrent(uri, id)))
|
||||
def getRoot(uri: URI) = Load.getRootProject(structure.units)(uri)
|
||||
|
||||
def apply(action: Navigate): State =
|
||||
action match
|
||||
{
|
||||
case ShowCurrent => show(); s
|
||||
case Root => setProject(uri, getRoot(uri))
|
||||
case b: ChangeBuild => changeBuild(b.base)
|
||||
/* else if(to.forall(_ == '.'))
|
||||
if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined
|
||||
case s: ChangeProject => selectProject(s.id)
|
||||
}
|
||||
|
||||
def show(): Unit = logger(s).info(pid + " (in build " + uri + ")")
|
||||
def selectProject(to: String): State =
|
||||
if( structure.units(uri).defined.contains(to) )
|
||||
setProject(uri, to)
|
||||
else
|
||||
fail("Invalid project name '" + to + "' (type 'projects' to list available projects).")
|
||||
|
||||
def changeBuild(to: URI): State =
|
||||
{
|
||||
val newBuild = (uri resolve to).normalize
|
||||
if(structure.units contains newBuild)
|
||||
setProject(newBuild, getRoot(newBuild))
|
||||
else
|
||||
fail("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).")
|
||||
}
|
||||
def fail(msg: String): State =
|
||||
{
|
||||
logger(s).error(msg)
|
||||
s.fail
|
||||
}
|
||||
|
||||
import complete.Parser._
|
||||
import complete.Parsers._
|
||||
|
||||
val parser: Parser[Navigate] =
|
||||
{
|
||||
val buildP = token('^') ~> token(Uri(builds) map(u => new ChangeBuild(u) ) )
|
||||
val projectP = token(ID map (id => new ChangeProject(id)) examples projects.toSet )
|
||||
success(ShowCurrent) | ( token(Space) ~> (token('/' ^^^ Root) | buildP | projectP) )
|
||||
}
|
||||
val command: Parser[State] = parser map apply
|
||||
}
|
||||
|
|
@ -22,6 +22,10 @@ trait Identity {
|
|||
override final def toString = super.toString
|
||||
}
|
||||
|
||||
object Next extends Enumeration {
|
||||
val Reload, Fail, Done, Continue = Value
|
||||
}
|
||||
|
||||
trait StateOps {
|
||||
def process(f: (String, State) => State): State
|
||||
def ::: (commands: Seq[String]): State
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ package sbt
|
|||
import std.TaskExtra._
|
||||
import Task._
|
||||
import Project.{ScopedKey, Setting}
|
||||
import parse.Parser
|
||||
import complete.Parser
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ object Watched
|
|||
val PollDelaySeconds = 1
|
||||
def isEnter(key: Int): Boolean = key == 10 || key == 13
|
||||
|
||||
def executeContinuously(watched: Watched, s: State, in: Input): State =
|
||||
def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State =
|
||||
{
|
||||
@tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate)
|
||||
val sourcesFinder = watched.watchPaths
|
||||
|
|
@ -41,7 +41,7 @@ object Watched
|
|||
val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, PollDelaySeconds, watchState)(shouldTerminate)
|
||||
|
||||
if(triggered)
|
||||
(in.arguments :: FailureWall :: in.line :: s).put(ContinuousState, newWatchState)
|
||||
(next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState)
|
||||
else
|
||||
{
|
||||
while (System.in.available() > 0) System.in.read()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt.parse
|
||||
package sbt.complete
|
||||
|
||||
/**
|
||||
* Represents a set of completions.
|
||||
|
|
@ -72,9 +72,8 @@ sealed trait Completion
|
|||
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 }
|
||||
}
|
||||
final class DisplayOnly(display0: String) extends Completion
|
||||
final class DisplayOnly(val display: String) extends Completion
|
||||
{
|
||||
lazy val display = display0
|
||||
def isEmpty = display.isEmpty
|
||||
def append = ""
|
||||
override def toString = "{" + display + "}"
|
||||
|
|
|
|||
|
|
@ -83,4 +83,19 @@ object HistoryCommands
|
|||
else
|
||||
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*/
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2011 Mark Harrah
|
||||
*/
|
||||
package sbt.parse
|
||||
package sbt.complete
|
||||
|
||||
import jline.{CandidateListCompletionHandler,Completor,CompletionHandler,ConsoleReader}
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -41,14 +41,17 @@ object JLineCompletion
|
|||
customCompletor(str => convertCompletions(Parser.completions(p, str)))
|
||||
def convertCompletions(c: Completions): (Seq[String], Seq[String]) =
|
||||
{
|
||||
( (Seq[String](), Seq[String]()) /: c.get) { case ( t @ (insert,display), comp) =>
|
||||
if(comp.isEmpty) t else (insert :+ comp.append, insert :+ comp.display)
|
||||
}
|
||||
val (insert, 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 =
|
||||
reader => {
|
||||
val success = complete(beforeCursor(reader), f, reader, false)
|
||||
val success = complete(beforeCursor(reader), f, reader)
|
||||
reader.flushConsole()
|
||||
success
|
||||
}
|
||||
|
|
@ -59,29 +62,34 @@ object JLineCompletion
|
|||
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)
|
||||
if(insert.isEmpty)
|
||||
inserted
|
||||
else
|
||||
{
|
||||
lazy val common = commonPrefix(insert)
|
||||
if(inserted || common.isEmpty)
|
||||
{
|
||||
showCompletions(display, reader)
|
||||
reader.drawLine()
|
||||
true
|
||||
}
|
||||
val common = commonPrefix(insert)
|
||||
if(common.isEmpty)
|
||||
if(display.isEmpty)
|
||||
()
|
||||
else
|
||||
{
|
||||
reader.getCursorBuffer.write(common)
|
||||
reader.redrawLine()
|
||||
complete(beforeCursor + common, completions, reader, true)
|
||||
}
|
||||
}
|
||||
showCompletions(display, reader)
|
||||
else
|
||||
appendCompletion(common, reader)
|
||||
|
||||
!(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)
|
||||
|
||||
def commonPrefix(s: Seq[String]): String = if(s.isEmpty) "" else s reduceLeft commonPrefix
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package sbt
|
|||
|
||||
import jline.{Completor, ConsoleReader}
|
||||
import java.io.File
|
||||
import complete.Parser
|
||||
|
||||
abstract class JLine extends LineReader
|
||||
{
|
||||
|
|
@ -56,27 +57,21 @@ private object JLine
|
|||
val MaxHistorySize = 500
|
||||
}
|
||||
|
||||
trait LineReader extends NotNull
|
||||
trait LineReader
|
||||
{
|
||||
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 =
|
||||
{
|
||||
val cr = new ConsoleReader
|
||||
cr.setBellEnabled(false)
|
||||
JLine.initializeHistory(cr, historyPath)
|
||||
// cr.addCompletor(new LazyCompletor(completor))
|
||||
sbt.complete.JLineCompletion.installCustomCompletor(cr, complete)
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2010 Mark Harrah
|
||||
* Copyright 2008, 2010, 2011 Mark Harrah
|
||||
*/
|
||||
package sbt.parse
|
||||
package sbt.complete
|
||||
|
||||
import Parser._
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ sealed trait Parser[+T]
|
|||
def resultEmpty: Option[T]
|
||||
def result: Option[T] = None
|
||||
def completions: Completions
|
||||
def valid: Boolean = true
|
||||
def valid: Boolean
|
||||
def isTokenStart = false
|
||||
}
|
||||
sealed trait RichParser[A]
|
||||
|
|
@ -54,56 +54,8 @@ sealed trait RichParser[A]
|
|||
|
||||
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])
|
||||
{
|
||||
|
|
@ -135,37 +87,22 @@ object Parser
|
|||
if(a.valid) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
else Invalid
|
||||
|
||||
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 {
|
||||
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 (None, Some(bv)) => a map { av => (av, bv) }
|
||||
case (None, None) => new SeqParser(a,b)
|
||||
}
|
||||
}
|
||||
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]] =
|
||||
if(a.valid)
|
||||
if(b.valid) new HetParser(a,b) else a.map( Left(_) )
|
||||
|
|
@ -173,14 +110,14 @@ object Parser
|
|||
b.map( Right(_) )
|
||||
|
||||
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 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]] =
|
||||
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(max >= min, "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")")
|
||||
|
|
@ -189,8 +126,8 @@ object Parser
|
|||
if(repeated.valid)
|
||||
repeated.result match
|
||||
{
|
||||
case Some(value) => success(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 Some(value) => successStrict(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
|
||||
case None => if(max.isZero) successStrict(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
|
||||
}
|
||||
else if(min == 0)
|
||||
invalidButOptional
|
||||
|
|
@ -207,27 +144,55 @@ object Parser
|
|||
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
|
||||
}
|
||||
else Invalid
|
||||
case None => checkRepeated(success(Nil))
|
||||
case None => checkRepeated(successStrict(Nil))
|
||||
}
|
||||
}
|
||||
|
||||
def success[T](value: T): Parser[T] = new Parser[T] {
|
||||
override def result = Some(value)
|
||||
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
|
||||
}
|
||||
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 derive(c: Char) = Invalid
|
||||
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] =
|
||||
new CharacterClass(r contains _).examples(r.map(_.toString) : _*)
|
||||
def chars(legal: String): Parser[Char] =
|
||||
|
|
@ -237,29 +202,97 @@ object Parser
|
|||
}
|
||||
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 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))
|
||||
override def toString = "'" + ch + "'"
|
||||
}
|
||||
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 ~ {
|
||||
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 derive(c: Char) = error("Invalid.")
|
||||
override def valid = false
|
||||
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)
|
||||
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 + ")"
|
||||
}
|
||||
|
||||
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)
|
||||
lazy val resultEmpty = a.resultEmpty orElse b.resultEmpty
|
||||
lazy val completions = a.completions ++ b.completions
|
||||
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)
|
||||
lazy val resultEmpty = a.resultEmpty.map(Left(_)) orElse b.resultEmpty.map(Right(_))
|
||||
lazy val completions = a.completions ++ b.completions
|
||||
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 completions = {
|
||||
lazy val completions =
|
||||
a.completions flatMap { c =>
|
||||
apply(a)(c.append).resultEmpty match {
|
||||
case None => Completions.strict(Set.empty + c)
|
||||
case Some(av) => c x f(av).completions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def derive(c: Char) =
|
||||
{
|
||||
val common = a derive c flatMap f
|
||||
a.resultEmpty match
|
||||
{
|
||||
case Some(av) => common | f(av).derive(c)
|
||||
case Some(av) => common | derive1(f(av), c)
|
||||
case None => common
|
||||
}
|
||||
}
|
||||
override def isTokenStart = a.isTokenStart
|
||||
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
|
||||
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 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
|
||||
def derive(c: Char) = (p derive c) filter f
|
||||
lazy val completions = p.completions filterS { s => apply(p)(s).resultEmpty.filter(f).isDefined }
|
||||
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)
|
||||
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 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)
|
||||
lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isDefined )
|
||||
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 completions = Completions.empty
|
||||
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 resultEmpty = delegate.resultEmpty
|
||||
lazy val completions = if(fixed.isEmpty) Completions.empty else Completions(fixed map(f => Completion.suggestion(f)) )
|
||||
def derive(c: Char) = examples(delegate derive c, fixed.collect { case x if x.length > 0 && x(0) == c => x substring 1 })
|
||||
lazy val resultEmpty = delegate.resultEmpty
|
||||
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) + ")"
|
||||
}
|
||||
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)
|
||||
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))
|
||||
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 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
|
||||
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 derive(c: Char) = (delegate derive c).map(Some(_))
|
||||
lazy val completions = Completion.empty +: delegate.completions
|
||||
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(max >= min, "Minimum occurences must be less than the maximum occurences")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008,2010 Mark Harrah
|
||||
*/
|
||||
package sbt.parse
|
||||
package sbt.complete
|
||||
|
||||
sealed trait UpperBound
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package sbt.parse
|
||||
package sbt.complete
|
||||
|
||||
import Parser._
|
||||
import org.scalacheck._
|
||||
|
|
@ -25,14 +25,14 @@ object JLineTest
|
|||
}
|
||||
object ParserTest extends Properties("Completing Parser")
|
||||
{
|
||||
val wsc = charClass(_.isWhitespace)
|
||||
val ws = ( wsc + ) examples(" ")
|
||||
val optWs = ( wsc * ) examples("")
|
||||
import Parsers._
|
||||
|
||||
val nested = (token("a1") ~ token("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) =
|
||||
( ("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 c3") = checkSingle("a1b2c", Completion.suggestStrict("3"))()
|
||||
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
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue