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

scala.tools.nsc.doc.html.SyntaxHigh.scala Maven / Gradle / Ivy

/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package tools.nsc.doc.html

import scala.annotation.{switch, tailrec}

/** Highlight the syntax of Scala code appearing in a `{{{` wiki block
  * (see method `HtmlPage.blockToHtml`).
  *
  * @author Stephane Micheloud
  */
private[html] object SyntaxHigh {
  import HtmlTags.{Elems, Raw, NoElems}

  /** Reserved words, sorted alphabetically
    * (see [[scala.reflect.internal.StdNames]]) */
  val reserved = Array(
    "abstract", "case", "catch", "class", "def",
    "do", "else", "extends", "false", "final", "finally",
    "for", "if", "implicit", "import", "lazy", "match",
    "new", "null", "object", "override", "package",
    "private", "protected", "return", "sealed", "super",
    "this", "throw", "trait", "true", "try", "type",
    "val", "var", "while", "with", "yield").sorted

  /** Annotations, sorted alphabetically */
  val annotations = Array(
    "BeanProperty", "BooleanBeanProperty", "SerialVersionUID",
    "beanGetter", "beanSetter",
    "companionClass", "companionMethod", "companionObject", "compileTimeOnly",
    "deprecated", "deprecatedName", "deprecatedOverriding", "deprecatedInheritance",
    "elidable", "field", "getter", "implicitAmbiguous", "implicitNotFound", "inline",
    "languageFeature",
    "migration", "native", "noinline", "param",
    "setter", "showAsInfix", "specialized", "strictfp", "switch",
    "tailrec", "throws", "transient",
    "unchecked", "uncheckedStable", "uncheckedVariance", "unspecialized",
    "varargs", "volatile").sorted

  /** Standard library classes/objects, sorted alphabetically */
  val standards = Array(
    "Any", "AnyRef", "AnyVal", "App", "Array",
    "Boolean", "Byte", "Char", "Class", "ClassManifest", "ClassTag",
    "Console", "Double", "Enumeration", "Float", "Function", "Int",
    "List", "Long", "Manifest", "Map",
    "NoManifest", "None", "Nothing", "Null", "Object", "Option", "OptManifest",
    "Predef",
    "Seq", "Set", "Short", "Some", "String", "Symbol",
    "TypeTag", "Unit", "WeakTypeTag").sorted

  def apply(data: String): Elems = {
    val buf = data.toCharArray
    val out = new StringBuilder

    def compare(offset: Int, key: String): Int = {
      var i = offset
      var j = 0
      val l = key.length
      while (i < buf.length && j < l) {
        val bch = buf(i)
        val kch = key charAt j
        if (bch < kch) return -1
        else if (bch > kch) return 1
        i += 1
        j += 1
      }
      if (j < l) -1
      else if (i < buf.length &&
               ('A' <= buf(i) && buf(i) <= 'Z' ||
                'a' <= buf(i) && buf(i) <= 'z' ||
                '0' <= buf(i) && buf(i) <= '9' ||
                buf(i) == '_')) 1
      else 0
    }

    def lookup(a: Array[String], i: Int): Int = {
      var lo = 0
      var hi = a.length - 1
      while (lo <= hi) {
        val m = (hi + lo) / 2
        val d = compare(i, a(m))
        if (d < 0) hi = m - 1
        else if (d > 0) lo = m + 1
        else return m
      }
      -1
    }

    def comment(i: Int): String = {
      val out = new StringBuilder("/")
      def line(i: Int): Int =
        if (i == buf.length || buf(i) == '\n') i
        else {
          out append buf(i)
          line(i+1)
        }
      var level = 0
      def multiline(i: Int, star: Boolean): Int = {
        if (i == buf.length) return i
        val ch = buf(i)
        out append ch
        ch match {
          case '*' =>
            if (star) level += 1
            multiline(i+1, !star)
          case '/' =>
            if (star) {
              if (level > 0) level -= 1
              if (level == 0) i else multiline(i+1, star = true)
            } else
              multiline(i+1, star = false)
          case _ =>
            multiline(i+1, star = false)
        }
      }
      if (buf(i) == '/') line(i) else multiline(i, star = true)
      out.toString
    }

    /* e.g. `val endOfLine = '\u000A'`*/
    def charlit(j: Int): String = {
      val out = new StringBuilder("'")
      def charlit0(i: Int, bslash: Boolean): Int = {
        if (i == buf.length) i
        else if (i > j+6) { out setLength 0; j }
        else {
          val ch = buf(i)
          out append ch
          ch match {
            case '\\' =>
              charlit0(i+1, bslash = true)
            case '\'' if !bslash =>
              i
            case _ =>
              if (bslash && '0' <= ch && ch <= '9') charlit0(i+1, bslash = true)
              else charlit0(i+1, bslash = false)
          }
        }
      }
      charlit0(j, bslash = false)
      out.toString
    }

    def strlit(i: Int): String = {
      val out = new StringBuilder()

      @tailrec def rawstrlit0(i: Int, bslash: Boolean): Unit = {
        if (i == buf.length) return
        val ch = buf(i)
        out.append(ch)
        ch match {
          case '\\' =>
            rawstrlit0(i+1, bslash = true)
          case '"' if !bslash && buf.slice(i+1, i+3).toString == "\"\"" =>
            out.append("\"\"")
          case _ =>
            rawstrlit0(i+1, bslash = false)
        }
      }

      @tailrec def strlit0(i: Int, bslash: Boolean): Unit = {
        if (i == buf.length) return
        val ch = buf(i)
        out append ch
        ch match {
          case '\\' =>
            strlit0(i+1, bslash = true)
          case '"' if !bslash =>
          case _ =>
            strlit0(i+1, bslash = false)
        }
      }

      buf.slice(i, i+3) match {
        case Array('"','"','"') =>
          out append "\"\"\""
          rawstrlit0(i+3, bslash = false)
        case _ =>
          out append "\""
          strlit0(i+1, bslash = false)
      }
      out.toString
    }

    def numlit(i: Int): String = {
      val out = new StringBuilder
      def intg(i: Int): Int = {
        if (i == buf.length) return i
        val ch = buf(i)
        ch match {
          case '.' =>
            out append ch
            frac(i+1)
          case _ =>
            if (Character.isDigit(ch)) {
              out append ch
              intg(i+1)
            } else i
        }
      }
      def frac(i: Int): Int = {
        if (i == buf.length) return i
        val ch = buf(i)
        ch match {
          case 'e' | 'E' =>
            out append ch
            expo(i+1, signed = false)
          case _ =>
            if (Character.isDigit(ch)) {
              out append ch
              frac(i+1)
            } else i
        }
      }
      def expo(i: Int, signed: Boolean): Int = {
        if (i == buf.length) return i
        val ch = buf(i)
        ch match {
          case '+' | '-' if !signed =>
            out append ch
            expo(i+1, signed = true)
          case _ =>
            if (Character.isDigit(ch)) {
              out append ch
              expo(i+1, signed)
            } else i
        }
      }
      intg(i)
      out.toString
    }

    def escape(str: String): String = {
      val array = str.toCharArray
      val len = array.length
      val buf = new java.lang.StringBuilder(len)
      @tailrec def loop(i: Int): String = {
        if (i < len) {
          (array(i): @switch) match {
            case '<' =>
              buf append "<"
            case '>' =>
              buf append ">"
            case c =>
              buf append c
          }
          loop(i + 1)
        } else {
          buf.toString
        }
      }
      loop(0)
    }

    @tailrec def parse(pre: String, i: Int): Unit = {
      out append pre
      if (i == buf.length) return
      buf(i) match {
        case '\n' =>
          parse("\n", i+1)
        case ' ' =>
          parse(" ", i+1)
        case '&' =>
          parse("&", i+1)
        case '<' if i+1 < buf.length =>
          val ch = buf(i+1)
          if (ch == '-' || ch == ':' || ch == '%')
            parse("<"+ch+"", i+2)
          else
            parse("<", i+1)
        case '>' =>
          if (i+1 < buf.length && buf(i+1) == ':')
            parse(">:", i+2)
          else
            parse(">", i+1)
        case '=' =>
          if (i+1 < buf.length && buf(i+1) == '>')
            parse("=>", i+2)
          else
            parse(buf(i).toString, i+1)
        case '/' =>
          if (i+1 < buf.length && (buf(i+1) == '/' || buf(i+1) == '*')) {
            val c = comment(i+1)
            val escaped = escape(c)
            parse(""+escaped+"", i+c.length)
          } else
            parse(buf(i).toString, i+1)
        case '\'' =>
          val s = charlit(i+1)
          if (s.length > 0)
            parse(""+s+"", i+s.length)
          else
            parse(buf(i).toString, i+1)
        case '"' =>
          val s = strlit(i)
          val escaped = escape(s)
          parse(""+escaped+"", i+s.length)
        case '@' =>
          val k = lookup(annotations, i+1)
          if (k >= 0)
            parse("@"+annotations(k)+"", i+annotations(k).length+1)
          else
            parse(buf(i).toString, i+1)
        case _ =>
          if (i == 0 || (i >= 1 && !Character.isJavaIdentifierPart(buf(i-1)))) {
            if (Character.isDigit(buf(i).toInt) ||
                (buf(i) == '.' && i + 1 < buf.length && Character.isDigit(buf(i+1).toInt))) {
              val s = numlit(i)
              parse(""+s+"", i+s.length)
            } else {
              val k = lookup(reserved, i)
              if (k >= 0)
                parse(""+reserved(k)+"", i+reserved(k).length)
              else {
                val k = lookup(standards, i)
                if (k >= 0)
                  parse(""+standards(k)+"", i+standards(k).length)
                else
                  parse(buf(i).toString, i+1)
              }
            }
          } else
            parse(buf(i).toString, i+1)
      }
    }

    parse("", 0)
    Raw(out.toString) :: NoElems
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy