diff options
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") + ) + } + } +} |