summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-05-23 18:21:07 +0000
committerPaul Phillips <paulp@improving.org>2010-05-23 18:21:07 +0000
commit41d361a9d26cb550a11691a5b93fb7f3ab5223d3 (patch)
tree040c9276da673832295571af5585b60a846bf7c0 /src/compiler/scala/tools
parent3bad6d54b19c9ccc01a491b6483bb205d855381a (diff)
downloadscala-41d361a9d26cb550a11691a5b93fb7f3ab5223d3.tar.gz
scala-41d361a9d26cb550a11691a5b93fb7f3ab5223d3.tar.bz2
scala-41d361a9d26cb550a11691a5b93fb7f3ab5223d3.zip
Changed the script runner mechanism to alchemiz...
Changed the script runner mechanism to alchemize from AST atoms rather than generating wrapper source, and fixed script position reporting. This patch does not include a discussed change to mark some positions as synthetic. Closes #3119, #3121. Review by milessabin.
Diffstat (limited to 'src/compiler/scala/tools')
-rw-r--r--src/compiler/scala/tools/nsc/CompilationUnits.scala5
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala25
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala19
-rw-r--r--src/compiler/scala/tools/nsc/ScriptRunner.scala77
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeDSL.scala8
-rw-r--r--src/compiler/scala/tools/nsc/ast/parser/Parsers.scala65
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala2
-rw-r--r--src/compiler/scala/tools/nsc/interactive/RangePositions.scala2
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala1
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Parsed.scala1
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/package.scala3
-rw-r--r--src/compiler/scala/tools/nsc/io/PlainFile.scala3
-rw-r--r--src/compiler/scala/tools/nsc/util/SourceFile.scala180
-rw-r--r--src/compiler/scala/tools/nsc/util/package.scala29
14 files changed, 176 insertions, 244 deletions
diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala
index 4071f98b5b..a9300ff304 100644
--- a/src/compiler/scala/tools/nsc/CompilationUnits.scala
+++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala
@@ -5,9 +5,8 @@
package scala.tools.nsc
-import scala.tools.nsc.util.{FreshNameCreator,OffsetPosition,Position,NoPosition,SourceFile}
-import scala.tools.nsc.io.AbstractFile
-import scala.collection.mutable.{LinkedHashSet, HashSet, HashMap, ListBuffer}
+import util.{ FreshNameCreator,Position,NoPosition,SourceFile }
+import scala.collection.mutable.{ LinkedHashSet, HashSet, HashMap, ListBuffer }
trait CompilationUnits { self: Global =>
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index bb5b073f15..7efd8ad2a0 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -11,7 +11,7 @@ import compat.Platform.currentTime
import io.{ SourceReader, AbstractFile, Path }
import reporters.{ Reporter, ConsoleReporter }
-import util.{ ClassPath, SourceFile, Statistics, BatchSourceFile }
+import util.{ ClassPath, SourceFile, Statistics, BatchSourceFile, ScriptSourceFile, returning }
import collection.mutable.{ HashSet, HashMap, ListBuffer }
import reflect.generic.{ PickleBuffer }
@@ -219,8 +219,13 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
if (settings.verbose.value || settings.Ylogcp.value)
inform("[Classpath = " + classPath.asClasspathString + "]")
+ /** True if -Xscript has been set, indicating a script run.
+ */
+ def isScriptRun = settings.script.value != ""
+
def getSourceFile(f: AbstractFile): BatchSourceFile =
- new BatchSourceFile(f, reader.read(f))
+ if (isScriptRun) ScriptSourceFile(f, reader read f)
+ else new BatchSourceFile(f, reader read f)
def getSourceFile(name: String): SourceFile = {
val f = AbstractFile.getFile(name)
@@ -795,21 +800,15 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
/** Compile list of abstract files */
def compileFiles(files: List[AbstractFile]) {
- try {
- compileSources(files map getSourceFile)
- } catch {
- case ex: IOException => error(ex.getMessage())
- }
+ try compileSources(files map getSourceFile)
+ catch { case ex: IOException => error(ex.getMessage()) }
}
/** Compile list of files given by their names */
def compile(filenames: List[String]) {
- val scriptMain = settings.script.value
- def sources: List[SourceFile] = scriptMain match {
- case "" => filenames map getSourceFile
- case main if filenames.length == 1 => List(ScriptRunner.wrappedScript(main, filenames.head, getSourceFile))
- case _ => error("can only compile one script at a time") ; Nil
- }
+ val sources: List[SourceFile] =
+ if (isScriptRun && filenames.size > 1) returning(Nil)(_ => error("can only compile one script at a time"))
+ else filenames map getSourceFile
try compileSources(sources)
catch { case ex: IOException => error(ex.getMessage()) }
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index 743ffefcd0..868af7a4bc 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -26,7 +26,7 @@ import scala.util.control.Exception.{ Catcher, catching, ultimately, unwrapping
import io.{ PlainFile, VirtualDirectory }
import reporters.{ ConsoleReporter, Reporter }
import symtab.{ Flags, Names }
-import util.{ SourceFile, BatchSourceFile, ClassPath, Chars }
+import util.{ SourceFile, BatchSourceFile, ScriptSourceFile, ClassPath, Chars, stringFromWriter }
import scala.reflect.NameTransformer
import scala.tools.nsc.{ InterpreterResults => IR }
import interpreter._
@@ -312,15 +312,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
def getVarName = varNameCreator()
def getSynthVarName = synthVarNameCreator()
- /** generate a string using a routine that wants to write on a stream */
- private def stringFrom(writer: PrintWriter => Unit): String = {
- val stringWriter = new StringWriter()
- val stream = new NewLinePrintWriter(stringWriter)
- writer(stream)
- stream.close
- stringWriter.toString
- }
-
/** Truncate a string if it is longer than isettings.maxPrintString */
private def truncPrintString(str: String): String = {
val maxpr = isettings.maxPrintString
@@ -343,7 +334,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
def indentCode(code: String) = {
/** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */
val noIndent = (code contains "\n") && (List("\"\"\"", "</", "/>") exists (code contains _))
- stringFrom(str =>
+ stringFromWriter(str =>
for (line <- code.lines) {
if (!noIndent)
str.print(spaces)
@@ -831,7 +822,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
def toCompute = line
/** generate the source code for the object that computes this request */
- def objectSourceCode: String = stringFrom { code =>
+ def objectSourceCode: String = stringFromWriter { code =>
val preamble = """
|object %s {
| %s%s
@@ -845,7 +836,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
/** generate source code for the object that retrieves the result
from objectSourceCode */
- def resultObjectSourceCode: String = stringFrom { code =>
+ def resultObjectSourceCode: String = stringFromWriter { code =>
/** We only want to generate this code when the result
* is a value which can be referred to as-is.
*/
@@ -966,7 +957,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
case t: Throwable if bindLastException =>
withoutBindingLastException {
quietBind("lastException", "java.lang.Throwable", t)
- (stringFrom(t.printStackTrace(_)), false)
+ (stringFromWriter(t.printStackTrace(_)), false)
}
}
diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala
index da2738500b..a7d67a3af9 100644
--- a/src/compiler/scala/tools/nsc/ScriptRunner.scala
+++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala
@@ -16,11 +16,9 @@ import io.{ Directory, File, Path, PlainFile }
import java.lang.reflect.InvocationTargetException
import java.net.URL
import java.util.jar.{ JarEntry, JarOutputStream }
-import java.util.regex.Pattern
import scala.tools.util.PathResolver
import scala.tools.nsc.reporters.{Reporter,ConsoleReporter}
-import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, SourceFile, SourceFileFragment}
/** An object that runs Scala code in script files.
*
@@ -48,8 +46,7 @@ import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, Sou
* @todo It would be better if error output went to stderr instead
* of stdout...
*/
-object ScriptRunner
-{
+object ScriptRunner {
/* While I'm chasing down the fsc and script bugs. */
def DBG(msg: Any) {
System.err.println(msg.toString)
@@ -68,6 +65,8 @@ object ScriptRunner
case x => x
}
+ def isScript(settings: Settings) = settings.script.value != ""
+
/** Choose a jar filename to hold the compiled version of a script. */
private def jarFileFor(scriptFile: String): File = {
val name =
@@ -118,22 +117,6 @@ object ScriptRunner
/** Read the entire contents of a file as a String. */
private def contentsOfFile(filename: String) = File(filename).slurp()
- /** Find the length of the header in the specified file, if
- * there is one. The header part starts with "#!" or "::#!"
- * and ends with a line that begins with "!#" or "::!#".
- */
- private def headerLength(filename: String): Int = {
- val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE)
- val fileContents = contentsOfFile(filename)
- def isValid = List("#!", "::#!") exists (fileContents startsWith _)
-
- if (!isValid) 0 else {
- val matcher = headerPattern matcher fileContents
- if (matcher.find) matcher.end
- else throw new IOException("script file does not close its header with !# or ::!#")
- }
- }
-
/** Split a fully qualified object name into a
* package and an unqualified object name */
private def splitObjectName(fullname: String): (Option[String], String) =
@@ -142,48 +125,6 @@ object ScriptRunner
case idx => (Some(fullname take idx), fullname drop (idx + 1))
}
- /** Code that is added to the beginning of a script file to make
- * it a complete Scala compilation unit.
- */
- protected def preambleCode(objectName: String): String = {
- val (maybePack, objName) = splitObjectName(objectName)
- val packageDecl = maybePack map ("package %s\n" format _) getOrElse ("")
-
- return """|
- | object %s {
- | def main(argv: Array[String]): Unit = {
- | val args = argv
- | new AnyRef {
- |""".stripMargin.format(objName)
- }
-
- /** Code that is added to the end of a script file to make
- * it a complete Scala compilation unit.
- */
- val endCode = """
- | }
- | }
- | }
- |""".stripMargin
-
- /** Wrap a script file into a runnable object named
- * <code>scala.scripting.Main</code>.
- */
- def wrappedScript(
- objectName: String,
- filename: String,
- getSourceFile: PlainFile => BatchSourceFile): SourceFile =
- {
- val preamble = new BatchSourceFile("<script preamble>", preambleCode(objectName).toCharArray)
- val middle = {
- val bsf = getSourceFile(PlainFile fromPath filename)
- new SourceFileFragment(bsf, headerLength(filename), bsf.length)
- }
- val end = new BatchSourceFile("<script trailer>", endCode.toCharArray)
-
- new CompoundSourceFile(preamble, middle, end)
- }
-
/** Compile a script using the fsc compilation daemon.
*
* @param settings ...
@@ -242,12 +183,15 @@ object ScriptRunner
settings.outdir.value = compiledPath.path
if (settings.nocompdaemon.value) {
+ /** Setting settings.script.value informs the compiler this is not a
+ * self contained compilation unit.
+ */
+ settings.script.value = scriptMain(settings)
val reporter = new ConsoleReporter(settings)
val compiler = newGlobal(settings, reporter)
val cr = new compiler.Run
- val wrapped = wrappedScript(scriptMain(settings), scriptFile, compiler getSourceFile _)
- cr compileSources List(wrapped)
+ cr compile List(scriptFile)
if (reporter.hasErrors) None else Some(compiledPath)
}
else if (compileWithDaemon(settings, scriptFile)) Some(compiledPath)
@@ -294,10 +238,7 @@ object ScriptRunner
val classpath = pr.asURLs :+ File(compiledLocation).toURL
try {
- ObjectRunner.run(
- classpath,
- scriptMain(settings),
- scriptArgs)
+ ObjectRunner.run(classpath, scriptMain(settings), scriptArgs)
true
}
catch {
diff --git a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
index 38240316bb..34d3423401 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeDSL.scala
@@ -7,6 +7,8 @@
package scala.tools.nsc
package ast
+import PartialFunction._
+
/** A DSL for generating scala code. The goal is that the
* code generating code should look a lot like the code it
* generates.
@@ -18,22 +20,20 @@ trait TreeDSL {
import global._
import definitions._
import gen.{ scalaDot }
- import PartialFunction._
object CODE {
// Add a null check to a Tree => Tree function
def nullSafe[T](f: Tree => Tree, ifNull: Tree): Tree => Tree =
tree => IF (tree MEMBER_== NULL) THEN ifNull ELSE f(tree)
- // Applies a function to a value and then returns the value.
- def returning[T](x: T)(f: T => Unit): T = { f(x) ; x }
-
// strip bindings to find what lies beneath
final def unbind(x: Tree): Tree = x match {
case Bind(_, y) => unbind(y)
case y => y
}
+ def returning[T](x: T)(f: T => Unit): T = util.returning(x)(f)
+
object LIT extends (Any => Literal) {
def apply(x: Any) = Literal(Constant(x))
def unapply(x: Any) = condOpt(x) { case Literal(Constant(value)) => value }
diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
index 8e9af3c341..73cc62c3fa 100644
--- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
+++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
@@ -9,7 +9,7 @@ package scala.tools.nsc
package ast.parser
import scala.collection.mutable.ListBuffer
-import scala.tools.nsc.util.{OffsetPosition, BatchSourceFile}
+import util.{ OffsetPosition, BatchSourceFile }
import symtab.Flags
import Tokens._
@@ -63,6 +63,13 @@ self =>
def this(unit: global.CompilationUnit) = this(unit, List())
+ /** The parse starting point depends on whether the source file is self-contained:
+ * if not, the AST will be supplemented.
+ */
+ def parseStartRule =
+ if (unit.source.isSelfContained) () => compilationUnit()
+ else () => scriptBody()
+
val in = new UnitScanner(unit, patches)
in.init()
@@ -169,14 +176,66 @@ self =>
*/
var classContextBounds: List[Tree] = Nil
- /** this is the general parse method
+ def parseStartRule: () => Tree
+
+ /** This is the general parse entry point.
*/
def parse(): Tree = {
- val t = compilationUnit()
+ val t = parseStartRule()
accept(EOF)
t
}
+ /** This is the parse entry point for code which is not self-contained, e.g.
+ * a script which is a series of template statements. They will be
+ * swaddled in Trees until the AST is equivalent to the one returned
+ * by compilationUnit().
+ */
+ def scriptBody(): Tree = {
+ val stmts = templateStatSeq(false)._2
+ accept(EOF)
+
+ /** Here we are building an AST representing the following source fiction,
+ * where <moduleName> is from -Xscript (defaults to "Main") and <stmts> are
+ * the result of parsing the script file.
+ *
+ * object <moduleName> {
+ * def main(argv: Array[String]): Unit = {
+ * val args = argv
+ * new AnyRef {
+ * <stmts>
+ * }
+ * }
+ * }
+ */
+ import definitions._
+
+ def emptyPkg = atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) }
+ def emptyInit = DefDef(
+ NoMods,
+ nme.CONSTRUCTOR,
+ Nil,
+ List(Nil),
+ TypeTree(),
+ Block(List(Apply(Select(Super("", ""), nme.CONSTRUCTOR), Nil)), Literal(Constant(())))
+ )
+
+ // def main
+ def mainParamType = AppliedTypeTree(Ident("Array".toTypeName), List(Ident("String".toTypeName)))
+ def mainParameter = List(ValDef(Modifiers(Flags.PARAM), "argv", mainParamType, EmptyTree))
+ def mainSetArgv = List(ValDef(NoMods, "args", TypeTree(), Ident("argv")))
+ def mainNew = makeNew(Nil, emptyValDef, stmts, List(Nil), NoPosition, NoPosition)
+ def mainDef = DefDef(NoMods, "main", Nil, List(mainParameter), scalaDot(nme.Unit.toTypeName), Block(mainSetArgv, mainNew))
+
+ // object Main
+ def moduleName = ScriptRunner scriptMain settings
+ def moduleBody = Template(List(scalaScalaObjectConstr), emptyValDef, List(emptyInit, mainDef))
+ def moduleDef = ModuleDef(NoMods, moduleName, moduleBody)
+
+ // package <empty> { ... }
+ makePackaging(0, emptyPkg, List(moduleDef))
+ }
+
/* --------------- PLACEHOLDERS ------------------------------------------- */
/** The implicit parameters introduced by `_' in the current expression.
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala
index 03fd92235d..2c174860e4 100644
--- a/src/compiler/scala/tools/nsc/interactive/Global.scala
+++ b/src/compiler/scala/tools/nsc/interactive/Global.scala
@@ -7,7 +7,7 @@ import scala.collection.mutable.{LinkedHashMap, SynchronizedMap}
import scala.concurrent.SyncVar
import scala.util.control.ControlThrowable
import scala.tools.nsc.io.AbstractFile
-import scala.tools.nsc.util.{SourceFile, Position, RangePosition, OffsetPosition, NoPosition, WorkScheduler}
+import scala.tools.nsc.util.{SourceFile, Position, RangePosition, NoPosition, WorkScheduler}
import scala.tools.nsc.reporters._
import scala.tools.nsc.symtab._
import scala.tools.nsc.ast._
diff --git a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala
index 337f306664..6ef85b2f59 100644
--- a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala
+++ b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala
@@ -3,7 +3,7 @@ package interactive
import ast.Trees
import symtab.Positions
-import scala.tools.nsc.util.{SourceFile, Position, RangePosition, OffsetPosition, NoPosition, WorkScheduler}
+import scala.tools.nsc.util.{SourceFile, Position, RangePosition, NoPosition, WorkScheduler}
import scala.collection.mutable.ListBuffer
/** Handling range positions
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
index bdbcde31a2..58ce85f1f6 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -9,6 +9,7 @@ package interpreter
import jline._
import java.util.{ List => JList }
+import util.returning
object Completion {
def looksLikeInvocation(code: String) = (
diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
index 0b92608d88..84f5477c21 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
@@ -7,6 +7,7 @@ package scala.tools.nsc
package interpreter
import jline.ArgumentCompletor.{ ArgumentDelimiter, ArgumentList }
+import util.returning
/** One instance of a command buffer.
*/
diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala
index ab2860ac8e..eaf736c5b7 100644
--- a/src/compiler/scala/tools/nsc/interpreter/package.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/package.scala
@@ -6,9 +6,6 @@
package scala.tools.nsc
package object interpreter {
- /** Apply a function and return the passed value */
- def returning[T](x: T)(f: T => Unit): T = { f(x) ; x }
-
/** Tracing */
def tracing[T](msg: String)(x: T): T = { println("(" + msg + ") " + x) ; x }
diff --git a/src/compiler/scala/tools/nsc/io/PlainFile.scala b/src/compiler/scala/tools/nsc/io/PlainFile.scala
index 81e5221a3c..9346e88bb2 100644
--- a/src/compiler/scala/tools/nsc/io/PlainFile.scala
+++ b/src/compiler/scala/tools/nsc/io/PlainFile.scala
@@ -10,8 +10,7 @@ package io
import java.io.{ File => JFile, FileInputStream, FileOutputStream, IOException }
import PartialFunction._
-object PlainFile
-{
+object PlainFile {
/**
* If the specified File exists, returns an abstract file backed
* by it. Otherwise, returns null.
diff --git a/src/compiler/scala/tools/nsc/util/SourceFile.scala b/src/compiler/scala/tools/nsc/util/SourceFile.scala
index ca22f53f7d..8c1d308209 100644
--- a/src/compiler/scala/tools/nsc/util/SourceFile.scala
+++ b/src/compiler/scala/tools/nsc/util/SourceFile.scala
@@ -7,9 +7,11 @@
package scala.tools.nsc
package util
-import scala.tools.nsc.io.{AbstractFile, VirtualFile}
+import io.{ AbstractFile, VirtualFile }
import scala.collection.mutable.ArrayBuffer
-import annotation.{ tailrec, switch }
+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 */
@@ -17,6 +19,7 @@ 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)
@@ -46,6 +49,42 @@ abstract class SourceFile {
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)
+}
+
/** a file whose contents do not change over time */
class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends SourceFile {
@@ -54,16 +93,13 @@ class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends
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
+ case that : BatchSourceFile => file.path == that.file.path && start == that.start
case _ => false
}
- override def hashCode = file.path.hashCode
+ override def hashCode = file.path.## + start.##
val length = content.length
-
- // in SourceFileFragments, these are overridden to compensate during offset calculation
- // Invariant: length + start = underlyingLength
- def underlyingLength = length
def start = 0
+ def isSelfContained = true
override def identifier(pos: Position, compiler: Global) =
if (pos.isDefined && pos.source == this && pos.point != -1) {
@@ -81,13 +117,14 @@ class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends
else isLineBreakChar(ch)
}
- private lazy val lineIndices: Array[Int] = {
+ def calculateLineIndices(cs: Array[Char]) = {
val buf = new ArrayBuffer[Int]
buf += 0
- for (i <- 0 until content.length) if (isLineBreak(i)) buf += i + 1
- buf += content.length // sentinel, so that findLine below works smoother
+ for (i <- 0 until cs.length) if (isLineBreak(i)) buf += i + 1
+ buf += cs.length // sentinel, so that findLine below works smoother
buf.toArray
}
+ private lazy val lineIndices: Array[Int] = calculateLineIndices(content)
def lineToOffset(index : Int): Int = lineIndices(index)
@@ -105,125 +142,4 @@ class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends
lastLine = findLine(0, lines.length, lastLine)
lastLine
}
-
-/**
-
- // An array which maps line numbers (counting from 0) to char offset into content
- private lazy val lineIndices: Array[Int] = {
-
- val xs = content.indices filter isLineBreak map (_ + 1) toArray
- val arr = new Array[Int](xs.length + 1)
- arr(0) = 0
- System.arraycopy(xs, 0, arr, 1, xs.length)
-
- arr
- }
- // A reverse map which also hunts down the right answer on non-exact lookups
- private class SparseReverser() {
- val revMap = Map(lineIndices.zipWithIndex: _*)
-
- def apply(x: Int): Int = revMap.get(x) match {
- case Some(res) => res
- case _ =>
- var candidate = x - 1
- while (!revMap.contains(candidate))
- candidate -= 1
-
- revMap(candidate)
- }
- }
- private lazy val lineIndicesRev = new SparseReverser()
-
- def lineToOffset(index : Int): Int = lineIndices(index)
- def offsetToLine(offset: Int): Int = lineIndicesRev(offset)
-
- */
-}
-
-/** 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, components flatMap (CompoundSourceFile stripSU _.content) toArray)
-
- /** Create an instance with the specified components and a generic name. */
- def this(components: BatchSourceFile*) = this("(virtual file)", components: _*)
-
- override def positionInUltimateSource(position: Position) = {
- if (!position.isDefined) super.positionInUltimateSource(position)
- else {
- var off = position.point
- 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) {
- 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 == SU)
- chars dropRight 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) =
- super.positionInUltimateSource(
- if (position.isDefined) new OffsetPosition(this, position.point)
- else position
- )
}
diff --git a/src/compiler/scala/tools/nsc/util/package.scala b/src/compiler/scala/tools/nsc/util/package.scala
new file mode 100644
index 0000000000..92d4eab54f
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/util/package.scala
@@ -0,0 +1,29 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+
+import java.io.{ OutputStream, PrintStream, ByteArrayOutputStream, PrintWriter, StringWriter }
+
+package object util {
+ /** Apply a function and return the passed value */
+ def returning[T](x: T)(f: T => Unit): T = { f(x) ; x }
+
+ /** Generate a string using a routine that wants to write on a stream. */
+ def stringFromWriter(writer: PrintWriter => Unit): String = {
+ val stringWriter = new StringWriter()
+ val stream = new NewLinePrintWriter(stringWriter)
+ writer(stream)
+ stream.close()
+ stringWriter.toString
+ }
+ def stringFromStream(stream: OutputStream => Unit): String = {
+ val bs = new ByteArrayOutputStream()
+ val ps = new PrintStream(bs)
+ stream(ps)
+ ps.close()
+ bs.toString()
+ }
+}