package play.api.libs.iteratee
import scala.concurrent.Future
import play.api.libs.iteratee.Execution.Implicits.{ defaultExecutionContext => dec }
object Parsing {
sealed trait MatchInfo[A] {
def content: A
def isMatch = this match {
case Matched(_) => true
case Unmatched(_) => false
}
}
case class Matched[A](val content: A) extends MatchInfo[A]
case class Unmatched[A](val content: A) extends MatchInfo[A]
def search(needle: Array[Byte]): Enumeratee[Array[Byte], MatchInfo[Array[Byte]]] = new Enumeratee[Array[Byte], MatchInfo[Array[Byte]]] {
val needleSize = needle.size
val fullJump = needleSize
val jumpBadCharecter: (Byte => Int) = {
val map = Map(needle.dropRight(1).reverse.zipWithIndex.reverse: _*)
byte => map.get(byte).map(_ + 1).getOrElse(fullJump)
}
def applyOn[A](inner: Iteratee[MatchInfo[Array[Byte]], A]): Iteratee[Array[Byte], Iteratee[MatchInfo[Array[Byte]], A]] = {
Iteratee.flatten(inner.fold1((a, e) => Future.successful(Done(Done(a, e), Input.Empty: Input[Array[Byte]])),
k => Future.successful(Cont(step(Array[Byte](), Cont(k)))),
(err, r) => throw new Exception())(dec))
}
def scan(previousMatches: List[MatchInfo[Array[Byte]]], piece: Array[Byte], startScan: Int): (List[MatchInfo[Array[Byte]]], Array[Byte]) = {
if (piece.length < needleSize) {
(previousMatches, piece)
} else {
val fullMatch = Range(needleSize - 1, -1, -1).forall(scan => needle(scan) == piece(scan + startScan))
if (fullMatch) {
val (prefix, suffix) = piece.splitAt(startScan)
val (matched, left) = suffix.splitAt(needleSize)
val newResults = previousMatches ++ List(Unmatched(prefix), Matched(matched)) filter (!_.content.isEmpty)
if (left.length < needleSize) (newResults, left) else scan(newResults, left, 0)
} else {
val jump = jumpBadCharecter(piece(startScan + needleSize - 1))
val isFullJump = jump == fullJump
val newScan = startScan + jump
if (newScan + needleSize > piece.length) {
val (prefix, suffix) = (piece.splitAt(startScan))
(previousMatches ++ List(Unmatched(prefix)), suffix)
} else scan(previousMatches, piece, newScan)
}
}
}
def step[A](rest: Array[Byte], inner: Iteratee[MatchInfo[Array[Byte]], A])(in: Input[Array[Byte]]): Iteratee[Array[Byte], Iteratee[MatchInfo[Array[Byte]], A]] = {
in match {
case Input.Empty => Cont(step(rest, inner))
case Input.EOF => Done(inner, Input.El(rest))
case Input.El(chunk) =>
val all = rest ++ chunk
def inputOrEmpty(a: Array[Byte]) = if (a.isEmpty) Input.Empty else Input.El(a)
Iteratee.flatten(inner.fold1((a, e) => Future.successful(Done(Done(a, e), inputOrEmpty(rest))),
k => {
val (result, suffix) = scan(Nil, all, 0)
val fed = result.filter(!_.content.isEmpty).foldLeft(Future.successful(Array[Byte]() -> Cont(k))) { (p, m) =>
p.flatMap(i => i._2.fold1((a, e) => Future.successful((i._1 ++ m.content, Done(a, e))),
k => Future.successful((i._1, k(Input.El(m)))),
(err, e) => throw new Exception())(dec)
)(dec)
}
fed.flatMap {
case (ss, i) => i.fold1((a, e) => Future.successful(Done(Done(a, e), inputOrEmpty(ss ++ suffix))),
k => Future.successful(Cont[Array[Byte], Iteratee[MatchInfo[Array[Byte]], A]]((in: Input[Array[Byte]]) => in match {
case Input.EOF => Done(k(Input.El(Unmatched(suffix))), Input.EOF)
case other => step(ss ++ suffix, Cont(k))(other)
})),
(err, e) => throw new Exception())(dec)
}(dec)
},
(err, e) => throw new Exception())(dec)
)
}
}
}
}