summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--src/partest/scala/tools/partest/nest/ConsoleRunner.scala1
-rw-r--r--test/files/run/script-positions.scala86
16 files changed, 262 insertions, 245 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()
+ }
+}
diff --git a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
index eae79f23af..33f575c0a0 100644
--- a/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
+++ b/src/partest/scala/tools/partest/nest/ConsoleRunner.scala
@@ -15,7 +15,6 @@ import RunnerUtils._
import scala.tools.nsc.Properties.{ versionMsg, setProp }
import scala.tools.nsc.util.CommandLineParser
import scala.tools.nsc.io
-import scala.tools.nsc.interpreter.returning
import io.{ Path, Process }
class ConsoleRunner extends DirectRunner {
diff --git a/test/files/run/script-positions.scala b/test/files/run/script-positions.scala
new file mode 100644
index 0000000000..6982ed8440
--- /dev/null
+++ b/test/files/run/script-positions.scala
@@ -0,0 +1,86 @@
+import scala.tools.nsc._
+import util.stringFromStream
+
+// Testing "scripts" without the platform delights which accompany actual scripts.
+object Scripts {
+
+ val test1 =
+"""#!/bin/sh
+ exec scala $0 $@
+!#
+
+println("statement 1")
+println("statement 2".thisisborked)
+println("statement 3")
+"""
+
+ val output1 =
+"""thisisborked.scala:6: error: value thisisborked is not a member of java.lang.String
+println("statement 2".thisisborked)
+ ^
+one error found"""
+ val test2 =
+"""#!scala
+// foo
+// bar
+!#
+
+val x = "line 6"
+val y = "line 7"
+val z "line 8""""
+
+ val output2 =
+"""bob.scala:8: error: '=' expected but string literal found.
+val z "line 8"
+ ^
+bob.scala:8: error: illegal start of simple expression
+val z "line 8"
+ ^
+two errors found"""
+}
+
+object Test {
+ import Scripts._
+
+ def settings = new GenericRunnerSettings(println _)
+ settings.nocompdaemon.value = true
+
+ def runScript(code: String): String =
+ stringFromStream(stream =>
+ Console.withOut(stream) {
+ Console.withErr(stream) {
+ ScriptRunner.runCommand(settings, code, Nil)
+ }
+ }
+ )
+
+ val tests: List[(String, String)] = List(
+ test1 -> output1,
+ test2 -> output2
+ )
+ // def lines(s: String) = s split """\r\n|\r|\n""" toList
+ def lines(s: String) = s split "\\n" toList
+
+ // strip the random temp filename from error msgs
+ def stripFilename(s: String) = (s indexOf ".scala:") match {
+ case -1 => s
+ case idx => s drop (idx + 7)
+ }
+ def toLines(text: String) = lines(text) map stripFilename
+
+ def main(args: Array[String]): Unit = {
+ for ((code, expected) <- tests) {
+ val out = toLines(runScript(code))
+ val exp = toLines(expected)
+ val nomatch = out zip exp filter { case (x, y) => x != y }
+ val success = out.size == exp.size && nomatch.isEmpty
+
+ assert(
+ success,
+ "Output doesn't match expected:\n" +
+ "Expected:\n" + expected +
+ "Actual:\n" + out.mkString("\n")
+ )
+ }
+ }
+}