diff options
author | Paul Phillips <paulp@improving.org> | 2010-05-23 18:21:07 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-05-23 18:21:07 +0000 |
commit | 41d361a9d26cb550a11691a5b93fb7f3ab5223d3 (patch) | |
tree | 040c9276da673832295571af5585b60a846bf7c0 | |
parent | 3bad6d54b19c9ccc01a491b6483bb205d855381a (diff) | |
download | scala-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.
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") + ) + } + } +} |