diff options
author | Guillaume Martres <smarter@ubuntu.com> | 2016-02-28 20:10:31 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-02-28 20:10:31 +0100 |
commit | ba67e556271156c11f5f313f68891bf18f7a51d2 (patch) | |
tree | 8d091cbf9789000ac72910a3735b7e01da92362c | |
parent | c2cb4694556731aaa243889d702afb5ac2b9e74a (diff) | |
parent | 7e7ee820df7647680d9aaf1ca991fe9718159097 (diff) | |
download | dotty-ba67e556271156c11f5f313f68891bf18f7a51d2.tar.gz dotty-ba67e556271156c11f5f313f68891bf18f7a51d2.tar.bz2 dotty-ba67e556271156c11f5f313f68891bf18f7a51d2.zip |
Merge pull request #1125 from dotty-staging/add/interface
Add a `dotty-interfaces` package
21 files changed, 419 insertions, 88 deletions
@@ -42,7 +42,8 @@ ReplMain=test.DottyRepl -# autodetecting the compiler jar. this is location where sbt 'packages' it +# autodetecting the compiler jars. this is the location where sbt 'packages' them +INTERFACES_JAR=$DOTTY_ROOT/interfaces/target/dotty-interfaces-$DOTTY_VERSION.jar MAIN_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION.jar TEST_JAR=$DOTTY_ROOT/target/scala-$SCALA_BINARY_VERSION/dotty_$SCALA_BINARY_VERSION-$DOTTY_VERSION-tests.jar DOTTY_JAR=$DOTTY_ROOT/dotty.jar @@ -62,7 +63,7 @@ function checkjar { echo "The required jar file was built successfully." fi else - NEW_FILES="$(find "$DOTTY_ROOT/$3" -iname "*.scala" -newer "$1")" + NEW_FILES="$(find "$DOTTY_ROOT/$3" \( -iname "*.scala" -o -iname "*.java" \) -newer "$1")" if [ ! -z "$NEW_FILES" ]; then echo "new files detected. rebuilding" @@ -74,6 +75,7 @@ function checkjar { fi } +checkjar $INTERFACES_JAR interfaces/package interfaces checkjar $MAIN_JAR package src checkjar $TEST_JAR test:package test @@ -198,6 +200,8 @@ classpathArgs () { else toolchain="$SCALA_LIBRARY_JAR:$SCALA_REFLECT_JAR:$SCALA_COMPILER_JAR:$JLINE_JAR" fi + bcpJars="$INTERFACES_JAR:$MAIN_JAR" + cpJars="$INTERFACES_JAR:$MAIN_JAR:$TEST_JAR" if [[ -n "$cygwin" ]]; then if [[ "$OS" = "Windows_NT" ]] && cygpath -m .>/dev/null 2>/dev/null ; then @@ -207,18 +211,18 @@ classpathArgs () { fi if [[ -n $bootcp ]]; then - boot_classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR")" - classpath="$(cygpath --path --$format "$MAIN_JAR:$TEST_JAR")" + boot_classpath="$(cygpath --path --$format "$toolchain:$bcpJars")" + classpath="$(cygpath --path --$format "$cpJars")" cpArgs="-Xbootclasspath/a:$boot_classpath -classpath $classpath" else - classpath="$(cygpath --path --$format "$toolchain:$MAIN_JAR:$TEST_JAR")" + classpath="$(cygpath --path --$format "$toolchain:$cpJars")" cpArgs="-classpath $classpath" fi else if [[ -n $bootcp ]]; then - cpArgs="-Xbootclasspath/a:$toolchain:$MAIN_JAR -classpath $MAIN_JAR:$TEST_JAR" + cpArgs="-Xbootclasspath/a:$toolchain:$bcpJars -classpath $cpJars" else - cpArgs="-classpath $toolchain:$MAIN_JAR:$TEST_JAR" + cpArgs="-classpath $toolchain:$cpJars" fi fi echo ${cpArgs} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/AbstractFile.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/AbstractFile.java new file mode 100644 index 000000000..a1b9cc5c7 --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/AbstractFile.java @@ -0,0 +1,24 @@ +package dotty.tools.dotc.interfaces; + +import java.io.File; +import java.util.Optional; + +/** An abstract file may either be a file on disk or a virtual file. + * + * Do not rely on the identity of instances of this class. + * + * User code should not implement this interface, but it may have to + * manipulate objects of this type. + */ +public interface AbstractFile { + /** The name of this file, note that two files may have the same name. */ + String name(); + + /** The path of this file, this might be a virtual path of an unspecified format. */ + String path(); + + /** If this is a real file on disk, a `java.io.File` that corresponds to this file. + * Otherwise, an empty `Optional`. + */ + Optional<File> jfile(); +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/CompilerCallback.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/CompilerCallback.java new file mode 100644 index 000000000..17d2f6bbd --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/CompilerCallback.java @@ -0,0 +1,28 @@ +package dotty.tools.dotc.interfaces; + +/** Set of callbacks called in response to events during the compilation process. + * + * You should implement this interface if you want to react to one or more of + * these events. + * + * @see the method `process` of `dotty.tools.dotc.Driver` for more information. + */ +public interface CompilerCallback { + /** Called when a class has been generated. + * + * @param source The source file corresponding to this class. + * Example: ./src/library/scala/collection/Seq.scala + * @param generatedClass The generated classfile for this class. + * Example: ./scala/collection/Seq$.class + * @param className The name of this class. + * Example: scala.collection.Seq$ + */ + default void onClassGenerated(SourceFile source, AbstractFile generatedClass, String className) {}; + + /** Called when every class for this file has been generated. + * + * @param source The source file. + * Example: ./src/library/scala/collection/Seq.scala + */ + default void onSourceCompiled(SourceFile source) {}; +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java new file mode 100644 index 000000000..436f9f8ea --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/Diagnostic.java @@ -0,0 +1,26 @@ +package dotty.tools.dotc.interfaces; + +import java.util.Optional; + +/** A diagnostic is a message emitted during the compilation process. + * + * It can either be an error, a warning or an information. + * + * User code should not implement this interface, but it may have to + * manipulate objects of this type. + */ +public interface Diagnostic { + public static final int ERROR = 2; + public static final int WARNING = 1; + public static final int INFO = 0; + + /** The message to report */ + String message(); + + /** Level of the diagnostic, can be either ERROR, WARNING or INFO */ + int level(); + + /** The position in a source file of the code that caused this diagnostic + * to be emitted. */ + Optional<SourcePosition> position(); +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/ReporterResult.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/ReporterResult.java new file mode 100644 index 000000000..63c5104df --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/ReporterResult.java @@ -0,0 +1,18 @@ +package dotty.tools.dotc.interfaces; + +/** Summary of the diagnostics emitted by a Reporter. + * + * User code should not implement this interface, but it may have to + * manipulate objects of this type. + */ +public interface ReporterResult { + /** Have we emitted any error ? */ + boolean hasErrors(); + /** Number of errors that have been emitted */ + int errorCount(); + + /** Have we emitted any warning ? */ + boolean hasWarnings(); + /** Number of warnings that have been emitted */ + int warningCount(); +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/SimpleReporter.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SimpleReporter.java new file mode 100644 index 000000000..e52537096 --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SimpleReporter.java @@ -0,0 +1,13 @@ +package dotty.tools.dotc.interfaces; + +/** Report errors, warnings and info messages during the compilation process + * + * You should implement this interface if you want to handle the diagnostics + * returned by the compiler yourself. + * + * @see the method `process` of `dotty.tools.dotc.Driver` for more information. + */ +public interface SimpleReporter { + /** Report a diagnostic. */ + void report(Diagnostic diag); +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourceFile.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourceFile.java new file mode 100644 index 000000000..5611d3885 --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourceFile.java @@ -0,0 +1,13 @@ +package dotty.tools.dotc.interfaces; + +import java.io.File; + +/** A source file. + * + * User code should not implement this interface, but it may have to + * manipulate objects of this type. + */ +public interface SourceFile extends AbstractFile { + /** The content of this file as seen by the compiler. */ + char[] content(); +} diff --git a/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourcePosition.java b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourcePosition.java new file mode 100644 index 000000000..c9b42d4ad --- /dev/null +++ b/interfaces/src/main/java/dotty/tools/dotc/interfaces/SourcePosition.java @@ -0,0 +1,44 @@ +package dotty.tools.dotc.interfaces; + +/** A position in a source file. + * + * A position is a range between a start offset and an end offset, as well as a + * point inside this range. + * + * As a convenience, we also provide methods that return the line and the column + * corresponding to each offset. + * + * User code should not implement this interface, but it may have to + * manipulate objects of this type. + */ +public interface SourcePosition { + /** Content of the line which contains the point */ + String lineContent(); + + /** Offset to the point */ + int point(); + /** Line number of the point, starting at 0 */ + int line(); + /** Column number of the point, starting at 0 */ + int column(); + + /** Offset to the range start */ + int start(); + /** Line number of the range start, starting at 0 */ + int startLine(); + /** Column number of the range start, starting at 0 */ + int startColumn(); + + /** Offset to the range end */ + int end(); + /** Line number of the range end, starting at 0 */ + int endLine(); + /** Column number of the range end, starting at 0 */ + int endColumn(); + + /** The source file corresponding to this position. + * The values returned by `point()`, `start()` and `end()` + * are indices in the array returned by `source().content()`. + */ + SourceFile source(); +} diff --git a/project/Build.scala b/project/Build.scala index 980c7ccb0..8b7238a09 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -35,7 +35,16 @@ object DottyBuild extends Build { ) } + lazy val `dotty-interfaces` = project.in(file("interfaces")). + settings( + // Do not append Scala versions to the generated artifacts + crossPaths := false, + // Do not depend on the Scala library + autoScalaLibrary := false + ) + lazy val dotty = project.in(file(".")). + dependsOn(`dotty-interfaces`). settings( // set sources to src/, tests to test/ and resources to resources/ scalaSource in Compile := baseDirectory.value / "src", @@ -105,7 +114,7 @@ object DottyBuild extends Build { parallelExecution in Test := false, // http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala - javaOptions <++= (managedClasspath in Runtime, packageBin in Compile) map { (attList, bin) => + javaOptions <++= (dependencyClasspath in Runtime, packageBin in Compile) map { (attList, bin) => // put the Scala {library, reflect} in the classpath val path = for { file <- attList.map(_.data) diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala index 2d444d3be..8cec93977 100644 --- a/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/src/dotty/tools/backend/jvm/GenBCode.scala @@ -13,6 +13,9 @@ import dotty.tools.dotc import dotty.tools.dotc.backend.jvm.DottyPrimitives import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.interfaces +import java.util.Optional + import scala.reflect.ClassTag import dotty.tools.dotc.core._ import Periods._ @@ -51,7 +54,17 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter var tree: Tree = _ - val sourceJFile: JFile = ctx.compilationUnit.source.file.file + val sourceFile = ctx.compilationUnit.source + + /** Convert a `scala.reflect.io.AbstractFile` into a + * `dotty.tools.dotc.interfaces.AbstractFile`. + */ + private[this] def convertAbstractFile(absfile: scala.reflect.io.AbstractFile): interfaces.AbstractFile = + new interfaces.AbstractFile { + override def name = absfile.name + override def path = absfile.path + override def jfile = Optional.ofNullable(absfile.file) + } final class PlainClassBuilder(cunit: CompilationUnit) extends SyncAndTryBuilder(cunit) @@ -307,7 +320,7 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter // Statistics.stopTimer(BackendStats.bcodeTimer, bcodeStart) if (ctx.compilerCallback != null) - ctx.compilerCallback.onSourceCompiled(sourceJFile) + ctx.compilerCallback.onSourceCompiled(sourceFile) /* TODO Bytecode can be verified (now that all classfiles have been written to disk) * @@ -373,10 +386,9 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter else getFileForClassfile(outFolder, jclassName, ".class") bytecodeWriter.writeClass(jclassName, jclassName, jclassBytes, outFile) - val outJFile = outFile.file val className = jclassName.replace('/', '.') if (ctx.compilerCallback != null) - ctx.compilerCallback.onClassGenerated(sourceJFile, outJFile, className) + ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(outFile), className) } catch { case e: FileConflictException => diff --git a/src/dotty/tools/dotc/CompilerCallback.scala b/src/dotty/tools/dotc/CompilerCallback.scala deleted file mode 100644 index e2f56430b..000000000 --- a/src/dotty/tools/dotc/CompilerCallback.scala +++ /dev/null @@ -1,42 +0,0 @@ -package dotty.tools.dotc - -import java.io.File - -/** This trait contains methods that can be overriden to execute code during the - * compilation process. - * - * NOTE: This trait is experimental and may be subject to arbitrary changes. - * - * Example usage: - * {{{ - * val args: Array[String] = ... - * val callback = new CompilerCallback { - * override def onClassGenerated(source: File, generatedClass: File, className: String) = - * println(s"onClassGenerated($source, $generatedClass, $className)") - * override def onSourceCompiled(source: File) = - * println(s"onSourceCompiled($source)") - * } - * dotty.tools.dotc.process(args, callback) - * // Or, if you have a custom root context `rootCtx`: - * dotty.tools.dotc.process(args, rootCtx.fresh.setCompilerCallback(callback)) - * }}} - */ -trait CompilerCallback { - /** Called when a class has been generated. - * - * @param source The source file corresponding to this class. - * Example: ./src/library/scala/collection/Seq.scala - * @param generatedClass The generated classfile for this class. - * Example: ./scala/collection/Seq$.class - * @param className The name of this class. - * Example: scala.collection.Seq$ - */ - def onClassGenerated(source: File, generatedClass: File, className: String): Unit = {} - - /** Called when every class for this file has been generated. - * - * @param source The source file. - * Example: ./src/library/scala/collection/Seq.scala - */ - def onSourceCompiled(source: File): Unit = {} -} diff --git a/src/dotty/tools/dotc/Driver.scala b/src/dotty/tools/dotc/Driver.scala index 5f613d67d..887274fa8 100644 --- a/src/dotty/tools/dotc/Driver.scala +++ b/src/dotty/tools/dotc/Driver.scala @@ -7,6 +7,12 @@ import util.DotClass import reporting._ import scala.util.control.NonFatal +/** Run the Dotty compiler. + * + * Extending this class lets you customize many aspect of the compilation + * process, but in most cases you only need to call [[process]] on the + * existing object [[Main]]. + */ abstract class Driver extends DotClass { val prompt = "\ndotc> " @@ -41,12 +47,35 @@ abstract class Driver extends DotClass { (fileNames, ctx) } + /** Entry point to the compiler that can be conveniently used with Java reflection. + * + * This entry point can easily be used without depending on the `dotty` package, + * you only need to depend on `dotty-interfaces` and call this method using + * reflection. This allows you to write code that will work against multiple + * versions of dotty without recompilation. + * + * The trade-off is that you can only pass a SimpleReporter to this method + * and not a normal Reporter which is more powerful. + * + * Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/InterfaceEntryPointTest.scala]] + * + * @param args Arguments to pass to the compiler. + * @param simple Used to log errors, warnings, and info messages. + * The default reporter is used if this is `null`. + * @param callback Used to execute custom code during the compilation + * process. No callbacks will be executed if this is `null`. + * @return + */ + final def process(args: Array[String], simple: interfaces.SimpleReporter, + callback: interfaces.CompilerCallback): interfaces.ReporterResult = { + val reporter = if (simple == null) null else Reporter.fromSimpleReporter(simple) + process(args, reporter, callback) + } /** Principal entry point to the compiler. - * Creates a new compiler instance and run it with arguments `args`. * - * The optional arguments of this method all have `null` as their default - * value, this makes it easier to call this method by reflection or from Java. + * Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/OtherEntryPointsTest.scala]] + * in method `runCompiler` * * @param args Arguments to pass to the compiler. * @param reporter Used to log errors, warnings, and info messages. @@ -57,7 +86,7 @@ abstract class Driver extends DotClass { * if compilation succeeded. */ final def process(args: Array[String], reporter: Reporter = null, - callback: CompilerCallback = null): Reporter = { + callback: interfaces.CompilerCallback = null): Reporter = { val ctx = initCtx.fresh if (reporter != null) ctx.setReporter(reporter) @@ -75,7 +104,7 @@ abstract class Driver extends DotClass { * with sbt. */ final def process(args: Array[String]): Reporter = - process(args, null, null) + process(args, null: Reporter, null: interfaces.CompilerCallback) /** Entry point to the compiler using a custom `Context`. * @@ -84,6 +113,9 @@ abstract class Driver extends DotClass { * the other overloads cannot be overriden, instead you * should override this one which they call internally. * + * Usage example: [[https://github.com/lampepfl/dotty/tree/master/test/test/OtherEntryPointsTest.scala]] + * in method `runCompilerWithContext` + * * @param args Arguments to pass to the compiler. * @param rootCtx The root Context to use. * @return The `Reporter` used. Use `Reporter#hasErrors` to check diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index c1132ce4b..f0537dffa 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -2,6 +2,7 @@ package dotty.tools package dotc package core +import interfaces.CompilerCallback import Decorators._ import Periods._ import Names._ diff --git a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala index 61678dee1..f35293d8d 100644 --- a/src/dotty/tools/dotc/reporting/ConsoleReporter.scala +++ b/src/dotty/tools/dotc/reporting/ConsoleReporter.scala @@ -22,7 +22,7 @@ class ConsoleReporter( protected def ErrorLimit = 100 def printSourceLine(pos: SourcePosition) = - printMessage(pos.lineContents.stripLineEnd) + printMessage(pos.lineContent.stripLineEnd) def printColumnMarker(pos: SourcePosition) = if (pos.exists) { printMessage(" " * pos.column + "^") } @@ -42,15 +42,15 @@ class ConsoleReporter( override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { case d: Error => - printMessageAndPos(s"error: ${d.msg}", d.pos) + printMessageAndPos(s"error: ${d.message}", d.pos) if (ctx.settings.prompt.value) displayPrompt() case d: ConditionalWarning if !d.enablingOption.value => case d: MigrationWarning => - printMessageAndPos(s"migration warning: ${d.msg}", d.pos) + printMessageAndPos(s"migration warning: ${d.message}", d.pos) case d: Warning => - printMessageAndPos(s"warning: ${d.msg}", d.pos) + printMessageAndPos(s"warning: ${d.message}", d.pos) case _ => - printMessageAndPos(d.msg, d.pos) + printMessageAndPos(d.message, d.pos) } def displayPrompt(): Unit = { diff --git a/src/dotty/tools/dotc/reporting/Diagnostic.scala b/src/dotty/tools/dotc/reporting/Diagnostic.scala index c57be3d26..bcf55e993 100644 --- a/src/dotty/tools/dotc/reporting/Diagnostic.scala +++ b/src/dotty/tools/dotc/reporting/Diagnostic.scala @@ -4,24 +4,24 @@ package reporting import util.SourcePosition -object Diagnostic { - - // Error levels - val ERROR = 2 - val WARNING = 1 - val INFO = 0 +import java.util.Optional +object Diagnostic { val nonSensicalStartTag = "<nonsensical>" val nonSensicalEndTag = "</nonsensical>" } -class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int) extends Exception { +class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int) + extends Exception with interfaces.Diagnostic { import Diagnostic._ private var myMsg: String = null private var myIsNonSensical: Boolean = false + override def position: Optional[interfaces.SourcePosition] = + if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty() + /** The message to report */ - def msg: String = { + def message: String = { if (myMsg == null) { myMsg = msgFn if (myMsg.contains(nonSensicalStartTag)) { @@ -40,8 +40,8 @@ class Diagnostic(msgFn: => String, val pos: SourcePosition, val level: Int) exte * they look weird and are normally follow-up errors to something that * was diagnosed before. */ - def isNonSensical = { msg; myIsNonSensical } + def isNonSensical = { message; myIsNonSensical } - override def toString = s"$getClass at $pos: $msg" - override def getMessage() = msg + override def toString = s"$getClass at $pos: $message" + override def getMessage() = message } diff --git a/src/dotty/tools/dotc/reporting/Reporter.scala b/src/dotty/tools/dotc/reporting/Reporter.scala index f4eb551a1..8236f93ef 100644 --- a/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/src/dotty/tools/dotc/reporting/Reporter.scala @@ -11,7 +11,7 @@ import config.Settings.Setting import config.Printers import java.lang.System.currentTimeMillis import typer.Mode -import Diagnostic.{ERROR, WARNING, INFO} +import interfaces.Diagnostic.{ERROR, WARNING, INFO} object Reporter { class Error(msgFn: => String, pos: SourcePosition) extends Diagnostic(msgFn, pos, ERROR) @@ -33,6 +33,16 @@ object Reporter { class MigrationWarning(msgFn: => String, pos: SourcePosition) extends ConditionalWarning(msgFn, pos) { def enablingOption(implicit ctx: Context) = ctx.settings.migration } + + /** Convert a SimpleReporter into a real Reporter */ + def fromSimpleReporter(simple: interfaces.SimpleReporter): Reporter = + new Reporter with UniqueMessagePositions with HideNonSensicalMessages { + override def doReport(d: Diagnostic)(implicit ctx: Context): Unit = d match { + case d: ConditionalWarning if !d.enablingOption.value => + case _ => + simple.report(d) + } + } } import Reporter._ @@ -157,7 +167,7 @@ trait Reporting { this: Context => * This interface provides methods to issue information, warning and * error messages. */ -abstract class Reporter { +abstract class Reporter extends interfaces.ReporterResult { /** Report a diagnostic */ def doReport(d: Diagnostic)(implicit ctx: Context): Unit diff --git a/src/dotty/tools/dotc/reporting/StoreReporter.scala b/src/dotty/tools/dotc/reporting/StoreReporter.scala index d19e8cf5c..954bff88e 100644 --- a/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -15,7 +15,7 @@ class StoreReporter(outer: Reporter) extends Reporter { private var infos: mutable.ListBuffer[Diagnostic] = null def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { - typr.println(s">>>> StoredError: ${d.msg}") // !!! DEBUG + typr.println(s">>>> StoredError: ${d.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer infos += d } diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index da2e54132..6b547203e 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -11,6 +11,8 @@ import Chars._ import ScriptSourceFile._ import Positions._ +import java.util.Optional + object ScriptSourceFile { @sharable private val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) private val headerStarts = List("#!", "::#!") @@ -32,7 +34,7 @@ object ScriptSourceFile { } } -case class SourceFile(file: AbstractFile, content: Array[Char]) { +case class SourceFile(file: AbstractFile, content: Array[Char]) extends interfaces.SourceFile { def this(_file: AbstractFile) = this(_file, _file.toCharArray) def this(sourceName: String, cs: Seq[Char]) = this(new VirtualFile(sourceName), cs.toArray) @@ -41,6 +43,10 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) { /** Tab increment; can be overridden */ def tabInc = 8 + override def name = file.name + override def path = file.path + override def jfile = Optional.ofNullable(file.file) + override def equals(that : Any) = that match { case that : SourceFile => file.path == that.file.path && start == that.start case _ => false @@ -113,8 +119,8 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) { def nextLine(offset: Int): Int = lineToOffset(offsetToLine(offset) + 1 min lineIndices.length - 1) - /** The contents of the line containing position `offset` */ - def lineContents(offset: Int): String = + /** The content of the line containing position `offset` */ + def lineContent(offset: Int): String = content.slice(startOfLine(offset), nextLine(offset)).mkString /** The column corresponding to `offset`, starting at 0 */ diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index 9e02841f2..0b2b2aa0b 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -1,23 +1,29 @@ package dotty.tools -package dotc.util +package dotc +package util import Positions.{Position, NoPosition} /** A source position is comprised of a position in a source file */ -case class SourcePosition(source: SourceFile, pos: Position) { - def point: Int = pos.point - def start: Int = pos.start - def end: Int = pos.end +case class SourcePosition(source: SourceFile, pos: Position) extends interfaces.SourcePosition { def exists = pos.exists - def lineContents: String = source.lineContents(point) + def lineContent: String = source.lineContent(point) + def point: Int = pos.point /** The line of the position, starting at 0 */ def line: Int = source.offsetToLine(point) - /** The column of the position, starting at 0 */ def column: Int = source.column(point) + def start: Int = pos.start + def startLine: Int = source.offsetToLine(start) + def startColumn: Int = source.column(start) + + def end: Int = pos.end + def endLine: Int = source.offsetToLine(end) + def endColumn: Int = source.column(end) + override def toString = if (source.exists) s"${source.file}:${line + 1}" else s"(no source file, offset = ${pos.point})" diff --git a/test/test/InterfaceEntryPointTest.scala b/test/test/InterfaceEntryPointTest.scala new file mode 100644 index 000000000..b193b5f64 --- /dev/null +++ b/test/test/InterfaceEntryPointTest.scala @@ -0,0 +1,61 @@ +package test + +import org.junit.Test +import org.junit.Assert._ +import dotty.tools.dotc.interfaces._ +import scala.collection.mutable.ListBuffer + +/** Test that demonstrates how to use dotty-interfaces + * + * This test requires: + * - dotty-interfaces to be present at compile-time + * - dotty-interfaces and dotty to be present at run-time + * + * Since the ABI of dotty-interfaces is stable, this means that users can write + * code that works with multiple versions of dotty without recompilation. + * + * @see [[OtherEntryPointsTest]] + */ +class InterfaceEntryPointTest { + @Test def runCompilerFromInterface = { + val sources = List("./tests/pos/HelloWorld.scala") + val args = sources ++ List("-d", "./out/") + + val mainClass = Class.forName("dotty.tools.dotc.Main") + val process = mainClass.getMethod("process", + classOf[Array[String]], classOf[SimpleReporter], classOf[CompilerCallback]) + + val reporter = new CustomSimpleReporter + val callback = new CustomCompilerCallback + + // Run the compiler by calling dotty.tools.dotc.Main.process + process.invoke(null, args.toArray, reporter, callback) + + assertEquals("Number of errors", 0, reporter.errorCount) + assertEquals("Number of warnings", 0, reporter.warningCount) + assertEquals("Compiled sources", sources, callback.paths) + } + + private class CustomSimpleReporter extends SimpleReporter { + var errorCount = 0 + var warningCount = 0 + + def report(diag: Diagnostic): Unit = { + if (diag.level == Diagnostic.ERROR) + errorCount += 1 + if (diag.level == Diagnostic.WARNING) + warningCount += 1 + } + } + + private class CustomCompilerCallback extends CompilerCallback { + private val pathsBuffer = new ListBuffer[String] + def paths = pathsBuffer.toList + + override def onSourceCompiled(source: SourceFile): Unit = { + if (source.jfile.isPresent) + pathsBuffer += source.jfile.get.getPath + } + } +} + diff --git a/test/test/OtherEntryPointsTest.scala b/test/test/OtherEntryPointsTest.scala new file mode 100644 index 000000000..0186b357b --- /dev/null +++ b/test/test/OtherEntryPointsTest.scala @@ -0,0 +1,66 @@ +package test + +import org.junit.Test +import org.junit.Assert._ +import dotty.tools.dotc.Main +import dotty.tools.dotc.interfaces.{CompilerCallback, SourceFile} +import dotty.tools.dotc.reporting._ +import dotty.tools.dotc.core.Contexts._ +import java.io.File +import scala.collection.mutable.ListBuffer + +/** Test the compiler entry points that depend on dotty + * + * This file also serve as an example for using [[dotty.tools.dotc.Driver#process]]. + * + * @see [[InterfaceEntryPointTest]] + */ +class OtherEntryPointsTest { + @Test def runCompiler = { + val sources = List("./tests/pos/HelloWorld.scala") + val args = sources ++ List("-d", "./out/") + + val reporter = new CustomReporter + val callback = new CustomCompilerCallback + + Main.process(args.toArray, reporter, callback) + + assertEquals("Number of errors", false, reporter.hasErrors) + assertEquals("Number of warnings", false, reporter.hasWarnings) + assertEquals("Compiled sources", sources, callback.paths) + } + + @Test def runCompilerWithContext = { + val sources = List("./tests/pos/HelloWorld.scala") + val args = sources ++ List("-d", "./out/") + + val reporter = new CustomReporter + val callback = new CustomCompilerCallback + val context = (new ContextBase).initialCtx.fresh + .setReporter(reporter) + .setCompilerCallback(callback) + + Main.process(args.toArray, context) + + assertEquals("Number of errors", false, reporter.hasErrors) + assertEquals("Number of warnings", false, reporter.hasWarnings) + assertEquals("Compiled sources", sources, callback.paths) + } + + private class CustomReporter extends Reporter + with UniqueMessagePositions + with HideNonSensicalMessages { + def doReport(d: Diagnostic)(implicit ctx: Context): Unit = { + } + } + + private class CustomCompilerCallback extends CompilerCallback { + private val pathsBuffer = new ListBuffer[String] + def paths = pathsBuffer.toList + + override def onSourceCompiled(source: SourceFile): Unit = { + if (source.jfile.isPresent) + pathsBuffer += source.jfile.get.getPath + } + } +} |