make explicit the separation between parsing and execution

Parser[() => State] instead of Parser[State]
This commit is contained in:
Mark Harrah 2011-01-22 15:01:10 -05:00
parent c3a265dbd3
commit 784d83af17
5 changed files with 43 additions and 37 deletions

View File

@ -9,15 +9,15 @@ package sbt
sealed trait Command { sealed trait Command {
def help: Seq[Help] def help: Seq[Help]
def parser: State => Parser[State] def parser: State => Parser[() => State]
def tags: AttributeMap def tags: AttributeMap
def tag[T](key: AttributeKey[T], value: T): Command def tag[T](key: AttributeKey[T], value: T): Command
} }
private[sbt] final class SimpleCommand(val name: String, val help: Seq[Help], val parser: State => Parser[State], val tags: AttributeMap) extends Command { 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." ) 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)) 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 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)) def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand = new ArbitraryCommand(parser, help, tags.put(key, value))
} }
@ -35,40 +35,42 @@ object Command
def command(name: String)(f: State => State): Command = command(name, Nil)(f) 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, 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 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 = def apply(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[() => State]): Command =
apply(name, Help(name, briefHelp, detail) )(parser) 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 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 = 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) args(name, display, Help(name, briefHelp, detail) )(f)
def args(name: String, display: String, help: Help*)(f: (State, Seq[String]) => State): Command = def args(name: String, display: String, help: Help*)(f: (State, Seq[String]) => State): Command =
apply(name, help : _*)( state => spaceDelimited(display) map f.curried(state) ) apply(name, help : _*)( state => spaceDelimited(display) map apply1(f, state) )
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command = def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
single(name, Help(name, briefHelp, detail) )(f) single(name, Help(name, briefHelp, detail) )(f)
def single(name: String, help: Help*)(f: (State, String) => State): Command = def single(name: String, help: Help*)(f: (State, String) => State): Command =
apply(name, help : _*)( state => token(any.+.string map f.curried(state)) ) apply(name, help : _*)( state => token(any.+.string map apply1(f, state)) )
def custom(parser: State => Parser[State], help: Seq[Help]): Command = new ArbitraryCommand(parser, help, AttributeMap.empty) def custom(parser: State => Parser[() => State], help: Seq[Help]): Command = new ArbitraryCommand(parser, help, AttributeMap.empty)
def validID(name: String) = def validID(name: String) =
Parser(OpOrID)(name).resultEmpty.isDefined Parser(OpOrID)(name).resultEmpty.isDefined
def combine(cmds: Seq[Command]): State => Parser[State] = def combine(cmds: Seq[Command]): State => Parser[() => State] =
{ {
val (simple, arbs) = separateCommands(cmds) val (simple, arbs) = separateCommands(cmds)
state => (simpleParser(simple)(state) /: arbs.map(_ parser state) ){ _ | _ } state => (simpleParser(simple)(state) /: arbs.map(_ parser state) ){ _ | _ }
} }
private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) = private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
Collections.separate(cmds){ case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) } Collections.separate(cmds){ case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) }
private[this] def apply1[A,B,C](f: (A,B) => C, a: A): B => () => C =
b => () => f(a,b)
def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[State] = def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[() => State] =
simpleParser(cmds.map(sc => (sc.name, sc.parser)).toMap ) simpleParser(cmds.map(sc => (sc.name, sc.parser)).toMap )
def simpleParser(commandMap: Map[String, State => Parser[State]]): State => Parser[State] = def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
(state: State) => token(OpOrID examples commandMap.keys.toSet) flatMap { id => (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) } (commandMap get id) match { case None => failure("No command named '" + id + "'"); case Some(c) => c(state) }
} }
@ -78,7 +80,7 @@ object Command
val parser = combine(state.processors) val parser = combine(state.processors)
Parser.result(parser(state), command) match Parser.result(parser(state), command) match
{ {
case Right(s) => s case Right(s) => s() // apply command. command side effects happen here
case Left((msg,pos)) => case Left((msg,pos)) =>
val errMsg = commandError(command, msg, pos) val errMsg = commandError(command, msg, pos)
logger(state).info(errMsg) logger(state).info(errMsg)

View File

@ -60,7 +60,7 @@ object Commands
def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover, def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover,
projects, project, setOnFailure, ifLast, multi, shell, alias, append, nop) projects, project, setOnFailure, ifLast, multi, shell, alias, append, nop)
def nop = Command.custom(successStrict, Nil) def nop = Command.custom(s => success(() => s), Nil)
def ignore = Command.command(FailureWall)(identity) def ignore = Command.command(FailureWall)(identity)
def detail(selected: Iterable[String])(h: Help): Option[String] = def detail(selected: Iterable[String])(h: Help): Option[String] =
@ -82,12 +82,18 @@ object Commands
def alias = Command(AliasCommand, AliasBrief, AliasDetailed) { s => def alias = Command(AliasCommand, AliasBrief, AliasDetailed) { s =>
val name = token(OpOrID.examples( aliasNames(s) : _*) ) val name = token(OpOrID.examples( aliasNames(s) : _*) )
val assign = token(Space ~ '=' ~ Space) ~> matched(Command.combine(s.processors)(s), partial = true) val assign = token(Space ~ '=' ~ Space) ~> matched(Command.combine(s.processors)(s), partial = true)
(OptSpace ~> (name ~ assign.?).?) map { val base = (OptSpace ~> (name ~ assign.?).?)
applyEffect(base)(t => runAlias(s, t) )
}
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] =
p map { t => () => f(t) }
def runAlias(s: State, args: Option[(String, Option[String])]): State =
args match
{
case Some((name, Some(value))) => addAlias(s, name.trim, value.trim) case Some((name, Some(value))) => addAlias(s, name.trim, value.trim)
case Some((x, None)) if !x.isEmpty=> printAlias(s, x.trim); s case Some((x, None)) if !x.isEmpty=> printAlias(s, x.trim); s
case None => printAliases(s); s case None => printAliases(s); s
} }
}
def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s => def shell = Command.command(Shell, ShellBrief, ShellDetailed) { s =>
val historyPath = (s get HistoryPath.key) getOrElse Some((s.baseDir / ".history").asFile) val historyPath = (s get HistoryPath.key) getOrElse Some((s.baseDir / ".history").asFile)
@ -138,7 +144,7 @@ object Commands
portAndSuccess || files portAndSuccess || files
} }
def read = Command(ReadCommand, ReadBrief, ReadDetailed)(s => readParser(s) map doRead(s)) def read = Command(ReadCommand, ReadBrief, ReadDetailed)(s => applyEffect(readParser(s))(doRead(s)) )
def doRead(s: State)(arg: Either[Int, Seq[File]]): State = def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
arg match arg match
@ -340,7 +346,7 @@ object Commands
def newAlias(name: String, value: String): Command = def newAlias(name: String, value: String): Command =
Command(name, (name, "<alias>"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value)) Command(name, (name, "<alias>"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[State] = def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
Parser(Command.combine(removeAlias(state,name).processors)(state))(value) Parser(Command.combine(removeAlias(state,name).processors)(state))(value)
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias") val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias")

View File

@ -18,7 +18,7 @@ object ProjectNavigation
final class ChangeBuild(val base: URI) extends Navigate final class ChangeBuild(val base: URI) extends Navigate
final class ChangeProject(val id: String) extends Navigate final class ChangeProject(val id: String) extends Navigate
def command(s: State): Parser[State] = def command(s: State): Parser[() => State] =
if(s get Project.SessionKey isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command if(s get Project.SessionKey isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command
} }
final class ProjectNavigation(s: State) final class ProjectNavigation(s: State)
@ -73,5 +73,5 @@ final class ProjectNavigation(s: State)
val projectP = token(ID map (id => new ChangeProject(id)) examples projects.toSet ) val projectP = token(ID map (id => new ChangeProject(id)) examples projects.toSet )
success(ShowCurrent) | ( token(Space) ~> (token('/' ^^^ Root) | buildP | projectP) ) success(ShowCurrent) | ( token(Space) ~> (token('/' ^^^ Root) | buildP | projectP) )
} }
val command: Parser[State] = parser map apply val command: Parser[() => State] = Commands.applyEffect(parser)(apply)
} }

View File

@ -87,7 +87,7 @@ object Parser extends ParserMain
if(a.valid) { if(a.valid) {
a.result match a.result match
{ {
case Some(av) => if( f(av) ) successStrict( av ) else Invalid case Some(av) => if( f(av) ) success( av ) else Invalid
case None => new Filter(a, f) case None => new Filter(a, f)
} }
} }
@ -96,7 +96,7 @@ object Parser extends ParserMain
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)) => successStrict( (av, bv) ) case (Some(av), Some(bv)) => success( (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)
@ -110,7 +110,7 @@ object Parser extends ParserMain
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 successStrict(None) if(a.valid) new Optional(a) else success(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)
@ -126,8 +126,8 @@ object Parser extends ParserMain
if(repeated.valid) if(repeated.valid)
repeated.result match repeated.result match
{ {
case Some(value) => successStrict(revAcc reverse_::: value :: Nil) // revAcc should be Nil here case Some(value) => success(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) case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
} }
else if(min == 0) else if(min == 0)
invalidButOptional invalidButOptional
@ -144,7 +144,7 @@ object Parser extends ParserMain
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(successStrict(Nil)) case None => checkRepeated(success(Nil))
} }
} }
@ -183,14 +183,12 @@ trait ParserMain
implicit def literalRichParser(s: String): RichParser[String] = richParser(s) implicit def literalRichParser(s: String): RichParser[String] = richParser(s)
def failure[T](msg: String): Parser[T] = Invalid(msg) 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] {
def success[T](value: => T): Parser[T] = new ValidParser[T] { override def result = Some(value)
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(" + v + ")" override def toString = "success(" + value + ")"
} }
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] =
@ -204,7 +202,7 @@ trait ParserMain
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] { implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
def resultEmpty = None def resultEmpty = None
def derive(c: Char) = if(c == ch) successStrict(ch) else Invalid def derive(c: Char) = if(c == ch) success(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 + "'"
} }
@ -244,7 +242,7 @@ trait ParserMain
if(a.valid) { if(a.valid) {
a.result match a.result match
{ {
case Some(av) => successStrict( av ) case Some(av) => success( av )
case None => case None =>
if(check) checkMatches(a, completions.toSeq) if(check) checkMatches(a, completions.toSeq)
new Examples(a, completions) new Examples(a, completions)
@ -254,11 +252,11 @@ trait ParserMain
def matched(t: Parser[_], seenReverse: List[Char] = Nil, partial: Boolean = false): Parser[String] = def matched(t: Parser[_], seenReverse: List[Char] = Nil, partial: Boolean = false): Parser[String] =
if(!t.valid) if(!t.valid)
if(partial && !seenReverse.isEmpty) successStrict(seenReverse.reverse.mkString) else Invalid if(partial && !seenReverse.isEmpty) success(seenReverse.reverse.mkString) else Invalid
else if(t.result.isEmpty) else if(t.result.isEmpty)
new MatchedString(t, seenReverse, partial) new MatchedString(t, seenReverse, partial)
else else
successStrict(seenReverse.reverse.mkString) success(seenReverse.reverse.mkString)
def token[T](t: Parser[T]): Parser[T] = token(t, "", true) 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], description: String): Parser[T] = token(t, description, false)
@ -422,7 +420,7 @@ private final class StringLiteral(str: String, remaining: List[Char]) extends Va
private final class CharacterClass(f: Char => Boolean) extends ValidParser[Char] private final class CharacterClass(f: Char => Boolean) extends ValidParser[Char]
{ {
def resultEmpty = None def resultEmpty = None
def derive(c: Char) = if( f(c) ) successStrict(c) else Invalid def derive(c: Char) = if( f(c) ) success(c) else Invalid
def completions = Completions.empty def completions = Completions.empty
override def toString = "class()" override def toString = "class()"
} }

View File

@ -43,7 +43,7 @@ trait Parsers
(neg.toSeq ++ digits).mkString.toInt (neg.toSeq ++ digits).mkString.toInt
def mapOrFail[S,T](p: Parser[S])(f: S => T): Parser[T] = 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) } } p flatMap { s => try { success(f(s)) } catch { case e: Exception => failure(e.toString) } }
def spaceDelimited(display: String): Parser[Seq[String]] = (token(Space) ~> token(NotSpace, display)).* def spaceDelimited(display: String): Parser[Seq[String]] = (token(Space) ~> token(NotSpace, display)).*