All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mdoc.internal.markdown.Instrumenter.scala Maven / Gradle / Ivy

There is a newer version: 2.2.24
Show newest version
package mdoc.internal.markdown

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import scala.meta._
import scala.meta.inputs.Position
import Instrumenter.position
import mdoc.internal.markdown.Instrumenter.Binders
import scala.meta.Mod.Lazy

class Instrumenter(sections: List[SectionInput]) {
  def instrument(): String = {
    printAsScript()
    out.toString
  }
  private val out = new ByteArrayOutputStream()
  private val sb = new PrintStream(out)
  val gensym = new Gensym()
  private var nestCount = 0
  private def nest(): Unit = {
    nestCount += 1
    sb.append(s"_root_.scala.Predef.locally {\n")
  }
  private def unnest(): Unit = {
    1.to(nestCount).foreach { _ =>
      sb.print("};")
    }
    nestCount = 0
  }
  private def printAsScript(): Unit = {
    sections.zipWithIndex.foreach {
      case (section, i) =>
        if (section.mod.isReset) {
          unnest()
          sb.print(Instrumenter.reset(section.mod, gensym.fresh("App")))
        } else if (section.mod.isNest) {
          nest()
        }
        sb.println("\n$doc.startSection();")
        if (section.mod.isFail) {
          sb.println(s"$$doc.startStatement(${position(section.source.pos)});")
          val out = new FailInstrumenter(sections, i).instrument()
          val literal = Instrumenter.stringLiteral(out)
          val binder = gensym.fresh("res")
          sb.append("val ")
            .append(binder)
            .append(" = _root_.mdoc.internal.document.FailSection(")
            .append(literal)
            .append(", ")
            .append(position(section.source.pos))
            .append(");")
          printBinder(binder, section.source.pos)
          sb.println("\n$doc.endStatement();")
        } else if (section.mod.isCompileOnly) {
          section.source.stats.foreach { stat =>
            sb.println(s"$$doc.startStatement(${position(stat.pos)});")
            sb.println("\n$doc.endStatement();")
          }
          sb.println(s"""object ${gensym.fresh("compile")} {""")
          sb.println(section.source.pos.text)
          sb.println("\n}")
        } else {
          section.source.stats.foreach { stat =>
            sb.println(s"$$doc.startStatement(${position(stat.pos)});")
            printStatement(stat, section.mod, sb)
            sb.println("\n$doc.endStatement();")
          }
        }
        sb.println("$doc.endSection();")
    }
    unnest()
  }

  private def printBinder(name: String, pos: Position): Unit = {
    sb.print(s"; $$doc.binder($name, ${position(pos)})")
  }
  private def printStatement(stat: Stat, m: Modifier, sb: PrintStream): Unit = {
    if (m.isCrash) {
      sb.append("$doc.crash(")
        .append(position(stat.pos))
        .append(") {\n")
        .append(stat.pos.text)
        .append("\n}")
    } else {
      val binders = stat match {
        case Binders(names) =>
          names.map(name => name -> name.pos)
        case _ =>
          val fresh = gensym.fresh("res")
          sb.print(s"val $fresh = ")
          List(Name(fresh) -> stat.pos)
      }
      sb.print(stat.pos.text)
      binders.foreach {
        case (name, pos) =>
          printBinder(name.syntax, pos)
      }
    }
  }
}
object Instrumenter {
  def reset(mod: Modifier, identifier: String): String = {
    val ctor =
      if (mod.isResetClass) s"new $identifier()"
      else identifier
    val keyword =
      if (mod.isResetClass) "class"
      else "object"
    s"$ctor\n}\n$keyword $identifier {\n"
  }
  def instrument(sections: List[SectionInput]): String = {
    val body = new Instrumenter(sections).instrument()
    wrapBody(body)
  }

  def position(pos: Position): String = {
    s"${pos.startLine}, ${pos.startColumn}, ${pos.endLine}, ${pos.endColumn}"
  }

  def stringLiteral(string: String): String = {
    import scala.meta.internal.prettyprinters._
    enquote(string, DoubleQuotes)
  }

  def wrapBody(body: String): String = {
    val wrapped = new StringBuilder()
      .append("package repl\n")
      .append("object Session extends _root_.mdoc.internal.document.DocumentBuilder {\n")
      .append("  def app(): _root_.scala.Unit = {val _ = new App()}\n")
      .append("  class App {\n")
      .append(body)
      .append("  }\n")
      .append("}\n")
      .toString()
    wrapped
  }
  object Binders {
    def binders(pat: Pat): List[Name] =
      pat.collect { case m: Member => m.name }
    def unapply(tree: Tree): Option[List[Name]] = tree match {
      case Defn.Val(mods, _, _, _) if mods.exists(_.isInstanceOf[Lazy]) => Some(Nil)
      case Defn.Val(_, pats, _, _) => Some(pats.flatMap(binders))
      case Defn.Var(_, pats, _, _) => Some(pats.flatMap(binders))
      case _: Defn => Some(Nil)
      case _: Import => Some(Nil)
      case _ => None
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy