Commits

pchiusano committed d14eb2a

added takeUntil combinator, which could use more testing

  • Participants
  • Parent commits 673ca44
  • Branches error-reporting

Comments (0)

Files changed (3)

File src/main/scala/nomo/Monotypic.scala

   def toSeq(f: F): Seq[A] 
   def concat(l: F, r: F): F
   def slice(c: F, from: Int, to: Int): F
+  def indexOf(text: F, s: F): Option[Int]
 }
 
 case class MonotypicW[F,A](get: F, module: Monotypic[F,A]) {
     }
     return (this, MonotypicW(module(List()), module), s2)
   }
-
+  def indexOf(s: F): Option[Int] = module.indexOf(get, s)
   def toSeq = module.toSeq(get)
   def isEmpty: Boolean = module.isEmpty(get)
   def ++(r: MonotypicW[F,A]): MonotypicW[F,A] = MonotypicW(module.concat(get, r.get), module)
     def isEmpty(l: Seq[A]) = l.isEmpty
     def apply(l: Seq[A]) = l
     def toSeq(l: Seq[A]) = l
+    def indexOf(text: Seq[A], s: Seq[A]): Option[Int] = text.indexOfSlice(s) match {
+      case i if i < 0 => None
+      case i => Some(i)
+    }
     def concat(l: Seq[A], r: Seq[A]) = l ++ r
   }
   def String = new Monotypic[String,Char] {
     def apply(l: Seq[Char]) = l.mkString
     def toSeq(s: String): Seq[Char] = s.toIndexedSeq
     def concat(l: String, r: String) = l + r
+    def indexOf(s1: String, s2: String) = s1.indexOf(s2) match {
+      case i if i < 0 => None
+      case i => Some(i)
+    }
     def slice(s: String, from: Int, to: Int) = s.substring(from, to)
     def size(s: String) = s.length
   }

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

     go(MonotypicW(container(List()), container), s0)
   }
 
+  /** Reads until the given string is a prefix of the remaining input, or until EOF.
+    * This parser does not consume the given string. */
+  def takeUntil[S](f: F): Parser[MonotypicW[F,I]] = {
+    val s = MonotypicW(f,container)
+    def go(acc: MonotypicW[F,I], lookback: MonotypicW[F,I]): Parser[MonotypicW[F,I]] = 
+    Cont((i,a) => 
+      i match {
+        case EOF => { 
+          val ai = a(lookback)(i)
+          suspendS { (Done(Success(acc++lookback), IndexedSeq(EOF), ai), ai) }
+        }
+        case ChunkInput(chunk) => {
+          val text = lookback ++ chunk 
+          lazy val notFound = chunk.slice(0, chunk.size - s.size)
+          lazy val possible = chunk.slice(chunk.size - s.size)
+          text.indexOf(f) match {
+            case None => suspendS { (go(acc ++ notFound, possible), a(notFound)) }
+            case Some(i) => suspendS { 
+              val (h,t) = (text.slice(0,i), text.slice(i))
+              val ah = a(h)
+              (Done(
+                Success(acc ++ h), 
+                IndexedSeq(ChunkInput(t)),
+                ah 
+              ), ah)
+            }
+          }
+        }
+      }
+    )
+    val empty = MonotypicW(container(List()), container)
+    go(empty, empty)
+  }
+
   /** Parser which consumes no input but returns the current accumulated position. */
   def position: Parser[X] = Cont((i,a) => suspendS { (Done(Success(a.get), IndexedSeq(i), a), a) })
 

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

     P.word(s)(s).get.get == s &&
     ((!s.isEmpty) ==> (P.word("oog" + s)(s).isFailure))})
 
+  property("takeUntil") = secure {
+    val s = "ooga booga end"
+    //P.takeUntil("end")(s).get.get == "ooga booga " &&
+    //P.takeUntil("end")(s).position == P.takeWhile(_ != 'e')(s).position &&
+    P.takeUntil("@#$%")(s).position ?= P.takeWhile(_ => true)(s).position //&&
+    //true
+    //P.takeUntil("end")((0 to 10000).mkString + "end ").isSuccess
+  }
+
   property("map") = forAll((s: String) => 
     P.word(s).map(_.get.reverse)(s).get == s.reverse)