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

scalariform.lexer.NewlineInferencer.scala Maven / Gradle / Ivy

The newest version!
package scalariform.lexer

import scalariform.lexer.Tokens._

/**
 * Logic for promoting intertoken whitespace/comments to a NEWLINE or NEWLINES token as required.
 */
class NewlineInferencer(delegate: WhitespaceAndCommentsGrouper) extends Iterator[Token] {

  import NewlineInferencer._

  /**
   * Contains the next unprocessed token from the delegate, or null if we've processed them all.
   */
  private var nextToken: Token = if (delegate.hasNext) delegate.next() else null

  private var previousToken: Token = _

  /**
   * If not null, we emit this token rather than looking at nextToken (used for inserting NEWLINE or NEWLINES
   * into the stream).
   */
  private var tokenToEmitNextTime: Token = _

  /**
   *  Keeps track of the nested regions which allow multiple statements or not. An item in the stack indicates the
   *  expected closing token type for that region.
   */
  private var regionMarkerStack: List[TokenType] = Nil

  def hasNext = nextToken != null || tokenToEmitNextTime != null

  def next(): Token = {
    val token =
      if (tokenToEmitNextTime == null)
        fetchNextToken()
      else {
        val result = tokenToEmitNextTime
        tokenToEmitNextTime = null
        result
      }
    updateRegionStack(token.tokenType, nextToken)
    previousToken = token
    token
  }

  /**
   * Multiple statements are allowed immediately inside "{..}" regions, but disallowed immediately inside "[..]", "(..)"
   * and "case ... =>" regions.
   */
  private def updateRegionStack(tokenType: TokenType, nextToken: Token) =
    tokenType match {
      case LBRACE ⇒
        regionMarkerStack ::= RBRACE
      case LPAREN ⇒
        regionMarkerStack ::= RPAREN
      case LBRACKET ⇒
        regionMarkerStack ::= RBRACKET
      case CASE if nextToken != null ⇒
        val followingTokenType = nextToken.tokenType
        // "case class" and "case object" are excluded from the usual "case .. =>" region.
        if (followingTokenType != CLASS && followingTokenType != OBJECT)
          regionMarkerStack ::= ARROW
      case tokenType if regionMarkerStack.headOption == Some(tokenType) ⇒
        regionMarkerStack = regionMarkerStack.tail
      case _ ⇒
    }

  private def fetchNextToken(): Token = {
    val token = nextToken
    if (delegate.hasNext)
      nextToken = delegate.next()
    else
      nextToken = null
    if (shouldTranslateToNewline(previousToken, token, nextToken))
      translateToNewline(token)
    else
      token
  }

  private def translateToNewline(currentToken: Token): Token = {
    val currentHiddenTokens = currentToken.associatedWhitespaceAndComments
    val rawText = delegate.text.substring(currentHiddenTokens.offset, currentHiddenTokens.lastCharacterOffset + 1)
    val text = if (currentHiddenTokens.containsUnicodeEscape) currentHiddenTokens.text else rawText
    val tokenType = if (containsBlankLine(text)) NEWLINES else NEWLINE
    val newlineToken = Token(tokenType, text, currentHiddenTokens.offset, rawText)
    currentToken.associatedWhitespaceAndComments_ = NoHiddenTokens
    newlineToken.associatedWhitespaceAndComments_ = currentHiddenTokens
    tokenToEmitNextTime = currentToken
    newlineToken
  }

  private def containsNewline(hiddenTokens: HiddenTokens) =
    hiddenTokens.exists(_.text contains '\n')

  private def shouldTranslateToNewline(previousToken: Token, currentToken: Token, nextToken: Token): Boolean = {
    val hiddenTokens = currentToken.associatedWhitespaceAndComments
    val currentTokenType = currentToken.tokenType
    if (!containsNewline(hiddenTokens))
      false
    else if (TOKENS_WHICH_CANNOT_BEGIN_A_STATEMENT contains currentTokenType)
      false
//    else if (currentTokenType == CASE && !followingTokenIsClassOrObject(nextToken))
//      false
    else if (regionMarkerStack.nonEmpty && regionMarkerStack.head != RBRACE)
      false
    else
      return previousToken != null && (TOKENS_WHICH_CAN_END_A_STATEMENT contains previousToken.tokenType)
  }

  private def followingTokenIsClassOrObject(nextToken: Token): Boolean =
    nextToken != null && (nextToken.tokenType == CLASS || nextToken.tokenType == OBJECT)

  private def containsBlankLine(s: String): Boolean = {
    var i = 0
    var inBlankLine = false
    while (i < s.length) {
      val c = s(i)
      if (inBlankLine) {
        if (c == '\n' || c == '\f')
          return true
        else if (c > ' ')
          inBlankLine = false
      } else if (c == '\n' || c == '\f')
        inBlankLine = true
      i += 1
    }
    false
  }

}

object NewlineInferencer {

  private val TOKENS_WHICH_CAN_END_A_STATEMENT: Set[TokenType] = Set(
    INTEGER_LITERAL, FLOATING_POINT_LITERAL, CHARACTER_LITERAL, STRING_LITERAL, SYMBOL_LITERAL, VARID, OTHERID, PLUS,
    MINUS, STAR, PIPE, TILDE, EXCLAMATION, THIS, NULL, TRUE, FALSE, RETURN, TYPE, XML_EMPTY_CLOSE, XML_TAG_CLOSE,
    XML_COMMENT, XML_CDATA, XML_UNPARSED, XML_PROCESSING_INSTRUCTION, USCORE, RPAREN, RBRACKET, RBRACE)

  private val TOKENS_WHICH_CANNOT_BEGIN_A_STATEMENT: Set[TokenType] = Set(
    CATCH, ELSE, EXTENDS, FINALLY, FORSOME, MATCH, REQUIRES, WITH, YIELD, COMMA, DOT, SEMI, COLON, /* USCORE, */ EQUALS,
    ARROW, LARROW, SUBTYPE, VIEWBOUND, SUPERTYPE, HASH, LBRACKET, RPAREN, RBRACKET, RBRACE)

  private val BLANK_LINE_PATTERN = """(?s).*\n\s*\n.*"""

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy