Skip to content

Commit

Permalink
Fix tpolecat#65: assure incomplete input doesn't pass compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmulder committed Mar 20, 2018
1 parent 9c2d314 commit 6d31dc4
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 4 deletions.
13 changes: 9 additions & 4 deletions modules/core/src/main/scala/tut/Tut.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ object Tut {

def file(in: File): Tut[Unit] = for {
lines <- FileIO.lines(in).liftIO[Tut]
_ <- lines.zipWithIndex.traverse { case (l, num) => line(l, num + 1) }
_ <- lines.zipWithIndexAndNext(false)(nextCloses)
.traverse { case (l, nextCloses, num) => line(l, num + 1, nextCloses) }
} yield ()

// Private, utility methods

private def line(text: String, lineNumber: Int): Tut[Unit] =
private val nextCloses: String => Boolean =
_.trim.startsWith("```")

private def line(text: String, lineNumber: Int, nextCloses: Boolean): Tut[Unit] =
for {
s <- Tut.state
inv = s.mods.filter(m => m == Invisible || m == Passthrough || m.isInstanceOf[Decorate])
Expand All @@ -35,11 +39,11 @@ object Tut {
s
}
}.liftIO[Tut]
_ <- s.isCode.fold(interp(text, lineNumber), out(fixShed(text, mods ++ inv)))
_ <- s.isCode.fold(interp(text, lineNumber, nextCloses), out(fixShed(text, mods ++ inv)))
_ <- checkBoundary(text, "```tut", true, mods)
} yield ()

private def interp(text: String, lineNum: Int): Tut[Unit] =
private def interp(text: String, lineNum: Int, nextCloses: Boolean): Tut[Unit] =
Tut.state >>= { s =>
(text.trim.nonEmpty || s.partial.nonEmpty || s.mods(Silent)).whenM[Tut,Unit] {
for {
Expand All @@ -50,6 +54,7 @@ object Tut {
r <- // in 2.13 it's an error to interpret an empty line of text, evidently
if (text.trim.isEmpty && s.partial.isEmpty) success
else IO(s.imain.interpret(s.partial + "\n" + text)).liftIO[Tut] >>= {
case Results.Incomplete if nextCloses => error(lineNum, Some("incomplete input in code block, missing brace or paren?"))
case Results.Incomplete => incomplete(text)
case Results.Success => if (s.mods(Fail)) error(lineNum, Some("failure was asserted but no failure occurred")) else success
case Results.Error => if (s.mods(NoFail) || s.mods(Fail)) success else error(lineNum)
Expand Down
14 changes: 14 additions & 0 deletions modules/core/src/main/scala/tut/felix/Syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tut.felix

import java.io.OutputStream

import scala.annotation.tailrec

trait Syntax {

implicit class MonadOps[M[_], A](ma: M[A])(implicit M: Monad[M]) {
Expand Down Expand Up @@ -35,6 +37,18 @@ trait Syntax {
implicit class ListOps[A](as: List[A]) {
def traverse[M[_]: Monad, B](f: A => M[B]): M[List[B]] =
as.foldRight(List.empty[B].point[M])((a, mlb) => f(a).flatMap(b => mlb.map(b :: _)))

def zipWithIndexAndNext[B](z: => B)(f: A => B): List[(A, B, Int)] = {
@tailrec
def fold(xs: List[(A, Int)], acc: Vector[(A, B, Int)]): Vector[(A, B, Int)] =
xs match {
case (x, i) :: (xs @ ((y, _) :: _)) => fold(xs, acc :+ ((x, f(y), i)))
case (x, i) :: Nil => acc :+ ((x, z, i))
case Nil => Vector.empty
}

fold(as.zipWithIndex, Vector.empty).toList
}
}

implicit class BooleanOps(b: Boolean) {
Expand Down

0 comments on commit 6d31dc4

Please sign in to comment.