summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/util/SourceFile.scala
blob: b0c819fc08b38e52d4ae6c2f413cabae259fee41 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                

                          
 
 

                       
 
                                       
                                           


                              
              
 

                                                                
                                                                 

                                      
                              














                                                                                                         
 

                                                      
 
                                        
                                                                                         
 




                                                                            
 
 



































                                                                                                                                                      
                               

                           
                              
 
                             

                                   
                                                                          

                                                                   
     
 
                                               

                                  

                                                                       






                                                         


                                                








                                                                                  
 















                                                                                                                   

                                                                  















                                                                                                                     

                                                               
 
/* NSC -- new Scala compiler
 * Copyright 2005-2010 LAMP/EPFL
 * @author  Martin Odersky
 */


package scala.tools.nsc
package util

import io.{ AbstractFile, VirtualFile }
import scala.collection.mutable.ArrayBuffer
import annotation.tailrec
import java.util.regex.Pattern
import java.io.IOException
import Chars._

/** abstract base class of a source file used in the compiler */
abstract class SourceFile {
  def content : Array[Char]         // normalized, must end in SU
  def file    : AbstractFile
  def isLineBreak(idx : Int) : Boolean
  def isSelfContained: Boolean
  def length : Int
  def position(offset: Int) : Position = {
    assert(offset < length)
    new OffsetPosition(this, offset)
  }
  def position(line: Int, column: Int) : Position = new OffsetPosition(this, lineToOffset(line) + column)
  def offsetToLine(offset: Int): Int
  def lineToOffset(index : Int): Int
  /** Map a position to a position in the underlying source file.
   *  For regular source files, simply return the argument.
   */
  def positionInUltimateSource(position: Position) = position
  override def toString(): String = file.name /* + ":" + content.length */
  def dbg(offset: Int) = (new OffsetPosition(this, offset)).dbgString
  def path = file.path

  def beginsWith(offset: Int, text: String): Boolean =
    (content drop offset) startsWith text

  def lineToString(index: Int): String =
    content drop lineToOffset(index) takeWhile (c => !isLineBreakChar(c.toChar)) mkString

  @tailrec
  final def skipWhitespace(offset: Int): Int =
    if (content(offset).isWhitespace) skipWhitespace(offset + 1) else offset

  def identifier(pos: Position, compiler: Global): Option[String] = None
}

object ScriptSourceFile {
  /** Length of the script header from the given content, if there is one.
   *  The header begins with "#!" or "::#!" and ends with a line starting
   *  with "!#" or "::!#".
   */
  def headerLength(cs: Array[Char]): Int = {
    val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE)
    val headerStarts  = List("#!", "::#!")

    if (headerStarts exists (cs startsWith _)) {
      val matcher = headerPattern matcher cs.mkString
      if (matcher.find) matcher.end
      else throw new IOException("script file does not close its header with !# or ::!#")
    }
    else 0
  }
  def stripHeader(cs: Array[Char]): Array[Char] = cs drop headerLength(cs)

  def apply(file: AbstractFile, content: Array[Char]) = {
    val underlying = new BatchSourceFile(file, content)
    val headerLen = headerLength(content)
    val stripped = new ScriptSourceFile(underlying, content drop headerLen, headerLen)

    stripped
  }
}
import ScriptSourceFile._

class ScriptSourceFile(underlying: BatchSourceFile, content: Array[Char], override val start: Int) extends BatchSourceFile(underlying.file, content) {
  override def isSelfContained = false

  override def positionInUltimateSource(pos: Position) =
    if (!pos.isDefined) super.positionInUltimateSource(pos)
    else new OffsetPosition(underlying, pos.point + start)
}

sealed trait LineOffsetMapper {
  def content : Array[Char]
  def length : Int
  def lineIndices : Array[Int]

  def isLineBreak(idx: Int) =
    if (idx >= length) false else {
      val ch = content(idx)
      // don't identify the CR in CR LF as a line break, since LF will do.
      if (ch == CR) (idx + 1 == length) || (content(idx + 1) != LF)
      else isLineBreakChar(ch)
    }

  def calculateLineIndices(cs: Array[Char]) = {
    val buf = new ArrayBuffer[Int]
    buf += 0
    for (i <- 0 until cs.length) if (isLineBreak(i)) buf += i + 1
    buf += cs.length // sentinel, so that findLine below works smoother
    buf.toArray
  }

  def lineToOffset(index : Int): Int = lineIndices(index)

  private var lastLine = 0

  /** Convert offset to line in this source file
   *  Lines are numbered from 0
   */
  def offsetToLine(offset: Int): Int = {
    val lines = lineIndices
    def findLine(lo: Int, hi: Int, mid: Int): Int =
      if (offset < lines(mid)) findLine(lo, mid - 1, (lo + mid - 1) / 2)
      else if (offset >= lines(mid + 1)) findLine(mid + 1, hi, (mid + 1 + hi) / 2)
      else mid
    lastLine = findLine(0, lines.length, lastLine)
    lastLine
  }
}

/** a file whose contents do not change over time */
class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends SourceFile with LineOffsetMapper {

  def this(_file: AbstractFile)                 = this(_file, _file.toCharArray)
  def this(sourceName: String, cs: Seq[Char])   = this(new VirtualFile(sourceName), cs.toArray)
  def this(file: AbstractFile, cs: Seq[Char])   = this(file, cs.toArray)

  override def equals(that : Any) = that match {
    case that : BatchSourceFile => file.path == that.file.path && start == that.start
    case _ => false
  }
  override def hashCode = file.path.## + start.##
  val length = content.length
  def start = 0
  def isSelfContained = true

  lazy val lineIndices: Array[Int] = calculateLineIndices(content)
}

/** a file whose contents do change over time */
class MutableSourceFile(val file : AbstractFile, var content: Array[Char]) extends SourceFile with LineOffsetMapper {
  def this(_file: AbstractFile)                 = this(_file, _file.toCharArray)
  def this(sourceName: String, cs: Seq[Char])   = this(new VirtualFile(sourceName), cs.toArray)
  def this(file: AbstractFile, cs: Seq[Char])   = this(file, cs.toArray)

  override def equals(that : Any) = that match {
    case that : MutableSourceFile => file.path == that.file.path && start == that.start
    case _ => false
  }
  override def hashCode = file.path.## + start.##
  def length = content.length
  def start = 0
  def isSelfContained = true

  def lineIndices : Array[Int] =  calculateLineIndices(content)
}