From 21b95c1b72c728ff1c8e0c8b2bf791ce0b668180 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 25 Apr 2011 20:20:05 -0400 Subject: [PATCH] work on parser error handling --- util/complete/EditDistance.scala | 37 ++++ util/complete/Parser.scala | 314 +++++++++++++++++++++---------- util/complete/Parsers.scala | 19 +- 3 files changed, 262 insertions(+), 108 deletions(-) create mode 100644 util/complete/EditDistance.scala diff --git a/util/complete/EditDistance.scala b/util/complete/EditDistance.scala new file mode 100644 index 000000000..60137d533 --- /dev/null +++ b/util/complete/EditDistance.scala @@ -0,0 +1,37 @@ +package sbt.complete + +/** @author Paul Phillips*/ +object EditDistance { + /** Translated from the java version at + * http://www.merriampark.com/ld.htm + * which is declared to be public domain. + */ + def levenshtein(s: String, t: String, insertCost: Int, deleteCost: Int, subCost: Int, transposeCost: Int, transpositions: Boolean = false): Int = { + val n = s.length + val m = t.length + if (n == 0) return m + if (m == 0) return n + + val d = Array.ofDim[Int](n + 1, m + 1) + 0 to n foreach (x => d(x)(0) = x) + 0 to m foreach (x => d(0)(x) = x) + + for (i <- 1 to n ; val s_i = s(i - 1) ; j <- 1 to m) { + val t_j = t(j - 1) + val cost = if (s_i == t_j) 0 else 1 + + val c1 = d(i - 1)(j) + deleteCost + val c2 = d(i)(j - 1) + insertCost + val c3 = d(i - 1)(j - 1) + cost*subCost + + d(i)(j) = c1 min c2 min c3 + + if (transpositions) { + if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1)) + d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost*transposeCost) + } + } + + d(n)(m) + } +} \ No newline at end of file diff --git a/util/complete/Parser.scala b/util/complete/Parser.scala index 7d1409bc1..821603e1f 100644 --- a/util/complete/Parser.scala +++ b/util/complete/Parser.scala @@ -5,15 +5,18 @@ package sbt.complete import Parser._ import sbt.Types.{left, right, some} + import sbt.Collections.separate sealed trait Parser[+T] { def derive(i: Char): Parser[T] - def resultEmpty: Option[T] - def result: Option[T] = None + def resultEmpty: Result[T] + def result: Option[T] def completions: Completions - def valid: Boolean + def failure: Option[Failure] def isTokenStart = false + def ifValid[S](p: => Parser[S]): Parser[S] + def valid: Boolean } sealed trait RichParser[A] { @@ -39,6 +42,9 @@ sealed trait RichParser[A] def ??[B >: A](alt: B): Parser[B] def <~[B](b: Parser[B]): Parser[A] def ~>[B](b: Parser[B]): Parser[B] + + /** Uses the specified message if the original Parser fails.*/ + def !!!(msg: String): Parser[A] def unary_- : Parser[Unit] def & (o: Parser[_]): Parser[A] @@ -51,57 +57,110 @@ sealed trait RichParser[A] def string(implicit ev: A <:< Seq[Char]): Parser[String] /** Produces a Parser that filters the original parser. * If 'f' is not true when applied to the output of the original parser, the Parser returned by this method fails.*/ - def filter(f: A => Boolean): Parser[A] + def filter(f: A => Boolean, msg: String => String): Parser[A] def flatMap[B](f: A => Parser[B]): Parser[B] } object Parser extends ParserMain { + sealed abstract class Result[+T] { + def isFailure: Boolean + def isValid: Boolean + def errors: Seq[String] + def or[B >: T](b: => Result[B]): Result[B] + def either[B](b: => Result[B]): Result[Either[T,B]] + def map[B](f: T => B): Result[B] + def flatMap[B](f: T => Result[B]): Result[B] + def &&(b: => Result[_]): Result[T] + def filter(f: T => Boolean, msg: => String): Result[T] + def seq[B](b: => Result[B]): Result[(T,B)] = app(b)( (m,n) => (m,n) ) + def app[B,C](b: => Result[B])(f: (T, B) => C): Result[C] + def toEither: Either[Seq[String], T] + } + final case class Value[+T](value: T) extends Result[T] { + def isFailure = false + def isValid: Boolean = true + def errors = Nil + def app[B,C](b: => Result[B])(f: (T, B) => C): Result[C] = b match { + case fail: Failure => fail + case Value(bv) => Value(f(value, bv)) + } + def &&(b: => Result[_]): Result[T] = b match { case f: Failure => f; case _ => this } + def or[B >: T](b: => Result[B]): Result[B] = this + def either[B](b: => Result[B]): Result[Either[T,B]] = Value(Left(value)) + def map[B](f: T => B): Result[B] = Value(f(value)) + def flatMap[B](f: T => Result[B]): Result[B] = f(value) + def filter(f: T => Boolean, msg: => String): Result[T] = if(f(value)) this else mkFailure(msg) + def toEither = Right(value) + } + final class Failure(mkErrors: => Seq[String]) extends Result[Nothing] { + lazy val errors: Seq[String] = mkErrors + def isFailure = true + def isValid = false + def map[B](f: Nothing => B) = this + def flatMap[B](f: Nothing => Result[B]) = this + def or[B](b: => Result[B]): Result[B] = b match { + case v: Value[B] => v + case f: Failure => concatErrors(f) + } + def either[B](b: => Result[B]): Result[Either[Nothing,B]] = b match { + case Value(v) => Value(Right(v)) + case f: Failure => concatErrors(f) + } + def filter(f: Nothing => Boolean, msg: => String) = this + def app[B,C](b: => Result[B])(f: (Nothing, B) => C): Result[C] = this + def &&(b: => Result[_]) = this + def toEither = Left(errors) + + private[this] def concatErrors(f: Failure) = mkFailures(errors ++ f.errors) + } + def mkFailures(errors: => Seq[String]): Failure = new Failure(errors.distinct) + def mkFailure(error: => String): Failure = new Failure(error :: Nil) + def checkMatches(a: Parser[_], completions: Seq[String]) { - val bad = completions.filter( apply(a)(_).resultEmpty.isEmpty) + val bad = completions.filter( apply(a)(_).resultEmpty.isFailure) if(!bad.isEmpty) error("Invalid example completions: " + bad.mkString("'", "', '", "'")) } + def tuple[A,B](a: Option[A], b: Option[B]): Option[(A,B)] = + (a,b) match { case (Some(av), Some(bv)) => Some(av, bv); case _ => None } def mapParser[A,B](a: Parser[A], f: A => B): Parser[B] = - if(a.valid) { + a.ifValid { a.result match { case Some(av) => success( f(av) ) case None => new MapParser(a, f) } } - else Invalid def bindParser[A,B](a: Parser[A], f: A => Parser[B]): Parser[B] = - if(a.valid) { + a.ifValid { a.result match { case Some(av) => f(av) case None => new BindParser(a, f) } } - else Invalid - def filterParser[T](a: Parser[T], f: T => Boolean): Parser[T] = - if(a.valid) { + def filterParser[T](a: Parser[T], f: T => Boolean, seen: String, msg: String => String): Parser[T] = + a.ifValid { a.result match { - case Some(av) => if( f(av) ) success( av ) else Invalid - case None => new Filter(a, f) + case Some(av) => if( f(av) ) success( av ) else Parser.failure(msg(seen)) + case None => new Filter(a, f, seen, msg) } } - else Invalid def seqParser[A,B](a: Parser[A], b: Parser[B]): Parser[(A,B)] = - if(a.valid && b.valid) + a.ifValid { b.ifValid { (a.result, b.result) match { case (Some(av), Some(bv)) => success( (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 choiceParser[A,B](a: Parser[A], b: Parser[B]): Parser[Either[A,B]] = if(a.valid) @@ -112,6 +171,9 @@ object Parser extends ParserMain def opt[T](a: Parser[T]): Parser[Option[T]] = if(a.valid) new Optional(a) else success(None) + def onFailure[T](delegate: Parser[T], msg: String): Parser[T] = + if(delegate.valid) new OnFailure(delegate, msg) else failure(msg) + 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) @@ -123,34 +185,35 @@ object Parser extends ParserMain assume(max >= min, "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")") def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] = - 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) - } - else if(min == 0) - invalidButOptional - else - Invalid + repeated match + { + case i: Invalid if min == 0 => invalidButOptional + case i: Invalid => i + case _ => + 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) + } + } partial match { case Some(part) => - if(part.valid) + part.ifValid { part.result match { case Some(value) => repeat(None, repeated, min, max, value :: revAcc) case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse)) } - else Invalid + } case None => checkRepeated(success(Nil)) } } 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 and[T](a: Parser[T], b: Parser[_]): Parser[T] = a.ifValid( b.ifValid( new And(a, b) )) } trait ParserMain { @@ -169,40 +232,43 @@ trait ParserMain 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 !!!(msg: String): Parser[A] = onFailure(a, msg) 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], check: Boolean = false): Parser[A] = Parser.examples(a, s, check) - def filter(f: A => Boolean): Parser[A] = filterParser(a, f) + def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg) 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 invalid(msgs: => Seq[String]): Parser[Nothing] = Invalid(mkFailures(msgs)) + def failure(msg: => String): Parser[Nothing] = invalid(msg :: Nil) def success[T](value: T): Parser[T] = new ValidParser[T] { override def result = Some(value) - def resultEmpty = result - def derive(c: Char) = Invalid + def resultEmpty = Value(value) + def derive(c: Char) = Parser.failure("Expected end of input.") def completions = Completions.empty override def toString = "success(" + value + ")" } implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = - new CharacterClass(r contains _).examples(r.map(_.toString) : _*) + charClass(r contains _).examples(r.map(_.toString) : _*) def chars(legal: String): Parser[Char] = { val set = legal.toSet - new CharacterClass(set) examples(set.map(_.toString)) + charClass(set, "character in '" + legal + "'") examples(set.map(_.toString)) } - def charClass(f: Char => Boolean): Parser[Char] = new CharacterClass(f) + def charClass(f: Char => Boolean, label: String = ""): Parser[Char] = new CharacterClass(f, label) 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 result = None + def resultEmpty = mkFailure( "Expected '" + ch + "'" ) + def derive(c: Char) = if(c == ch) success(ch) else new Invalid(resultEmpty) def completions = Completions.single(Completion.suggestStrict(ch.toString)) override def toString = "'" + ch + "'" } @@ -212,19 +278,22 @@ trait ParserMain } // intended to be temporary pending proper error feedback - def result[T](p: Parser[T], s: String): Either[(String,Int), T] = + def result[T](p: Parser[T], s: String): Either[(Seq[String],Int), T] = { - def loop(i: Int, a: Parser[T]): Either[(String,Int), T] = - if(a.valid) + def loop(i: Int, a: Parser[T]): Either[(Seq[String],Int), T] = + a match { - val ci = i+1 - if(ci >= s.length) - a.resultEmpty.toRight(("Unexpected end of input", ci)) - else - loop(ci, a derive s(ci) ) + case Invalid(f) => Left( (f.errors, i) ) + case _ => + val ci = i+1 + if(ci >= s.length) + a.resultEmpty.toEither.left.map { msgs => + val nonEmpty = if(msgs.isEmpty) "Unexpected end of input" :: Nil else msgs + (nonEmpty, ci) + } + else + loop(ci, a derive s(ci) ) } - else - Left(("Parse error",i)) loop(-1, p) } @@ -250,12 +319,15 @@ trait ParserMain else a def matched(t: Parser[_], seen: Vector[Char] = Vector.empty, partial: Boolean = false): Parser[String] = - if(!t.valid) - if(partial && !seen.isEmpty) success(seen.mkString) else Invalid - else if(t.result.isEmpty) - new MatchedString(t, seen, partial) - else - success(seen.mkString) + t match + { + case i: Invalid => if(partial && !seen.isEmpty) success(seen.mkString) else i + case _ => + if(t.result.isEmpty) + new MatchedString(t, seen, partial) + else + success(seen.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) @@ -273,10 +345,12 @@ trait ParserMain def not(p: Parser[_]): Parser[Unit] = new Not(p) - def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = + def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = seq0(p, Nil) + def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] = { - val valid = p.filter(_.valid) - if(valid.isEmpty) failure("") else new ParserSeq(valid) + val (newErrors, valid) = separate(p) { case Invalid(f) => Left(f.errors); case ok => Right(ok) } + def combinedErrors = errors ++ newErrors.flatten + if(valid.isEmpty) invalid(combinedErrors) else new ParserSeq(valid, combinedErrors) } def stringLiteral(s: String, start: Int): Parser[String] = @@ -288,27 +362,40 @@ trait ParserMain sealed trait ValidParser[T] extends Parser[T] { final def valid = true + final def failure = None + final def ifValid[S](p: => Parser[S]): Parser[S] = p } -private object Invalid extends Invalid("inv") -private sealed case class Invalid(val message: String) extends Parser[Nothing] +private final case class Invalid(fail: Failure) extends Parser[Nothing] { - def resultEmpty = None + def failure = Some(fail) + def result = None + def resultEmpty = fail def derive(c: Char) = error("Invalid.") - override def valid = false def completions = Completions.nil - override def toString = message + override def toString = fail.errors.mkString("; ") + def valid = false + def ifValid[S](p: => Parser[S]): Parser[S] = this +} +private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A] +{ + def result = a.result + def resultEmpty = a.resultEmpty match { case f: Failure => mkFailure(message); case v: Value[A] => v } + def derive(c: Char) = onFailure(a derive c, message) + def completions = a.completions + override def toString = "(" + a + " !!! \"" + message + "\" )" + override def isTokenStart = a.isTokenStart } 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) + lazy val result = tuple(a.result,b.result) + lazy val resultEmpty = a.resultEmpty seq b.resultEmpty def derive(c: Char) = { val common = a.derive(c) ~ b a.resultEmpty match { - case Some(av) => common | b.derive(c).map(br => (av,br)) - case None => common + case Value(av) => common | b.derive(c).map(br => (av,br)) + case _: Failure => common } } lazy val completions = a.completions x b.completions @@ -317,35 +404,47 @@ private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends ValidPars private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A] { + lazy val result = tuple(a.result, b.result) map (_._1) def derive(c: Char) = (a derive c) | (b derive c) - lazy val resultEmpty = a.resultEmpty orElse b.resultEmpty + lazy val resultEmpty = a.resultEmpty or 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 ValidParser[Either[A,B]] { + lazy val result = tuple(a.result, b.result) map { case (a,b) => Left(a) } def derive(c: Char) = (a derive c) || (b derive c) - lazy val resultEmpty = a.resultEmpty.map(left.fn) orElse b.resultEmpty.map(right.fn) + lazy val resultEmpty = a.resultEmpty either b.resultEmpty lazy val completions = a.completions ++ b.completions override def toString = "(" + a + " || " + b + ")" } -private final class ParserSeq[T](a: Seq[Parser[T]]) extends ValidParser[Seq[T]] +private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String]) extends ValidParser[Seq[T]] { assert(!a.isEmpty) - lazy val resultEmpty = { val rs = a.flatMap(_.resultEmpty); if(rs.isEmpty) None else Some(rs) } + lazy val resultEmpty: Result[Seq[T]] = + { + val res = a.map(_.resultEmpty) + val (failures, values) = separate(res)(_.toEither) + if(failures.isEmpty) Value(values) else mkFailures(failures.flatten ++ errors) + } + def result = { + val success = a.flatMap(_.result) + if(success.length == a.length) Some(success) else None + } lazy val completions = a.map(_.completions).reduceLeft(_ ++ _) - def derive(c: Char) = seq(a.map(_ derive c)) + def derive(c: Char) = seq0(a.map(_ derive c), errors) override def toString = "seq(" + a + ")" } 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 result = a.result flatMap { av => f(av).result } + lazy val resultEmpty = a.resultEmpty flatMap { av => f(av).resultEmpty } 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 + case _: Failure => Completions.strict(Set.empty + c) + case Value(av) => c x f(av).completions } } @@ -354,8 +453,8 @@ private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends Val val common = a derive c flatMap f a.resultEmpty match { - case Some(av) => common | derive1(f(av), c) - case None => common + case Value(av) => common | derive1(f(av), c) + case _: Failure => common } } override def isTokenStart = a.isTokenStart @@ -363,17 +462,20 @@ private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends Val } private final class MapParser[A,B](a: Parser[A], f: A => B) extends ValidParser[B] { + lazy val result = a.result map f lazy val resultEmpty = a.resultEmpty map f def derive(c: Char) = (a derive c) map f def completions = a.completions override def isTokenStart = a.isTokenStart override def toString = "map(" + a + ")" } -private final class Filter[T](p: Parser[T], f: T => Boolean) extends ValidParser[T] +private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String) 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 } + def filterResult(r: Result[T]) = p.resultEmpty.filter(f, msg(seen)) + lazy val result = p.result filter f + lazy val resultEmpty = filterResult(p.resultEmpty) + def derive(c: Char) = filterParser(p derive c, f, seen + c, msg) + lazy val completions = p.completions filterS { s => filterResult(apply(p)(s).resultEmpty).isValid } override def toString = "filter(" + p + ")" override def isTokenStart = p.isTokenStart } @@ -382,7 +484,8 @@ private final class MatchedString(delegate: Parser[_], seenV: Vector[Char], part lazy val seen = seenV.mkString def derive(c: Char) = matched(delegate derive c, seenV :+ c, partial) def completions = delegate.completions - def resultEmpty = if(delegate.resultEmpty.isDefined) Some(seen) else if(partial) Some(seen) else None + def result = if(delegate.result.isDefined) Some(seen) else None + def resultEmpty = delegate.resultEmpty match { case f: Failure if !partial => f; case _ => Value(seen) } override def isTokenStart = delegate.isTokenStart override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")" } @@ -398,30 +501,37 @@ private final class TokenStart[T](delegate: Parser[T], seen: String, track: Bool else Completions.single(Completion.displayStrict(seen)) + def result = delegate.result def resultEmpty = delegate.resultEmpty override def isTokenStart = true override def toString = "token('" + seen + "', " + track + ", " + delegate + ")" } private final class And[T](a: Parser[T], b: Parser[_]) extends ValidParser[T] { + lazy val result = tuple(a.result,b.result) map { _._1 } 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 + lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isValid ) + lazy val resultEmpty = a.resultEmpty && b.resultEmpty } 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(()) + def result = None + lazy val resultEmpty = delegate.resultEmpty match { + case f: Failure => Value(()) + case v: Value[_] => mkFailure("Excluded.") + } } 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 substring 1 }) + def result = delegate.result lazy val resultEmpty = delegate.resultEmpty lazy val completions = if(fixed.isEmpty) - if(resultEmpty.isEmpty) Completions.nil else Completions.empty + if(resultEmpty.isValid) Completions.nil else Completions.empty else Completions(fixed map(f => Completion.suggestion(f)) ) override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")" @@ -429,21 +539,25 @@ private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends private final class StringLiteral(str: String, start: Int) extends ValidParser[String] { assert(0 <= start && start < str.length) - def resultEmpty = None - def derive(c: Char) = if(str.charAt(start) == c) stringLiteral(str, start+1) else Invalid + def failMsg = "Expected '" + str + "'" + def resultEmpty = mkFailure(failMsg) + def result = None + def derive(c: Char) = if(str.charAt(start) == c) stringLiteral(str, start+1) else new Invalid(resultEmpty) lazy val completions = Completions.single(Completion.suggestion(str.substring(start))) override def toString = '"' + str + '"' } -private final class CharacterClass(f: Char => Boolean) extends ValidParser[Char] +private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char] { - def resultEmpty = None - def derive(c: Char) = if( f(c) ) success(c) else Invalid + def result = None + def resultEmpty = mkFailure("Expected " + label) + def derive(c: Char) = if( f(c) ) success(c) else Invalid(resultEmpty) def completions = Completions.empty - override def toString = "class()" + override def toString = "class(" + label + ")" } private final class Optional[T](delegate: Parser[T]) extends ValidParser[Option[T]] { - def resultEmpty = Some(None) + def result = delegate.result map some.fn + def resultEmpty = Value(None) def derive(c: Char) = (delegate derive c).map(some.fn) lazy val completions = Completion.empty +: delegate.completions override def toString = delegate.toString + "?" @@ -460,8 +574,8 @@ private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], m val partD = repeat(Some(part derive c), repeated, min, max, accumulatedReverse) part.resultEmpty match { - case Some(pv) => partD | repeatDerive(c, pv :: accumulatedReverse) - case None => partD + case Value(pv) => partD | repeatDerive(c, pv :: accumulatedReverse) + case _: Failure => partD } case None => repeatDerive(c, accumulatedReverse) } @@ -481,21 +595,21 @@ private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], m case None => fin } } - lazy val resultEmpty: Option[Seq[T]] = + def result = None + lazy val resultEmpty: Result[Seq[T]] = { val partialAccumulatedOption = partial match { - case None => Some(accumulatedReverse) + case None => Value(accumulatedReverse) case Some(partialPattern) => partialPattern.resultEmpty.map(_ :: accumulatedReverse) } - for(partialAccumulated <- partialAccumulatedOption; repeatEmpty <- repeatedParseEmpty) yield - partialAccumulated reverse_::: repeatEmpty + (partialAccumulatedOption app repeatedParseEmpty)(_ reverse_::: _) } - private def repeatedParseEmpty: Option[List[T]] = + private def repeatedParseEmpty: Result[List[T]] = { if(min == 0) - Some(Nil) + Value(Nil) else // forced determinism for(value <- repeated.resultEmpty) yield diff --git a/util/complete/Parsers.scala b/util/complete/Parsers.scala index 85c5a73fb..ceb441d09 100644 --- a/util/complete/Parsers.scala +++ b/util/complete/Parsers.scala @@ -14,12 +14,12 @@ 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) + lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet + lazy val Letter = charClass(_.isLetter, "letter") def IDStart = Letter - lazy val IDChar = charClass(isIDChar) + lazy val IDChar = charClass(isIDChar, "ID character") lazy val ID = IDStart ~ IDChar.* map { case x ~ xs => (x +: xs).mkString } - lazy val OpChar = charClass(isOpChar) + lazy val OpChar = charClass(isOpChar, "symbol") lazy val Op = OpChar.+.string lazy val OpOrID = ID | Op @@ -28,12 +28,15 @@ trait Parsers 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 NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character") + lazy val SpaceClass = charClass(_.isWhitespace, "whitespace character") lazy val NotSpace = NotSpaceClass.+.string lazy val Space = SpaceClass.+.examples(" ") lazy val OptSpace = SpaceClass.*.examples(" ") - lazy val URIClass = charClass(x => !x.isWhitespace && ')' != x).+.string + lazy val URIClass = URIChar.+.string !!! "Invalid URI" + + lazy val URIChar = charClass(alphanum) | chars("_-!.~'()*,;:$&+=?/[]@%") + def alphanum(c: Char) = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') // TODO: implement def fileParser(base: File): Parser[File] = token(mapOrFail(NotSpace)(s => new File(s.mkString)), "") @@ -63,6 +66,6 @@ object Parsers extends Parsers object DefaultParsers extends Parsers with ParserMain { def matches(p: Parser[_], s: String): Boolean = - apply(p)(s).resultEmpty.isDefined + apply(p)(s).resultEmpty.isValid def validID(s: String): Boolean = matches(ID, s) } \ No newline at end of file