Commits

pchiusano  committed 132de5f

redid commit handling

  • Participants
  • Parent commits 073c2c3
  • Branches error-reporting

Comments (0)

Files changed (5)

File src/main/scala/nomo/Configurations.scala

 import Accumulators.Position
 
 object Configurations {
-  def stringParsers[U]: Parsers[String,Char,Errors.TreeError,Position,U] = 
+  def stringParsers[U]: Parsers[String,Char,Errors.TreeMsg,Position,U] = 
     Parsers(Monotypic.String, Errors.tree[Char], Accumulators.position[U](5))
 }

File src/main/scala/nomo/Errors.scala

   //    sys.error("todo")
   //}
 
-  trait TreeError { 
-    def ++(pe: TreeError) = (this,pe) match {
+  trait TreeMsg { 
+    def ++(pe: TreeMsg) = (this,pe) match {
       case (Multiple(a), Multiple(b)) => Multiple(a ++ b)
       case (Multiple(a), b) => Multiple(a :+ b)
       case (a, Multiple(b)) => Multiple(a +: b)
       case (a,a2) => Multiple(IndexedSeq(a,a2))
     }
-    def &(pe: TreeError) = Associate(this, pe)
+    def &(pe: TreeMsg) = Associate(this, pe)
     def furthest: Int = this match {
       case Single(e,pos) => pos.map(_.charactersRead).getOrElse(0)
       case Multiple(es) => es.last.furthest
       case Nest(parent,child) => parent.furthest max child.furthest
       case Associate(left, right) => left.furthest max right.furthest
     }
-    def toSeq: IndexedSeq[TreeError] = this match {
+    def toSeq: IndexedSeq[TreeMsg] = this match {
       case Multiple(es) => es
       case _ => IndexedSeq(this)
     }
-    def nest(p: TreeError): TreeError = this match {
+    def nest(p: TreeMsg): TreeMsg = this match {
       case s@Single(_,_) => Nest(s,p)
       case Nest(parent,child) => Nest(parent,child nest p)
       case Multiple(es) => Multiple(es map (_ nest p))
       case Associate(left, right) => Associate(left nest p, right nest p) 
     }
-    def invert: TreeError = this match {
+    def invert: TreeMsg = this match {
       case Nest(parent,child) => child.invert nest parent
       case Multiple(es) => Multiple(es.map(_.invert))
       case Associate(l,r) => Associate(l.invert, r.invert)
       case Associate(left, right) => left.prettyprint(level, format) + "\n" + right.prettyprint(level, format)
     }
     override def toString = 
-      this.prettyprint(0, (msg,pos) =>  "%s.%s  %s".format(
+      this.trifectaStyle.prettyprint(0, (msg,pos) =>  "%s.%s  %s".format(
         pos.line+1, // lines and columns are 1-based
         pos.column+1,
         msg)) 
 
     def show(ctx: String) = 
       "errors: <line>.<column>  <description>  '<context>'\n" + 
-      this.prettyprint(0, (msg,pos) => 
+      this.trifectaStyle.prettyprint(0, (msg,pos) => 
         "%s.%s  %s  %s".format(
           pos.line+1, // lines and columns are 1-based
           pos.column+1,
                       takeWhile(c => c != '\n' && c != '\r').trim + "'"
           else "<no context>"
           )) 
+     def collapse: TreeMsg = 
+       this match {
+         case Multiple(es) => es.tail.foldLeft(
+           (Some(es.head): Option[TreeMsg], IndexedSeq[TreeMsg]()))(
+           (p,e) => (p._1,e) match { 
+             case (Some(Single(m1,p1)), Single(m2,p2)) if p1 == p2 => 
+               (Some(Single(m1 + "; " + m2, p1)), p._2)
+             case (None, a@Single(m2,p2)) => 
+               (Some(a),p._2)
+             case _ => (Some(e), p._1 map (p._2 :+ _) getOrElse p._2)
+           }) match { case (e,es) => Multiple(e map (es :+ _) getOrElse es) }
+         case _ => this 
+       }
+     def unnest: TreeMsg = this match {
+       case Nest(a,b) => Multiple(IndexedSeq(a)) ++ b.unnest
+       case _ => this
+     }
+     // collapses msgs at the same location and un-nests into a list of
+     // markers followed by the 
+     def trifectaStyle: TreeMsg =
+       unnest.collapse match {
+         case Multiple(es) if es.size > 1 => Multiple(IndexedSeq( 
+           Nest(Single("markers:", None), Multiple(es.init)),
+           Nest(Single("error:", None), es.last)
+         ))
+         case c => c
+       }
  
   }
-  case class Multiple(errors: IndexedSeq[TreeError]) extends TreeError 
-  case class Single(msg: String, position: Option[Position]) extends TreeError
-  case class Nest(parent: TreeError, child: TreeError) extends TreeError
-  case class Associate(left: TreeError, right: TreeError) extends TreeError
+  case class Multiple(errors: IndexedSeq[TreeMsg]) extends TreeMsg 
+  case class Single(msg: String, position: Option[Position]) extends TreeMsg
+  case class Nest(parent: TreeMsg, child: TreeMsg) extends TreeMsg
+  case class Associate(left: TreeMsg, right: TreeMsg) extends TreeMsg
   
-  def msg(s: String): Position => TreeError = pos => Single(s, Some(pos))
+  implicit def msg(s: String): Position => TreeMsg = pos => Single(s, Some(pos))
 
-  def tree[I] = new Errors[I,Position,TreeError] {
-    def choice(e1: TreeError, e2: TreeError) = {
+  def tree[I] = new Errors[I,Position,TreeMsg] {
+    def choice(e1: TreeMsg, e2: TreeMsg) = {
       //println("choosing between: ")
       //println(e1)
       //println(e2)
       if (e2.furthest >= e1.furthest) e2 
       else e1
     }
-    def sequence(e1: TreeError, e2: TreeError) = e1 ++ e2
-    def nest(parent: TreeError, child: TreeError) = Nest(parent, child)
+    def sequence(e1: TreeMsg, e2: TreeMsg) = e1 ++ e2
+    def nest(parent: TreeMsg, child: TreeMsg) = Nest(parent, child)
     def single(i: I, pos: Position) = Single("Expected '%s'".format(i), Some(pos))
     def unexpectedEOF(pos: Position) = Single("Unexpected EOF", Some(pos))
     def expectedEOF(pos: Position) = Single("Expected EOF", Some(pos))

File src/main/scala/nomo/Main.scala

     import P._
     import Errors.msg
     import Accumulators.Position
-    implicit def toTreeError(msg: String): Errors.TreeError = Errors.Single(msg, None)
     println {
       unit(42)
     }

File src/main/scala/nomo/Parser.scala

 import util.Trampoline._
 
 /** Indicates success, failure, or error. */
-sealed trait Status[+E,+A] { 
-  def right: Option[A] = this match { 
-    case Success(a) => Some(a)
-    case _ => None
-  }
-  def left: Option[E] = this match {
-    case Failure(e) => Some(e)
-    case Error(e) => Some(e)
-    case _ => None
-  }
+case class Status[+E,+A](committed: Boolean, status: Either[E,A]) {
+  def right: Option[A] = status.right.toOption 
+  def left: Option[E] = status.left.toOption 
   def get: A = right.get
-  def map[B](f: A => B): Status[E,B] = this match {
-    case Success(a) => Success(f(a))
-    case e@Failure(_) => e
-    case e@Error(_) => e
-  }
-  def mapL[E2,A2>:A](f: E => E2): Status[E2,A2] = this match {
-    case Failure(e) => Failure(f(e))
-    case Error(e) => Error(f(e))
-    case a@Success(_) => a
-  }
-  def flatMap[E2>:E,B](f: A => Status[E2,B]): Status[E2,B] = this match {
-    case Success(a) => f(a)
-    case e@Failure(_) => e
-    case e@Error(_) => e
-  }
-  def flatMapL[E2,A2>:A](f: E => Status[E2,A2]): Status[E2,A2] = this match {
-    case Failure(e) => f(e)
-    case Error(e) => f(e)
-    case a@Success(_) => a
-  }
+  def map[B](f: A => B): Status[E,B] = 
+    Status(committed, status.right map f)
+  def mapL[E2,A2>:A](f: E => E2): Status[E2,A2] =
+    Status(committed, status.left map f)
+  def uncommit = Status(false, status)
+  def commit = Status(true, status)
+  def flatMap[E2>:E,B](f: A => Status[E2,B]): Status[E2,B] = 
+    status match {
+      case Right(a) => 
+        val Status(c2, s2) = f(a)
+        Status(committed || c2, s2)
+      case _ => this.asInstanceOf[Status[E2,B]]
+    }
+  def flatMapL[E2,A2>:A](f: E => Status[E2,A2]): Status[E2,A2] =
+    status match {
+      case Left(e) => 
+        val Status(c2, s2) = f(e)
+        Status(committed || c2, s2)
+      case _ => this.asInstanceOf[Status[E2,A2]]
+    }
 }
-case class Success[A](a: A) extends Status[Nothing,A]
-case class Failure[E](e: E) extends Status[E,Nothing]
-case class Error[E](e: E) extends Status[E,Nothing]
+object Failure { def apply[E](e: E) = Status(false, Left(e)) }
+object Success { def apply[A](a: A) = Status(false, Right(a)) }
 
 /** The result of a parse. Contains the status (either a failure,
   * an error, or the successful result), the position, and the user state. 
     def feed(i: Input[F,I], a: Accumulator[I,X,U]): Trampoline[(Parser[A], Accumulator[I,X,U])]
 
     /** Transforms the result value of this `Parser` using the given function. */
-    def mapResult[B](f: Result[X,E,U,A] => Status[E,B]): Parser[B] = 
-      flatMapResult(f andThen (_ map (b => unit(b))))
+    def mapResult[B](g: Result[X,E,U,A] => Status[E,B]): Parser[B] = {
+      def go(p: Parser[A]): Parser[B] = Cont((i,a) => 
+        // note - this code is carefully written to avoid SOE 
+        more(p.feed(i,a)) flatMap { case (p2,a2) => p2 match {
+          case Done(t,rem,ann) => t flatMap (s => 
+            suspendS { (Done(g(Result(s, ann.get, ann.getUser)),rem,ann), a2) })
+          case _ => suspendS { (go(p2), a2) }
+        }})
+      go(this)
+    }
+
+    /** Transforms only the status portion of this `Parser` result. */ 
+    def mapStatus[B](f: Status[E,A] => Status[E,B]): Parser[B] = 
+      mapResult(r => f(r.status))
 
     def mapErr[E2](f: E => E): Parser[A] = 
       mapResult(r => r.status.mapL(f))
       def go(p: Parser[A]): Parser[B] = Cont((i,a) => 
         // note - this code is carefully written to avoid SOE 
         more(p.feed(i,a)) flatMap { case (p2,a2) => p2 match {
-          case Done(t,rem,ann) => t flatMap (s => g(Result(s, ann.get, ann.getUser)) match {
-            case Success(pb) => pb.feedAll(rem,ann)
-            case Failure(e) => suspendS { (Done(Failure(e),rem,ann), a2) }
-            case Error(e) => suspendS { (Done(Error(e),rem,ann), a2) }
-          })
+          case Done(t,rem,ann) => t flatMap (s => 
+            g(Result(s, ann.get, ann.getUser)) match {
+              case Status(c,Right(pb)) => 
+                (if (c) (pb.commit) else pb).feedAll(rem,ann)
+              case s => suspendS { 
+                (Done(s.asInstanceOf[Status[E,B]],rem,ann), a2) } 
+            })
           case _ => suspendS { (go(p2), a2) }
         }})
       go(this)
           r._1 match { 
             case Done(t,rem,ann) => t flatMap {
               // committed failure on the left kills the right branch 
-              case Error(_) => suspendS (r)
+              case Status(true, Left(_)) => suspendS (r) 
               // success on the left also kills the other branch
-              case Success(_) => suspendS (r)
+              case Status(_, Right(_)) => suspendS (r) 
               // uncomitted failure on left switches to right branch
-              case Failure(e) => p2.mapErr(err.choice(e,_)).feedAll(inputs :+ i, acc getOrElse a)
+              case Status(false, Left(e)) => 
+                p2.mapErr(err.choice(e,_)).feedAll(inputs :+ i, acc getOrElse a)
             }
             // otherwise keep both branches alive 
             case p12 => 
       */
     def scope(f: X => E): Parser[A] =
       position.flatMap(x => this.mapResult(r => r.status.mapL(e => err.nest(f(x), e))))
+    // might want a scope(f: (X,X,F) => E)
 
     /** Establishes a parent scope for this `Parser` - useful for creating hierarchical errors
       * during and after parsing has occurred. In the case of failure the result is the same
       * as `scope(X => E)`. In the case of success, the result value, `A`, is given a function
       * `E => E` to construct hierarchical errors at a later time. */
     def scope[B](f: X => E, g: (E => E, A) => B): Parser[B] =
-      position.flatMap(x => this.mapResult(r => r.status match {
-        case Failure(e) => Failure(err.nest(f(x), e))
-        case Error(e) => Error(err.nest(f(x), e))
-        case Success(a) => Success(g(err.nest(f(x),_), a))
-      }))
+      position.flatMap(x => this.mapStatus(
+        _.mapL(e => err.nest(f(x),e)).
+          map(a => g(err.nest(f(x),_), a))
+      ))
 
     /** Annotate the result of this parser with the current position. */
     def positional[B>:A](f: (X,A) => B): Parser[B] =
     def delimit1(sep: Parser[_]): Parser[List[A]] =
       (this map2 (sep >> this).many)(_ :: _)
 
-    /** A Parser in which a failure, e, becomes a successful parse of Failure(e).
-      * In the case of failure, onFail is run after the failure point purely
+    /** A Parser in which a failure, `e`, becomes a successful parse of `Failure(e)`.
+      * In the case of failure, `onFail` is run after the failure point purely
       * to consume input up until some recovery point.
       */
     def recover[C](onFail: Parser[_]): Parser[Status[E,A]] =
       this.flatMapResult(r => r.status match {
-        case Success(a) => Success(unit(Success(a)))
+        case Status(_,Right(a)) => Success(unit(Success(a)))
         case e => Success(onFail >> unit(e))
       })
 
   def binaryR[A](p: Parser[A])(f: Parser[(A,A) => A]): Parser[A] = p.binaryR(f)
 
   /** Try running this parser, backtracking to the starting position on failure. */
-  def attempt[A](p: Parser[A]): Parser[A] = block(p) 
+  def attempt[A](p: Parser[A]): Parser[A] = p.mapStatus(_.uncommit) 
 
   /** Parses the single token given. */
   def single(c: I): Parser[I] = any mapResult (s =>
   /** Parser which consumes no input and fails with the error generated from the current position. */
   def fail[A](f: X => E): Parser[A] = position.mapResult(r => r.status.flatMap(x => Failure(f(x))))
   
-  def block[A](p: Parser[A], handle: (X,E) => Option[E] = (a,e) => None): Parser[A] = p mapResult { r => 
-    r.status match {
-      case Error(e) => handle(r.position, e).
-                       map(e2 => Error(e2)).
-                       getOrElse (Failure(e)) 
-      case _ => r.status
-    }
-  }
-
-  def commit[A](p: Parser[A]): Parser[A] = position.flatMap(pos => 
-    p mapResult { r => 
-      if (r.position == pos) r.status
-      else r.status match {
-        case Failure(e) => Error(e)
-        case r => r
-      }
-    }
-  )
+  def commit[A](p: Parser[A]): Parser[A] = p mapStatus (_.commit)
 
   case class Cont[A](
       f: (Input[F,I], Accumulator[I,X,U]) => Trampoline[(Parser[A], Accumulator[I,X,U])]) extends Parser[A] {

File src/test/scala/nomo/ParserSpec.scala

   import Accumulators.Position
   val P = Parsers(Monotypic.String, Errors.tree[Char], Accumulators.position[Unit](4))
   import P._
-  implicit def toTreeError(msg: String): Errors.TreeError = Errors.Single(msg, None)
 
   property("unit") = forAll((s: String, k: Int) => 
     P.unit(k)(s).get == k)
 
   property("fail") = forAll((s: String) => {
     val p = P.word(s) ++ P.fail("oh noes!")
-    p(s).failure == Errors.Single("oh noes!", None)
+    p(s).isFailure 
   })
 
   property("optional") = forAll((s: String) => {