From 1612af8dbb9f27e7cc68f372ea5e329cfc601b74 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 15 Oct 2012 12:42:27 -0400 Subject: [PATCH] Parser.failOnException method, don't let rhs of alias fail the parse. Fixes #572. alias only parses the right hand side for tab completion help. The assignment should happen whether or not the parse is successful because the context may change by the time the alias is actually evaluated. In particular, the 'set' command uses the loaded project for tab completion in 0.12.1. When a .sbtrc file is processed, the project has not been loaded yet, so aliases involving set fail. Wrapping the rhs in failOnException addresses this. --- main/command/BasicCommands.scala | 2 +- sbt/src/sbt-test/actions/aliasrc/.sbtrc | 1 + sbt/src/sbt-test/actions/aliasrc/test | 1 + util/complete/Parser.scala | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 sbt/src/sbt-test/actions/aliasrc/.sbtrc create mode 100644 sbt/src/sbt-test/actions/aliasrc/test diff --git a/main/command/BasicCommands.scala b/main/command/BasicCommands.scala index c0f435816..18fe40d90 100644 --- a/main/command/BasicCommands.scala +++ b/main/command/BasicCommands.scala @@ -169,7 +169,7 @@ object BasicCommands val name = token(OpOrID.examples( aliasNames(s) : _*) ) val assign = token(OptSpace ~ '=' ~ OptSpace) val sfree = removeAliases(s) - val to = matched(sfree.combinedParser, partial = true) | any.+.string + val to = matched(sfree.combinedParser, partial = true).failOnException | any.+.string val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?) applyEffect(base)(t => runAlias(s, t) ) } diff --git a/sbt/src/sbt-test/actions/aliasrc/.sbtrc b/sbt/src/sbt-test/actions/aliasrc/.sbtrc new file mode 100644 index 000000000..3d7d3773e --- /dev/null +++ b/sbt/src/sbt-test/actions/aliasrc/.sbtrc @@ -0,0 +1 @@ +alias info= set logLevel := Level.Info diff --git a/sbt/src/sbt-test/actions/aliasrc/test b/sbt/src/sbt-test/actions/aliasrc/test new file mode 100644 index 000000000..2ae698547 --- /dev/null +++ b/sbt/src/sbt-test/actions/aliasrc/test @@ -0,0 +1 @@ +> info diff --git a/util/complete/Parser.scala b/util/complete/Parser.scala index 7f0fba5db..a10d1bdcb 100644 --- a/util/complete/Parser.scala +++ b/util/complete/Parser.scala @@ -45,6 +45,9 @@ sealed trait RichParser[A] /** Uses the specified message if the original Parser fails.*/ def !!!(msg: String): Parser[A] + /** If an exception is thrown by the original Parser, + * capture it and fail locally instead of allowing the exception to propagate up and terminate parsing.*/ + def failOnException: Parser[A] def unary_- : Parser[Unit] def & (o: Parser[_]): Parser[A] @@ -173,6 +176,8 @@ object Parser extends ParserMain def onFailure[T](delegate: Parser[T], msg: String): Parser[T] = if(delegate.valid) new OnFailure(delegate, msg) else failure(msg) + def trapAndFail[T](delegate: Parser[T]): Parser[T] = + delegate.ifValid( new TrapAndFail(delegate) ) 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) @@ -233,6 +238,7 @@ trait ParserMain 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 !!!(msg: String): Parser[A] = onFailure(a, msg) + def failOnException: Parser[A] = trapAndFail(a) def unary_- = not(a) def & (o: Parser[_]) = and(a, o) @@ -425,6 +431,18 @@ private final case class Invalid(fail: Failure) extends Parser[Nothing] def valid = false def ifValid[S](p: => Parser[S]): Parser[S] = this } + +private final class TrapAndFail[A](a: Parser[A]) extends ValidParser[A] +{ + def result = try { a.result } catch { case e: Exception => None } + def resultEmpty = try { a.resultEmpty } catch { case e: Exception => fail(e) } + def derive(c: Char) = try { trapAndFail(a derive c) } catch { case e: Exception => Invalid(fail(e)) } + def completions(level: Int) = try { a.completions(level) } catch { case e: Exception => Completions.nil } + override def toString = "trap(" + a + ")" + override def isTokenStart = a.isTokenStart + private[this] def fail(e: Exception): Failure = mkFailure(e.toString) +} + private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A] { def result = a.result