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

dotty.tools.scaladoc.tasty.comments.Preparser.scala Maven / Gradle / Ivy

There is a newer version: 3.6.0-RC1-bin-20240903-21a3d39-NIGHTLY
Show newest version
package dotty.tools.scaladoc
package tasty.comments

import scala.collection.mutable
import scala.collection.immutable.SortedMap
import scala.util.matching.Regex

object Preparser {
  import Regexes._

  /** Parses a raw comment string into a `Comment` object. */
  def preparse(
    comment: List[String],
  ): PreparsedComment = {

    /** Parses a comment (in the form of a list of lines) to a `Comment`
      * instance, recursively on lines. To do so, it splits the whole comment
      * into main body and tag bodies, then runs the `WikiParser` on each body
      * before creating the comment instance.
      *
      * @param docBody     The body of the comment parsed until now.
      * @param tags        All tags parsed until now.
      * @param lastTagKey  The last parsed tag, or `None` if the tag section
      *                    hasn't started. Lines that are not tagged are part
      *                    of the previous tag or, if none exists, of the body.
      * @param remaining   The lines that must still recursively be parsed.
      * @param inCodeBlock Whether the next line is part of a code block (in
      *                    which no tags must be read).
      */
    def go(
      docBody: StringBuilder,
      tags: Map[TagKey, List[String]],
      lastTagKey: Option[TagKey],
      remaining: List[String],
      inCodeBlock: Boolean,
    )(using strippedLinesBeforeNo: Int = 0): PreparsedComment = remaining match {
      case CodeBlockStartRegex(before, marker, after) :: ls if !inCodeBlock =>
        if (!before.trim.isEmpty && !after.trim.isEmpty && marker == "```")
          go(docBody, tags, lastTagKey, before :: (marker + after) :: ls, inCodeBlock = false)
        else if (!before.trim.isEmpty && !after.trim.isEmpty)
          go(docBody, tags, lastTagKey, before :: marker :: after :: ls, inCodeBlock = false)
        else if (!before.trim.isEmpty)
          go(docBody, tags, lastTagKey, before :: marker :: ls, inCodeBlock = false)
        else if (!after.trim.isEmpty && marker != "```")
          go(docBody, tags, lastTagKey, marker :: after :: ls, inCodeBlock = true)
        else lastTagKey match {
          case Some(key) =>
            val value =
              ((tags get key): @unchecked) match {
                case Some(b :: bs) => (b + endOfLine + marker) :: bs
                case None => oops("lastTagKey set when no tag exists for key")
              }
            go(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock = true)
          case None =>
            go(docBody append endOfLine append (marker + after), tags, lastTagKey, ls, inCodeBlock = true)
        }

      case CodeBlockEndRegex(before, marker, after) :: ls =>
        if (!before.trim.isEmpty && !after.trim.isEmpty)
          go(docBody, tags, lastTagKey, before :: marker :: after :: ls, inCodeBlock = true)
        else if (!before.trim.isEmpty)
          go(docBody, tags, lastTagKey, before :: marker :: ls, inCodeBlock = true)
        else if (!after.trim.isEmpty)
          go(docBody, tags, lastTagKey, marker :: after :: ls, inCodeBlock = false)
        else lastTagKey match {
          case Some(key) =>
            val value =
              ((tags get key): @unchecked) match {
                case Some(b :: bs) => (b + endOfLine + marker) :: bs
                case None => oops("lastTagKey set when no tag exists for key")
              }
            go(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock = false)
          case None =>
            go(docBody append endOfLine append marker, tags, lastTagKey, ls, inCodeBlock = false)
        }


      case SymbolTagRegex(name, sym, body) :: ls if !inCodeBlock =>
        val key = SymbolTagKey(name, sym)
        val value = body :: tags.getOrElse(key, Nil)
        go(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)

      case SimpleTagRegex(name, body) :: ls if !inCodeBlock =>
        val key = SimpleTagKey(name)
        val value = body :: tags.getOrElse(key, Nil)
        go(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)


      case SingleTagRegex(name) :: ls if !inCodeBlock =>
        val key = SimpleTagKey(name)
        val value = "" :: tags.getOrElse(key, Nil)
        go(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)


      case line :: ls if lastTagKey.isDefined =>
        val newtags = if !line.isEmpty then {
          val key = lastTagKey.get
          val value =
            ((tags get key): @unchecked) match {
              case Some(b :: bs) => (b + endOfLine + line) :: bs
              case None => oops("lastTagKey set when no tag exists for key")
            }
          tags + (key -> value)
        } else tags
        go(docBody, newtags, lastTagKey, ls, inCodeBlock)


      case line :: ls =>
        if docBody.length > 0 then docBody.append(endOfLine)
        docBody.append(line)
        go(docBody, tags, lastTagKey, ls, inCodeBlock)(using strippedLinesBeforeNo + (if line.isEmpty && docBody.length == 0 then 1 else 0))


      case Nil =>
        // Take the {inheritance, content} diagram keys aside, as it doesn't need any parsing
        val inheritDiagramTag = SimpleTagKey("inheritanceDiagram")
        val contentDiagramTag = SimpleTagKey("contentDiagram")

        val inheritDiagramText: List[String] = tags.get(inheritDiagramTag) match {
          case Some(list) => list
          case None => List.empty
        }

        val contentDiagramText: List[String] = tags.get(contentDiagramTag) match {
          case Some(list) => list
          case None => List.empty
        }

        val stripTags = List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template"), SimpleTagKey("documentable"))
        val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1))

        val bodyTags: mutable.Map[TagKey, List[String]] =
          mutable.Map((tagsWithoutDiagram).toSeq*)

        def allTags(key: SimpleTagKey): List[String] =
          (bodyTags remove key).getOrElse(Nil).reverse

        def allSymsOneTag(key: TagKey, filterEmpty: Boolean = true): SortedMap[String, String] = {
          val keys: Seq[SymbolTagKey] =
            bodyTags.keys.toSeq flatMap {
              case stk: SymbolTagKey if (stk.name == key.name) => Some(stk)
              case stk: SimpleTagKey if (stk.name == key.name) =>
                // scaladoc.println(s"$span: tag '@${stk.name}' must be followed by a symbol name")
                None
              case _ => None
            }
          val pairs: Seq[(String, String)] =
            for (key <- keys) yield {
              val bs = (bodyTags remove key).get
              // if (bs.length > 1)
                // scaladoc.println(s"$span: only one '@${key.name}' tag for symbol ${key.symbol} is allowed")
              (key.symbol, bs.head)
            }
          SortedMap.empty[String, String] ++ pairs
        }

        val cmt = PreparsedComment(
          body                    = docBody.toString,
          authors                 = allTags(SimpleTagKey("author")),
          see                     = allTags(SimpleTagKey("see")),
          result                  = allTags(SimpleTagKey("return")),
          throws                  = allSymsOneTag(SimpleTagKey("throws")),
          valueParams             = allSymsOneTag(SimpleTagKey("param")),
          typeParams              = allSymsOneTag(SimpleTagKey("tparam")),
          version                 = allTags(SimpleTagKey("version")),
          since                   = allTags(SimpleTagKey("since")),
          todo                    = allTags(SimpleTagKey("todo")),
          deprecated              = allTags(SimpleTagKey("deprecated")),
          note                    = allTags(SimpleTagKey("note")),
          example                 = allTags(SimpleTagKey("example")),
          constructor             = allTags(SimpleTagKey("constructor")),
          group                   = allTags(SimpleTagKey("group")),
          groupDesc               = allSymsOneTag(SimpleTagKey("groupdesc")),
          groupNames              = allSymsOneTag(SimpleTagKey("groupname")),
          groupPrio               =
            allSymsOneTag(SimpleTagKey("groupprio")).transform((_, v) => v.toInt),
          hideImplicitConversions = allTags(SimpleTagKey("hideImplicitConversion")),
          shortDescription        = allTags(SimpleTagKey("shortDescription")),
          syntax                  = allTags(SimpleTagKey("syntax")),
          strippedLinesBeforeNo   = strippedLinesBeforeNo
        )

        cmt
    }

    go(new StringBuilder(comment.size), Map.empty, None, comment, inCodeBlock = false)
  }

  /** A key used for a tag map. The key is built from the name of the tag and
    * from the linked symbol if the tag has one.
    * Equality on tag keys is structural. */
  private sealed abstract class TagKey {
    def name: String
  }

  private case class SimpleTagKey(name: String) extends TagKey
  private case class SymbolTagKey(name: String, symbol: String) extends TagKey

  /** Something that should not have happened, happened, and Scaladoc should exit. */
  private def oops(msg: String): Nothing =
    throw new IllegalArgumentException("program logic: " + msg)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy