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

                          
 
       
 
                            
                                                     
 
                   









                                                                    



                                     
 

                                                                
                     
                                                                   
















                                                                                                         
 




                                                                                            
 



                                                                                           



                                                                                               





                                                    







                                            



                                                                                         
 
                                                                                          
                                                                           




                                                                              
 



                                                     

                                           
 
                             


                                                                
             
 











                                                                 
 








                                                             
               

                  
 

                                                    
                                                  

                                                          
       
 
                                                                 
 


                                                   
 
                    



                                                                               
                                                          





                                                                
       
                       

     

                                                               
 
 
 
 

                                                          

                

                         
                                      
                          
                                       

                                                                      
                                  
     
                                                          



                                                
                                                              
   
 
                                                                             
                                          
                                                
 
                                                               

                                                                         
                    

                                   




                                                                                    
                                             
                                                       

                                  




                                                                                  
     

   
 







                                                        
 
                                                             
                                                              

                      
                                  
                 
                                    
                            

                          
                                         
                                                       
                                                                                  




                     




                                                            
 
                                                                    






                                                               

                                              



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

// $Id$

package scala.tools.nsc.util
import scala.tools.nsc.io.{AbstractFile, VirtualFile}

object SourceFile {
  // Be very careful touching these.
  // Apparently trivial changes to the way you write these constants
  // will cause Scanners.scala to go from a nice efficient switch to
  // a ghastly nested if statement which will bring the type checker
  // to its knees. See ticket #1456
  final val LF = '\u000A'
  final val FF = '\u000C'
  final val CR = '\u000D'
  final val SU = '\u001A'

  def isLineBreak(c: Int) = c match {
  case LF|FF|CR|SU => true
  case _ => false
  }
}
/** abstract base class of a source file used in the compiler */
abstract class SourceFile {
  import SourceFile._
  def content : RandomAccessSeq[Char] // normalized, must end in SU
  def file    : AbstractFile
  def isLineBreak(idx : Int) : 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
  def skipWhitespace(offset: Int): Int
  def lineToString(index: Int): String

  def identifier(pos : Position, compiler  : scala.tools.nsc.Global) : Option[String] = None

}
/** a file whose contents do not change over time */
class BatchSourceFile(val file : AbstractFile, _content : Array[Char]) extends SourceFile {
  import SourceFile._
  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 == that.file
    case _ => false
  }
  override def hashCode = file.hashCode

  var content = _content
  var length = _content.length

  def setContent(newContent : Array[Char]) {
    content = newContent
    length = newContent.length
  }

  // in SourceFileFragments, these are overridden to compensate during offset calculation
  // Invariant: length + start = underlyingLength
  def underlyingLength = length
  def start = 0

  override def identifier(pos : Position, compiler : scala.tools.nsc.Global) = pos match {
    case OffsetPosition(source,offset) if source == this && offset != -1 =>
      import java.lang.Character
      var i = offset + 1
      while (i < content.length &&
             (compiler.syntaxAnalyzer.isOperatorPart(content(i)) ||
              compiler.syntaxAnalyzer.isIdentifierPart(content(i)))) i = i + 1

      assert(i > offset)
      if (i <= content.length && offset >= 0)
        Some(new String(content, offset, i - offset))
      else None
  case _ => super.identifier(pos, compiler)
  }

  def isLineBreak(idx: Int) =
    if (idx >= content.length) false
    else if (!SourceFile.isLineBreak(content(idx))) false
    else if (content(idx) == CR && content(idx + 1) == LF) false
    else true

  def beginsWith(offset: Int, text: String): Boolean = {
    var idx = 0
    while (idx < text.length()) {
      if (offset + idx >= content.length) return false
      if (content(offset + idx) != text.charAt(idx)) return false
      idx += 1
    }
    return true
  }
  def skipWhitespace(offset: Int): Int =
    if (content(offset).isWhitespace) skipWhitespace(offset + 1)
    else offset

  def lineToString(index: Int): String = {
    var offset = lineToOffset(index)
    val buf = new StringBuilder()
    while (!isLineBreak(offset) && offset < content.length) {
      buf.append(content(offset))
      offset += 1
    }
    buf.toString()
  }
  object line {
    var index  = 0
    var offset = 0

    def find(toFind: Int, isIndex: Boolean): Int = {
      if (toFind == 0) return 0
      if (!isIndex && (toFind > content.length)) {
        throw new Error(toFind + " not valid offset in " +
                        file.name + ":" + content.length)
      }

      def get(isIndex : Boolean) = if (isIndex) index else offset

      val isBackward = toFind <= get(isIndex)
      val increment = if (isBackward) -1 else + 1
      val oneIfBackward = if (isBackward) +1 else 0

      while (true) {
        if (!isIndex && offset == toFind) return index;
        if (isBackward && offset <= 0)
          throw new Error(offset + " " + index + " " + toFind + " " + isIndex);
        offset = offset + increment
        if (!isBackward) assert(offset <= content.length);

        if (isLineBreak(offset + (if (isBackward) 0 else -1))) {
          index = index + increment
          if (isIndex && index + oneIfBackward == toFind)
            return offset + oneIfBackward;
        }
      }
      throw new Error()
    }
  }
  def offsetToLine(offset: Int): Int = line.find(offset, false)
  def lineToOffset(index : Int): Int = line.find(index , true)
}



/** A source file composed of multiple other source files.
 *
 *  @version 1.0
 */
class CompoundSourceFile(
    name: String,
    components: List[BatchSourceFile],
    contents: Array[Char])
extends BatchSourceFile(name, contents)
{
  /** The usual constructor.  Specify a name for the compound file and
   *  a list of component sources.
   */
  def this(name: String, components: BatchSourceFile*) = {
    this(
      name,
      components.toList,
      Array.concat(components.toList.map(comp =>
        CompoundSourceFile.stripSU(comp.content).toArray):_*))
  }

  /** Create an instance with the specified components and a generic name. */
  def this(components: BatchSourceFile*) =
    this("(virtual file)", components.toList:_*)

  override def positionInUltimateSource(position: Position) = {
    if (position.offset.isEmpty) super.positionInUltimateSource(position)
    else {
      println("!!!")
      var off = position.offset.get
      var compsLeft = components
      // the search here has to be against the length of the files underlying the
      // components, not their advertised length (which in the case of a fragment is
      // less than the underlying length.) Otherwise we can and will overshoot the
      // correct component and return a garbage position.
      while (compsLeft.head.underlyingLength-1 <= off && !compsLeft.tail.isEmpty) {
        println("discarding "+compsLeft.head)
        off = off - compsLeft.head.underlyingLength + 1
        compsLeft = compsLeft.tail
      }
      // now that we've identified the correct component, we have to adjust the
      // position we report since it is expected relative to the fragment, not the
      // underlying file.  Thus, off - comp.start.
      val comp = compsLeft.head
      comp.positionInUltimateSource(new OffsetPosition(this, off - comp.start))
    }
  }
}

object CompoundSourceFile {
  private[util] def stripSU(chars: Array[Char]) =
    if (chars.length > 0 && chars.last == SourceFile.SU)
      chars.slice(0, chars.length-1)
    else
      chars
}


/** One portion of an underlying file.  The fragment includes
  * the indices from the specified start (inclusively) to stop
  * (not inclusively).
  */
class SourceFileFragment private (
    name: String,
    underlyingFile: BatchSourceFile,
    override val start: Int,
    stop: Int,
    contents: Array[Char])
extends BatchSourceFile(name, contents) {
  override def underlyingLength = underlyingFile.length
  def this(name: String, underlyingFile: BatchSourceFile, start: Int, stop: Int) =
    this(
      name,
      underlyingFile,
      start,
      stop,
      { assert(start >= 0)
        assert(start <= stop)
        assert(start <= underlyingFile.length)
        assert(stop <= underlyingFile.length)
        underlyingFile.content.slice(start, stop).toArray })

  def this(underlyingFile: BatchSourceFile, start: Int, stop: Int) =
    this(
      "(fragment of " + underlyingFile.file.name + ")",
      underlyingFile,
      start,
      stop)

  override def positionInUltimateSource(position: Position) = {
    if (position.offset.isEmpty)
      super.positionInUltimateSource(position)
    else {
      super.positionInUltimateSource(
      new OffsetPosition(this, position.offset.get))
    }
  }
}