diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CodeWrapper.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CodeWrapper.scala new file mode 100644 index 000000000..c6ae524d3 --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CodeWrapper.scala @@ -0,0 +1,40 @@ +package ammonite.compiler.iface + +import ammonite.util.{Imports, Name} +import ammonite.util.Util.CodeSource + +abstract class CodeWrapper { + def wrapperPath: Seq[Name] = Nil + def apply( + code: String, + source: CodeSource, + imports: Imports, + printCode: String, + indexedWrapper: Name, + extraCode: String + ): (String, String, Int) + + def wrapCode(codeSource: CodeSource, + indexedWrapperName: Name, + code: String, + printCode: String, + imports: Imports, + extraCode: String, + markScript: Boolean) = { + + //we need to normalize topWrapper and bottomWrapper in order to ensure + //the snippets always use the platform-specific newLine + val extraCode0 = + if (markScript) extraCode + "/**/" + else extraCode + val (topWrapper, bottomWrapper, userCodeNestingLevel) = + apply(code, codeSource, imports, printCode, indexedWrapperName, extraCode0) + val (topWrapper0, bottomWrapper0) = + if (markScript) (topWrapper + "/**/ /**/" + bottomWrapper) + else (topWrapper, bottomWrapper) + val importsLen = topWrapper0.length + + (topWrapper0 + code + bottomWrapper0, importsLen, userCodeNestingLevel) + } + +} diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Compiler.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Compiler.scala new file mode 100644 index 000000000..d2207255d --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Compiler.scala @@ -0,0 +1,28 @@ +package ammonite.compiler.iface + +import ammonite.util.{Imports, Printer} + +abstract class Compiler { + + def compile( + src: Array[Byte], + printer: Printer, + importsLen: Int, + userCodeNestingLevel: Int, + fileName: String + ): Option[Compiler.Output] + + def preprocessor(fileName: String, markGeneratedSections: Boolean = false): Preprocessor + +} + +object Compiler { + + case class Output( + classFiles: Vector[(String, Array[Byte])], + imports: Imports, + usedEarlierDefinitions: Option[Seq[String]] + ) + +} + diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala new file mode 100644 index 000000000..eecaaeb47 --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerBuilder.scala @@ -0,0 +1,42 @@ +package ammonite.compiler.iface + +import java.net.URL +import java.nio.file.Path + +import ammonite.util.Frame + +abstract class CompilerBuilder { + + def newManager( + rtCacheDir: Option[Path], + headFrame: => Frame, + dependencyCompleter: => Option[String => (Int, Seq[String])], + whiteList: Set[Seq[String]], + initialClassLoader: ClassLoader + ): CompilerLifecycleManager + + def create( + initialClassPath: Seq[URL], + classPath: Seq[URL], + dynamicClassPath: Seq[(String, Array[Byte])], + evalClassLoader: ClassLoader, + pluginClassLoader: ClassLoader, + reporter: Option[CompilerBuilder.Message => Unit], + settings: Seq[String], + classPathWhiteList: Set[Seq[String]], + lineNumberModifier: Boolean + ): Compiler + + def scalaVersion: String +} + +object CompilerBuilder { + + case class Message( + severity: String, + start: Int, + end: Int, + message: String + ) + +} diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerLifecycleManager.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerLifecycleManager.scala new file mode 100644 index 000000000..382f12eea --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/CompilerLifecycleManager.scala @@ -0,0 +1,33 @@ +package ammonite.compiler.iface + +import java.nio.file.Path + +import ammonite.util.{Frame, Printer} +import ammonite.util.Util.ClassFiles + +abstract class CompilerLifecycleManager { + def compiler: Compiler + def compilationCount: Int + + def preprocess(fileName: String): Preprocessor + + def scalaVersion: String + + def init(force: Boolean = false): Unit + + def complete( + offset: Int, + previousImports: String, + snippet: String + ): (Int, Seq[String], Seq[String]) + + def compileClass( + processed: Preprocessor.Output, + printer: Printer, + fileName: String + ): Option[Compiler.Output] + + def addToClasspath(classFiles: ClassFiles): Unit + + def shutdownPressy(): Unit +} diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Parser.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Parser.scala new file mode 100644 index 000000000..1077b4098 --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Parser.scala @@ -0,0 +1,76 @@ +package ammonite.compiler.iface + +import ammonite.util.ImportTree +import ammonite.util.Util.CodeSource + +abstract class Parser { + + def split( + code: String, + ignoreIncomplete: Boolean = true, + fileName: String = "(console)" + ): Option[Either[String, Seq[String]]] + + final def parseImportHooks( + source: CodeSource, + stmts: Seq[String] + ): (Seq[String], Seq[ImportTree]) = + parseImportHooksWithIndices(source, stmts.map((0, _))) + def parseImportHooksWithIndices( + source: CodeSource, + stmts: Seq[(Int, String)] + ): (Seq[String], Seq[ImportTree]) + + /** + * Splits up a script file into its constituent blocks, each of which + * is a tuple of (leading-whitespace, statements). Leading whitespace + * is returned separately so we can later manipulate the statements e.g. + * by adding `val res2 = ` without the whitespace getting in the way + */ + def splitScript( + rawCode: String, + fileName: String + ): Either[String, IndexedSeq[(String, Seq[String])]] + + def scriptBlocksWithStartIndices( + rawCode: String, + fileName: String + ): Either[Parser.ScriptSplittingError, Seq[Parser.ScriptBlock]] + + def defaultHighlight(buffer: Vector[Char], + comment: fansi.Attrs, + `type`: fansi.Attrs, + literal: fansi.Attrs, + keyword: fansi.Attrs, + reset: fansi.Attrs): Vector[Char] + + def isObjDef(code: String): Boolean +} + +object Parser { + + case class ParsedImportHooks( + hookStatements: Seq[String], + importTrees: Seq[ImportTree] + ) + + case class ScriptBlock( + startIndex: Int, + ncomment: String, + codeWithStartIndices: Seq[(Int, String)] + ) + + object ScriptBlock { + def apply( + ncomment: String, + codeWithStartIndices: Seq[(Int, String)] + ): ScriptBlock = + ScriptBlock(0, ncomment, codeWithStartIndices) + } + + class ScriptSplittingError( + message: String, + val index: Int = -1, + val expected: String = "" + ) extends Exception(message) +} diff --git a/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Preprocessor.scala b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Preprocessor.scala new file mode 100644 index 000000000..0c7843b32 --- /dev/null +++ b/amm/compiler/interface/src/main/scala/ammonite/compiler/iface/Preprocessor.scala @@ -0,0 +1,50 @@ +package ammonite.compiler.iface + +import ammonite.util.{Imports, Name, Res} +import ammonite.util.Util.CodeSource + +/** + * Responsible for all scala-source-code-munging that happens within the + * Ammonite REPL. + * + * Performs several tasks: + * + * - Takes top-level Scala expressions and assigns them to `res{1, 2, 3, ...}` + * values so they can be accessed later in the REPL + * + * - Wraps the code snippet with an wrapper `object` since Scala doesn't allow + * top-level expressions + * + * - Mangles imports from our [[ammonite.util.ImportData]] data structure into a source + * String + * + * - Combines all of these into a complete compilation unit ready to feed into + * the Scala compiler + */ +abstract class Preprocessor { + + def transform( + stmts: Seq[String], + resultIndex: String, + leadingSpaces: String, + codeSource: CodeSource, + indexedWrapperName: Name, + imports: Imports, + printerTemplate: String => String, + extraCode: String, + skipEmpty: Boolean, + markScript: Boolean, + codeWrapper: CodeWrapper + ): Res[Preprocessor.Output] + +} + +object Preprocessor { + + case class Output( + code: String, + prefixCharLength: Int, + userCodeNestingLevel: Int + ) + +} diff --git a/amm/interp/src/main/scala-2.12.0_12/ammonite/interp/CompilerCompatibility.scala b/amm/compiler/src/main/scala-2.12.0_12/ammonite/compiler/CompilerCompatibility.scala similarity index 96% rename from amm/interp/src/main/scala-2.12.0_12/ammonite/interp/CompilerCompatibility.scala rename to amm/compiler/src/main/scala-2.12.0_12/ammonite/compiler/CompilerCompatibility.scala index d07a502b5..1a109e902 100644 --- a/amm/interp/src/main/scala-2.12.0_12/ammonite/interp/CompilerCompatibility.scala +++ b/amm/compiler/src/main/scala-2.12.0_12/ammonite/compiler/CompilerCompatibility.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala-2.12.0_8/ammonite/interp/ExtraCompilerCompatibility.scala b/amm/compiler/src/main/scala-2.12.0_8/ammonite/compiler/ExtraCompilerCompatibility.scala similarity index 92% rename from amm/interp/src/main/scala-2.12.0_8/ammonite/interp/ExtraCompilerCompatibility.scala rename to amm/compiler/src/main/scala-2.12.0_8/ammonite/compiler/ExtraCompilerCompatibility.scala index ff2a3772e..6d58f3122 100644 --- a/amm/interp/src/main/scala-2.12.0_8/ammonite/interp/ExtraCompilerCompatibility.scala +++ b/amm/compiler/src/main/scala-2.12.0_8/ammonite/compiler/ExtraCompilerCompatibility.scala @@ -1,4 +1,4 @@ -package ammonite.interp +package ammonite.compiler import scala.reflect.io.FileZipArchive import scala.tools.nsc.Settings diff --git a/amm/interp/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala b/amm/compiler/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala similarity index 95% rename from amm/interp/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala rename to amm/compiler/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala index 65bb52110..3efa67977 100644 --- a/amm/interp/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala +++ b/amm/compiler/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala @@ -3,7 +3,7 @@ package scala.tools.nsc import java.io.File import java.net.URL -import ammonite.interp.internal.CustomURLZipArchive +import ammonite.compiler.internal.CustomURLZipArchive import scala.reflect.io.AbstractFile import scala.tools.nsc.classpath.FileUtils.AbstractFileOps diff --git a/amm/interp/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala b/amm/compiler/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala similarity index 100% rename from amm/interp/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala rename to amm/compiler/src/main/scala-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala diff --git a/amm/interp/src/main/scala-2.12.13+/ammonite/interp/CompilerCompatibility.scala b/amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/CompilerCompatibility.scala similarity index 96% rename from amm/interp/src/main/scala-2.12.13+/ammonite/interp/CompilerCompatibility.scala rename to amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/CompilerCompatibility.scala index 85f7458e5..c36762350 100644 --- a/amm/interp/src/main/scala-2.12.13+/ammonite/interp/CompilerCompatibility.scala +++ b/amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/CompilerCompatibility.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala-2.12.13+/ammonite/interp/MakeReporter.scala b/amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/MakeReporter.scala similarity index 95% rename from amm/interp/src/main/scala-2.12.13+/ammonite/interp/MakeReporter.scala rename to amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/MakeReporter.scala index 42580afe5..3b9fbf5f2 100644 --- a/amm/interp/src/main/scala-2.12.13+/ammonite/interp/MakeReporter.scala +++ b/amm/compiler/src/main/scala-2.12.13+/ammonite/compiler/MakeReporter.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala-2.12.9+/ammonite/interp/ExtraCompilerCompatibility.scala b/amm/compiler/src/main/scala-2.12.9+/ammonite/compiler/ExtraCompilerCompatibility.scala similarity index 93% rename from amm/interp/src/main/scala-2.12.9+/ammonite/interp/ExtraCompilerCompatibility.scala rename to amm/compiler/src/main/scala-2.12.9+/ammonite/compiler/ExtraCompilerCompatibility.scala index 9ba9ff394..1f79dbffe 100644 --- a/amm/interp/src/main/scala-2.12.9+/ammonite/interp/ExtraCompilerCompatibility.scala +++ b/amm/compiler/src/main/scala-2.12.9+/ammonite/compiler/ExtraCompilerCompatibility.scala @@ -1,4 +1,4 @@ -package ammonite.interp +package ammonite.compiler import scala.reflect.io.FileZipArchive import scala.tools.nsc.Settings diff --git a/amm/interp/src/main/scala-2.13.1+/ammonite/interp/MakeReporter.scala b/amm/compiler/src/main/scala-2.13.1+/ammonite/compiler/MakeReporter.scala similarity index 95% rename from amm/interp/src/main/scala-2.13.1+/ammonite/interp/MakeReporter.scala rename to amm/compiler/src/main/scala-2.13.1+/ammonite/compiler/MakeReporter.scala index 42580afe5..3b9fbf5f2 100644 --- a/amm/interp/src/main/scala-2.13.1+/ammonite/interp/MakeReporter.scala +++ b/amm/compiler/src/main/scala-2.13.1+/ammonite/compiler/MakeReporter.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala-2.13/ammonite/interp/CompilerCompatibility.scala b/amm/compiler/src/main/scala-2.13/ammonite/compiler/CompilerCompatibility.scala similarity index 97% rename from amm/interp/src/main/scala-2.13/ammonite/interp/CompilerCompatibility.scala rename to amm/compiler/src/main/scala-2.13/ammonite/compiler/CompilerCompatibility.scala index 46bfd1d87..0e36feaf5 100644 --- a/amm/interp/src/main/scala-2.13/ammonite/interp/CompilerCompatibility.scala +++ b/amm/compiler/src/main/scala-2.13/ammonite/compiler/CompilerCompatibility.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala b/amm/compiler/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala similarity index 94% rename from amm/interp/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala rename to amm/compiler/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala index 52e38fc6a..d79fbe978 100644 --- a/amm/interp/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala +++ b/amm/compiler/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/AmmClassPath.scala @@ -3,7 +3,7 @@ package scala.tools.nsc import java.io.File import java.net.URL -import ammonite.interp.internal.CustomURLZipArchive +import ammonite.compiler.internal.CustomURLZipArchive import scala.reflect.io.AbstractFile import scala.tools.nsc.classpath.FileUtils.AbstractFileOps diff --git a/amm/interp/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala b/amm/compiler/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala similarity index 100% rename from amm/interp/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala rename to amm/compiler/src/main/scala-not-2.12.10-2.13.1+/scala/tools/nsc/WhiteListClasspath.scala diff --git a/amm/interp/src/main/scala-not-2.12.13+-2.13.1+/ammonite/interp/MakeReporter.scala b/amm/compiler/src/main/scala-not-2.12.13+-2.13.1+/ammonite/compiler/MakeReporter.scala similarity index 95% rename from amm/interp/src/main/scala-not-2.12.13+-2.13.1+/ammonite/interp/MakeReporter.scala rename to amm/compiler/src/main/scala-not-2.12.13+-2.13.1+/ammonite/compiler/MakeReporter.scala index 466618ab1..f9775817b 100644 --- a/amm/interp/src/main/scala-not-2.12.13+-2.13.1+/ammonite/interp/MakeReporter.scala +++ b/amm/compiler/src/main/scala-not-2.12.13+-2.13.1+/ammonite/compiler/MakeReporter.scala @@ -1,6 +1,6 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime.Classpath +import ammonite.util.Classpath import scala.reflect.internal.util.Position import scala.reflect.io.FileZipArchive diff --git a/amm/interp/src/main/scala/ammonite/interp/AmmonitePlugin.scala b/amm/compiler/src/main/scala/ammonite/compiler/AmmonitePlugin.scala similarity index 99% rename from amm/interp/src/main/scala/ammonite/interp/AmmonitePlugin.scala rename to amm/compiler/src/main/scala/ammonite/compiler/AmmonitePlugin.scala index c3596f18e..5fb543b71 100644 --- a/amm/interp/src/main/scala/ammonite/interp/AmmonitePlugin.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/AmmonitePlugin.scala @@ -1,4 +1,4 @@ -package ammonite.interp +package ammonite.compiler import ammonite.util.{ImportData, Name, Util} diff --git a/amm/interp/src/main/scala/ammonite/interp/CodeClassWrapper.scala b/amm/compiler/src/main/scala/ammonite/compiler/CodeClassWrapper.scala similarity index 98% rename from amm/interp/src/main/scala/ammonite/interp/CodeClassWrapper.scala rename to amm/compiler/src/main/scala/ammonite/compiler/CodeClassWrapper.scala index 10c0228dc..9ae7bfe91 100644 --- a/amm/interp/src/main/scala/ammonite/interp/CodeClassWrapper.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/CodeClassWrapper.scala @@ -1,5 +1,6 @@ -package ammonite.interp +package ammonite.compiler +import ammonite.compiler.iface.CodeWrapper import ammonite.util._ import ammonite.util.Util.{CodeSource, newLine, normalizeNewlines} diff --git a/amm/interp/src/main/scala/ammonite/interp/Compiler.scala b/amm/compiler/src/main/scala/ammonite/compiler/Compiler.scala similarity index 85% rename from amm/interp/src/main/scala/ammonite/interp/Compiler.scala rename to amm/compiler/src/main/scala/ammonite/compiler/Compiler.scala index 01bd2f229..efa716441 100644 --- a/amm/interp/src/main/scala/ammonite/interp/Compiler.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/Compiler.scala @@ -1,11 +1,10 @@ -package ammonite.interp +package ammonite.compiler import java.io.OutputStream -import java.nio.charset.StandardCharsets -import ammonite.runtime.{Classpath, Evaluator} -import ammonite.util.{ImportData, Imports, Name, Printer} +import ammonite.compiler.iface.{Compiler => ICompiler, Preprocessor} +import ammonite.util.{Classpath, ImportData, Imports, Printer} import ammonite.util.Util.newLine import scala.collection.mutable @@ -22,7 +21,6 @@ import scala.tools.nsc.classpath.{ import scala.tools.nsc.{CustomZipAndJarFileLookupFactory, Global, Settings} import scala.tools.nsc.interactive.Response import scala.tools.nsc.plugins.Plugin -import scala.util.Try /** @@ -36,19 +34,8 @@ import scala.util.Try * classfile per source-string (e.g. inner classes, or lambdas). Also lets * you query source strings using an in-built presentation compiler */ -trait Compiler{ +trait Compiler extends ICompiler { def compiler: nsc.Global - def compile(src: Array[Byte], - printer: Printer, - importsLen0: Int, - userCodeNestingLevel: Int, - fileName: String): Option[Compiler.Output] - - def search(name: scala.reflect.runtime.universe.Type): Option[String] - /** - * Either the statements that were parsed or the error message - */ - def parse(fileName: String, line: String): Either[String, Seq[Global#Tree]] var importsLen = 0 var userCodeNestingLevel = -1 @@ -81,15 +68,6 @@ object Compiler{ // should catch this and return before getting here case Nil => ??? } - /** - * If the Option is None, it means compilation failed - * Otherwise it's a Traversable of (filename, bytes) tuples - */ - case class Output( - classFiles: Vector[(String, Array[Byte])], - imports: Imports, - usedEarlierDefinitions: Option[Seq[String]] - ) /** * Converts a bunch of bytes into Scalac's weird VirtualFile class @@ -141,6 +119,9 @@ object Compiler{ initialClassPath: Seq[java.net.URL], lineNumberModifier: Boolean = true): Compiler = new Compiler{ + def preprocessor(fileName: String, markGeneratedSections: Boolean): Preprocessor = + new DefaultPreprocessor(parse(fileName, _), markGeneratedSections) + if(sys.env.contains("DIE"))??? val PluginXML = "scalac-plugin.xml" lazy val plugins0 = { @@ -236,7 +217,7 @@ object Compiler{ evalClassloader, createPlugins = g => { List( - new ammonite.interp.AmmonitePlugin( + new ammonite.compiler.AmmonitePlugin( g, lastImports = _, uses => usedEarlierDefinitions = Some(uses), @@ -277,40 +258,6 @@ object Compiler{ (vd, reporter, scalac) } - def search(target: scala.reflect.runtime.universe.Type) = { - def resolve(path: String*): compiler.Symbol = { - var curr = path.toList - var start: compiler.Symbol = compiler.RootClass - while(curr != Nil){ - val head :: rest = curr - start = start.typeSignature.member(compiler.newTermName(head)) - curr = rest - } - start - } - var thingsInScope = Map[compiler.Symbol, List[compiler.Name]]( - resolve() -> List(), - resolve("java", "lang") -> List(), - resolve("scala") -> List(), - resolve("scala", "Predef") -> List() - ) - var level = 5 - var found: Option[String] = None - while(level > 0){ - thingsInScope = for { - (sym, path) <- thingsInScope - // No clue why this one blows up - m <- Try(sym.typeSignature.members).toOption.toSeq.flatten - } yield (m, m.name :: path) - thingsInScope.find(target.typeSymbol.fullName == _._1.fullName).foreach{ path => - level = 0 - found = Some(path._2.mkString(".")) - } - } - found - } - - /** * Compiles a blob of bytes and spits of a list of classfiles @@ -322,7 +269,7 @@ object Compiler{ printer: Printer, importsLen0: Int, userCodeNestingLevel: Int, - fileName: String): Option[Output] = { + fileName: String): Option[ICompiler.Output] = { def enumerateVdFiles(d: VirtualDirectory): Iterator[AbstractFile] = { val (subs, files) = d.iterator.partition(_.isDirectory) @@ -359,7 +306,7 @@ object Compiler{ } val imports = lastImports.toList - Some(Output(files, Imports(imports), usedEarlierDefinitions)) + Some(ICompiler.Output(files, Imports(imports), usedEarlierDefinitions)) } } diff --git a/amm/compiler/src/main/scala/ammonite/compiler/CompilerBuilder.scala b/amm/compiler/src/main/scala/ammonite/compiler/CompilerBuilder.scala new file mode 100644 index 000000000..5958b2d4f --- /dev/null +++ b/amm/compiler/src/main/scala/ammonite/compiler/CompilerBuilder.scala @@ -0,0 +1,91 @@ +package ammonite.compiler + +import java.net.URL +import java.nio.file.Path + +import ammonite.compiler.iface.{ + Compiler => ICompiler, + CompilerBuilder => ICompilerBuilder +} +import ammonite.util.Frame + +import scala.collection.mutable +import scala.reflect.internal.util.{NoPosition, Position} +import scala.reflect.io.VirtualDirectory +import scala.tools.nsc.Settings + + +object CompilerBuilder extends ICompilerBuilder { + def create( + initialClassPath: Seq[URL], + classPath: Seq[URL], + dynamicClassPath: Seq[(String, Array[Byte])], + evalClassLoader: ClassLoader, + pluginClassLoader: ClassLoader, + reporter: Option[ICompilerBuilder.Message => Unit], + settings: Seq[String], + classPathWhiteList: Set[Seq[String]], + lineNumberModifier: Boolean + ): ICompiler = { + + val vd = new VirtualDirectory("(memory)", None) + Compiler.addToClasspath(dynamicClassPath, vd) + + val scalacSettings = { + // not 100% sure error collection is correct (duplicates?) + val errors = new mutable.ListBuffer[String] + val settings0 = new Settings(err => errors += err) + val (_, unparsed) = settings0.processArguments(settings.toList, processAll = true) + for (arg <- unparsed) + errors += s"Unrecognized argument: $arg" + // TODO Report the errors via reporter? + settings0 + } + + val scalacReporterOpt = reporter.map { f => + def report(pos: Position, message: String, severity: String) = { + val (start, end) = + if (pos == NoPosition) (0, 0) + else (pos.start, pos.end) + val msg = ICompilerBuilder.Message(severity, start, end, message) + f(msg) + } + MakeReporter.makeReporter( + (pos, msg) => report(pos, msg, "ERROR"), + (pos, msg) => report(pos, msg, "WARNING"), + (pos, msg) => report(pos, msg, "INFO"), + scalacSettings + ) + } + + Compiler( + classPath, + vd, + evalClassLoader, + pluginClassLoader, + () => (), + scalacReporterOpt, + scalacSettings, + classPathWhiteList.map(_.toSeq).toSet, + initialClassPath, + lineNumberModifier + ) + } + + def newManager( + rtCacheDir: Option[Path], + headFrame: => Frame, + dependencyCompleter: => Option[String => (Int, Seq[String])], + whiteList: Set[Seq[String]], + initialClassLoader: ClassLoader + ): CompilerLifecycleManager = + new CompilerLifecycleManager( + rtCacheDir, + headFrame, + dependencyCompleter, + whiteList, + initialClassLoader + ) + + def scalaVersion = scala.util.Properties.versionNumberString +} diff --git a/amm/compiler/src/main/scala/ammonite/compiler/CompilerExtensions.scala b/amm/compiler/src/main/scala/ammonite/compiler/CompilerExtensions.scala new file mode 100644 index 000000000..0bc045ed2 --- /dev/null +++ b/amm/compiler/src/main/scala/ammonite/compiler/CompilerExtensions.scala @@ -0,0 +1,46 @@ +package ammonite.compiler + +import ammonite.interp.api.InterpAPI +import ammonite.repl.api.ReplAPI + +object CompilerExtensions { + + implicit class CompilerInterpAPIExtensions(private val api: InterpAPI) extends AnyVal { + + private def compilerManager = api._compilerManager.asInstanceOf[CompilerLifecycleManager] + + /** + * Configures the current compiler, or if the compiler hasn't been initialized + * yet, registers the configuration callback and applies it to the compiler + * when it ends up being initialized later + */ + def configureCompiler(c: scala.tools.nsc.Global => Unit): Unit = + compilerManager.configureCompiler(c) + + /** + * Pre-configures the next compiler. Useful for tuning options that are + * used during parsing such as -Yrangepos + */ + def preConfigureCompiler(c: scala.tools.nsc.Settings => Unit): Unit = + compilerManager.preConfigureCompiler(c) + } + + implicit class CompilerReplAPIExtensions(private val api: ReplAPI) extends AnyVal { + + private def compilerManager = api._compilerManager.asInstanceOf[CompilerLifecycleManager] + + + /** + * Access the compiler to do crazy things if you really want to! + */ + def compiler: scala.tools.nsc.Global = + compilerManager.compiler.compiler + + /** + * Access the presentation compiler to do even crazier things if you really want to! + */ + def interactiveCompiler: scala.tools.nsc.interactive.Global = + compilerManager.pressy.compiler + } + +} diff --git a/amm/interp/src/main/scala/ammonite/interp/CompilerLifecycleManager.scala b/amm/compiler/src/main/scala/ammonite/compiler/CompilerLifecycleManager.scala similarity index 84% rename from amm/interp/src/main/scala/ammonite/interp/CompilerLifecycleManager.scala rename to amm/compiler/src/main/scala/ammonite/compiler/CompilerLifecycleManager.scala index 7796b2b1e..9c297a539 100644 --- a/amm/interp/src/main/scala/ammonite/interp/CompilerLifecycleManager.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/CompilerLifecycleManager.scala @@ -1,8 +1,14 @@ -package ammonite.interp +package ammonite.compiler -import ammonite.runtime._ +import ammonite.compiler.iface.{ + Compiler => ICompiler, + CompilerLifecycleManager => ICompilerLifecycleManager, + Preprocessor +} import ammonite.util.Util._ -import ammonite.util._ +import ammonite.util.{Classpath, Frame, Printer} + +import java.nio.file.Path import scala.collection.mutable import scala.reflect.io.VirtualDirectory @@ -21,13 +27,14 @@ import scala.tools.nsc.Settings * than necessary */ class CompilerLifecycleManager( - storage: Storage, - headFrame: => Frame, + rtCacheDir: Option[Path], + headFrame: => ammonite.util.Frame, dependencyCompleteOpt: => Option[String => (Int, Seq[String])], classPathWhitelist: Set[Seq[String]], initialClassLoader: ClassLoader -){ +) extends ICompilerLifecycleManager { + def scalaVersion = scala.util.Properties.versionNumberString private[this] object Internal{ @@ -55,7 +62,7 @@ class CompilerLifecycleManager( def preprocess(fileName: String) = synchronized{ init() - DefaultPreprocessor(compiler.parse(fileName, _)) + compiler.preprocessor(fileName) } @@ -82,9 +89,9 @@ class CompilerLifecycleManager( val settings = Option(compiler).fold(new Settings)(_.compiler.settings.copy) onSettingsInit.foreach(_(settings)) - val initialClassPath = Classpath.classpath(initialClassLoader, storage) + val initialClassPath = Classpath.classpath(initialClassLoader, rtCacheDir) val headFrameClassPath = - Classpath.classpath(headFrame.classloader, storage) + Classpath.classpath(headFrame.classloader, rtCacheDir) Internal.compiler = Compiler( headFrameClassPath, @@ -103,13 +110,13 @@ class CompilerLifecycleManager( // Pressy is lazy, so the actual presentation compiler won't get instantiated // & initialized until one of the methods on it is actually used Internal.pressy = Pressy( - Classpath.classpath(headFrame.classloader, storage), + headFrameClassPath, dynamicClasspath, headFrame.classloader, settings.copy(), dependencyCompleteOpt, classPathWhitelist, - Classpath.classpath(initialClassLoader, storage) + initialClassPath ) Internal.preConfiguredSettingsChanged = false @@ -121,14 +128,9 @@ class CompilerLifecycleManager( pressy.complete(offset, previousImports, snippet) } - def search(target: scala.reflect.runtime.universe.Type) = synchronized{ - init() - compiler.search(target) - } - def compileClass(processed: Preprocessor.Output, printer: Printer, - fileName: String): Res[Compiler.Output] = synchronized{ + fileName: String): Option[ICompiler.Output] = synchronized{ // Enforce the invariant that every piece of code Ammonite ever compiles, // gets run within the `ammonite` package. It's further namespaced into // things like `ammonite.$file` or `ammonite.$sess`, but it has to be @@ -136,20 +138,15 @@ class CompilerLifecycleManager( assert(processed.code.trim.startsWith("package ammonite")) init() - for { - compiled <- Res.Success{ - compiler.compile( - processed.code.getBytes(scala.util.Properties.sourceEncoding), - printer, - processed.prefixCharLength, - processed.userCodeNestingLevel, - fileName - ) - } - _ = Internal.compilationCount += 1 - output <- Res(compiled, "Compilation Failed") - } yield output - + val compiled = compiler.compile( + processed.code.getBytes(scala.util.Properties.sourceEncoding), + printer, + processed.prefixCharLength, + processed.userCodeNestingLevel, + fileName + ) + Internal.compilationCount += 1 + compiled } def configureCompiler(callback: scala.tools.nsc.Global => Unit) = synchronized{ diff --git a/amm/interp/src/main/scala/ammonite/interp/CodeWrapper.scala b/amm/compiler/src/main/scala/ammonite/compiler/DefaultCodeWrapper.scala similarity index 69% rename from amm/interp/src/main/scala/ammonite/interp/CodeWrapper.scala rename to amm/compiler/src/main/scala/ammonite/compiler/DefaultCodeWrapper.scala index 216debe00..06d5c62f9 100644 --- a/amm/interp/src/main/scala/ammonite/interp/CodeWrapper.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/DefaultCodeWrapper.scala @@ -1,20 +1,11 @@ -package ammonite.interp +package ammonite.compiler +import ammonite.compiler.iface.CodeWrapper import ammonite.util._ import ammonite.util.Util.{CodeSource, normalizeNewlines} -trait CodeWrapper{ - def wrapperPath: Seq[Name] = Nil - def apply( - code: String, - source: CodeSource, - imports: Imports, - printCode: String, - indexedWrapperName: Name, - extraCode: String - ): (String, String, Int) -} -object CodeWrapper extends CodeWrapper{ + +object DefaultCodeWrapper extends CodeWrapper{ private val userCodeNestingLevel = 1 def apply( code: String, diff --git a/amm/interp/src/main/scala/ammonite/interp/DefaultPreprocessor.scala b/amm/compiler/src/main/scala/ammonite/compiler/DefaultPreprocessor.scala similarity index 96% rename from amm/interp/src/main/scala/ammonite/interp/DefaultPreprocessor.scala rename to amm/compiler/src/main/scala/ammonite/compiler/DefaultPreprocessor.scala index 3b414720b..ff5f926be 100644 --- a/amm/interp/src/main/scala/ammonite/interp/DefaultPreprocessor.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/DefaultPreprocessor.scala @@ -1,5 +1,6 @@ -package ammonite.interp +package ammonite.compiler +import ammonite.compiler.iface.{CodeWrapper, Preprocessor} import ammonite.util._ import ammonite.util.Util.{CodeSource, newLine} @@ -14,9 +15,6 @@ object DefaultPreprocessor { case m: G#MemberDef => m.mods.isPrivate case _ => false } - - def apply(parse: => String => Either[String, Seq[G#Tree]]): Preprocessor = - new DefaultPreprocessor(parse) } class DefaultPreprocessor(parse: => String => Either[String, Seq[G#Tree]], @@ -40,10 +38,10 @@ class DefaultPreprocessor(parse: => String => Either[String, Seq[G#Tree]], assert(codeSource.pkgName.head == Name("ammonite")) for{ Expanded(code, printer) <- expandStatements(stmts, resultIndex, skipEmpty) - (wrappedCode, importsLength, userCodeNestingLevel) = Preprocessor.wrapCode( + (wrappedCode, importsLength, userCodeNestingLevel) = codeWrapper.wrapCode( codeSource, indexedWrapperName, leadingSpaces + code, printerTemplate(printer.mkString(", ")), - imports, extraCode, markScript, codeWrapper + imports, extraCode, markScript ) } yield Preprocessor.Output(wrappedCode, importsLength, userCodeNestingLevel) } diff --git a/amm/repl/src/main/scala/ammonite/repl/Highlighter.scala b/amm/compiler/src/main/scala/ammonite/compiler/Highlighter.scala similarity index 83% rename from amm/repl/src/main/scala/ammonite/repl/Highlighter.scala rename to amm/compiler/src/main/scala/ammonite/compiler/Highlighter.scala index f4f2bb33c..5c434ad52 100644 --- a/amm/repl/src/main/scala/ammonite/repl/Highlighter.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/Highlighter.scala @@ -1,8 +1,6 @@ -package ammonite.repl +package ammonite.compiler -import ammonite.interp.Parsers - import fastparse._, NoWhitespace._ import scalaparse.Scala._ @@ -28,14 +26,6 @@ object Highlighter { }.reduce(_ ++ _).render.toVector } - def defaultHighlight(buffer: Vector[Char], - comment: fansi.Attrs, - `type`: fansi.Attrs, - literal: fansi.Attrs, - keyword: fansi.Attrs, - reset: fansi.Attrs) = { - defaultHighlight0(Parsers.Splitter(_), buffer, comment, `type`, literal, keyword, reset) - } def defaultHighlight0(parser: P[_] => P[Any], buffer: Vector[Char], @@ -49,14 +39,6 @@ object Highlighter { flattenIndices(boundedIndices, buffer) } - def defaultHighlightIndices(buffer: Vector[Char], - comment: fansi.Attrs, - `type`: fansi.Attrs, - literal: fansi.Attrs, - keyword: fansi.Attrs, - reset: fansi.Attrs) = Highlighter.defaultHighlightIndices0( - Parsers.Splitter(_), buffer, comment, `type`, literal, keyword, reset - ) def defaultHighlightIndices0(parser: P[_] => P[Any], buffer: Vector[Char], comment: fansi.Attrs, diff --git a/amm/compiler/src/main/scala/ammonite/compiler/Parsers.scala b/amm/compiler/src/main/scala/ammonite/compiler/Parsers.scala new file mode 100644 index 000000000..83fe5a4cc --- /dev/null +++ b/amm/compiler/src/main/scala/ammonite/compiler/Parsers.scala @@ -0,0 +1,277 @@ +package ammonite.compiler + +import ammonite.compiler.iface.{Parser => IParser, _} +import ammonite.util.ImportTree +import ammonite.util.Util.{CodeSource, newLine, windowsPlatform} + +import scala.collection.mutable + +object Parsers extends IParser { + + import fastparse._ + + import ScalaWhitespace._ + import scalaparse.Scala._ + + // For some reason Scala doesn't import this by default + private def `_`[_: P] = scalaparse.Scala.`_` + + + private def ImportSplitter[_: P]: P[Seq[ammonite.util.ImportTree]] = { + def IdParser = P( (Id | `_` ).! ).map( + s => if (s(0) == '`') s.drop(1).dropRight(1) else s + ) + def Selector = P( IdParser ~ (`=>` ~/ IdParser).? ) + def Selectors = P( "{" ~/ Selector.rep(sep = ","./) ~ "}" ) + def BulkImport = P( `_`).map( + _ => Seq("_" -> None) + ) + def Prefix = P( IdParser.rep(1, sep = ".") ) + def Suffix = P( "." ~/ (BulkImport | Selectors) ) + def ImportExpr: P[ammonite.util.ImportTree] = { + // Manually use `WL0` parser here, instead of relying on WhitespaceApi, as + // we do not want the whitespace to be consumed even if the WL0 parser parses + // to the end of the input (which is the default behavior for WhitespaceApi) + P( Index ~~ Prefix ~~ (WL0 ~~ Suffix).? ~~ Index).map{ + case (start, idSeq, selectors, end) => + ammonite.util.ImportTree(idSeq, selectors, start, end) + } + } + P( `import` ~/ ImportExpr.rep(1, sep = ","./) ) + } + + private def PatVarSplitter[_: P] = { + def Prefixes = P(Prelude ~ (`var` | `val`)) + def Lhs = P( Prefixes ~/ BindPattern.rep(1, "," ~/ Pass) ~ (`:` ~/ Type).? ) + P( Lhs.! ~ (`=` ~/ WL ~ StatCtx.Expr.!) ~ End ) + } + private def patVarSplit(code: String) = { + val Parsed.Success((lhs, rhs), _) = parse(code, PatVarSplitter(_)) + (lhs, rhs) + } + + private def Prelude[_: P] = P( (Annot ~ OneNLMax).rep ~ (Mod ~/ Pass).rep ) + + private def TmplStat[_: P] = P( Import | Prelude ~ BlockDef | StatCtx.Expr ) + + + // Do this funny ~~WS thing to make sure we capture the whitespace + // together with each statement; otherwise, by default, it gets discarded. + // + // After each statement, there must either be `Semis`, a "}" marking the + // end of the block, or the `End` of the input + private def StatementBlock[_: P](blockSep: => P0) = + P( Semis.? ~ (Index ~ (!blockSep ~ TmplStat ~~ WS ~~ (Semis | &("}") | End)).!).repX) + + private def Splitter0[_: P] = P( StatementBlock(Fail) ) + def Splitter[_: P] = P( ("{" ~ Splitter0 ~ "}" | Splitter0) ~ End ) + + private def ObjParser[_: P] = P( ObjDef ) + + /** + * Attempts to break a code blob into multiple statements. Returns `None` if + * it thinks the code blob is "incomplete" and requires more input + */ + def split( + code: String, + ignoreIncomplete: Boolean, + fileName: String + ): Option[Either[String, Seq[String]]] = + if (ignoreIncomplete) { + // We use `instrument` to detect when the parser has reached the end of the + // input, any time during the parse. If it has done so, and failed, we + // consider the input incomplete. + var furthest = 0 + val instrument = new fastparse.internal.Instrument { + def beforeParse(parser: String, index: Int): Unit = () + def afterParse(parser: String, index: Int, success: Boolean): Unit = { + if (index > furthest) furthest = index + } + } + + parse(code, Splitter(_), instrument = instrument) match{ + case Parsed.Failure(_, index, extra) if furthest == code.length => None + case f @ Parsed.Failure(_, _, _) => Some(Left( + formatFastparseError(fileName, code, f) + )) + case Parsed.Success(value, index) => Some(Right(value.map(_._2))) + } + } else + parse(code, Splitter(_)) match{ + case f @ Parsed.Failure(_, _, _) => Some(Left( + formatFastparseError(fileName, code, f) + )) + case Parsed.Success(value, index) => Some(Right(value.map(_._2))) + } + + def isObjDef(code: String): Boolean = { + parse(code, ObjParser(_)) + .fold((_, _, _) => false, (_, _) => true) + } + + private def Separator[_: P] = P( WL ~ "@" ~~ CharIn(" \n\r").rep(1) ) + private def CompilationUnit[_: P] = P( WL.! ~ StatementBlock(Separator) ~ WL ) + private def ScriptSplitter[_: P] = P( CompilationUnit.repX(1, Separator) ~ End) + def splitScript(code: String): Parsed[Seq[(String, Seq[(Int, String)])]] = + parse(code, ScriptSplitter(_)) + private def ScriptSplitterWithStart[_: P] = + P( Start ~ (Index ~ CompilationUnit).repX(1, Separator) ~ End) + def splitScriptWithStart(code: String): Parsed[Seq[(Int, (String, Seq[(Int, String)]))]] = + parse(code, ScriptSplitterWithStart(_)) + + def stringWrap(s: String): String = "\"" + pprint.Util.literalize(s) + "\"" + def stringSymWrap(s: String): String = { + def idToEnd[_: P] = P( scalaparse.syntax.Identifiers.Id ~ End ) + if (s == "") "'" + else parse(s, idToEnd(_)) match{ + case Parsed.Success(v, _) => "'" + s + case f: Parsed.Failure => stringWrap(s) + } + } + def parseImportHooksWithIndices( + source: CodeSource, + stmts: Seq[(Int, String)] + ): (Seq[String], Seq[ImportTree]) = synchronized{ + val hookedStmts = mutable.Buffer.empty[String] + val importTrees = mutable.Buffer.empty[ImportTree] + for((startIdx, stmt) <- stmts) { + // Call `fastparse.ParserInput.fromString` explicitly, to avoid generating a + // lambda in the class body and making the we-do-not-load-fastparse-on-cached-scripts + // test fail + parse(fastparse.ParserInput.fromString(stmt), ImportSplitter(_)) match{ + case f: Parsed.Failure => hookedStmts.append(stmt) + case Parsed.Success(parsedTrees, _) => + var currentStmt = stmt + for(importTree <- parsedTrees){ + if (importTree.prefix(0)(0) == '$') { + val length = importTree.end - importTree.start + currentStmt = currentStmt.patch( + importTree.start, (importTree.prefix(0) + ".$").padTo(length, ' '), length + ) + val importTree0 = importTree.copy( + start = startIdx + importTree.start, + end = startIdx + importTree.end + ) + importTrees.append(importTree0) + } + } + hookedStmts.append(currentStmt) + } + } + (hookedStmts.toSeq, importTrees.toSeq) + } + def formatFastparseError(fileName: String, rawCode: String, f: Parsed.Failure) = { + + val lineColIndex = f.extra.input.prettyIndex(f.index) + val expected = f.trace().failure.label + val locationString = { + val (first, last) = rawCode.splitAt(f.index) + val lastSnippet = last.split(newLine).headOption.getOrElse("") + val firstSnippet = first.reverse + .split(newLine.reverse) + .lift(0).getOrElse("").reverse + firstSnippet + lastSnippet + newLine + (" " * firstSnippet.length) + "^" + } + s"$fileName:$lineColIndex expected $expected$newLine$locationString" + } + + + /** + * Splits up a script file into its constituent blocks, each of which + * is a tuple of (leading-whitespace, statements). Leading whitespace + * is returned separately so we can later manipulate the statements e.g. + * by adding `val res2 = ` without the whitespace getting in the way + */ + def splitScript( + rawCode: String, + fileName: String + ): Either[String, IndexedSeq[(String, Seq[String])]] = { + parse(rawCode, ScriptSplitter(_)) match { + case f: Parsed.Failure => + Left(formatFastparseError(fileName, rawCode, f)) + + case s: Parsed.Success[Seq[(String, Seq[(Int, String)])]] => + + var offset = 0 + val blocks = mutable.ArrayBuffer[(String, Seq[String])]() + + // comment holds comments or empty lines above the code which is not caught along with code + for( (comment, codeWithStartIdx) <- s.value){ + val code = codeWithStartIdx.map(_._2) + + //ncomment has required number of newLines appended based on OS and offset + //since fastparse has hardcoded `\n`s, while parsing strings with `\r\n`s it + //gives out one extra `\r` after '@' i.e. block change + //which needs to be removed to get correct line number (It adds up one extra line) + //thats why the `comment.substring(1)` thing is necessary + val ncomment = + if(windowsPlatform && blocks.nonEmpty && !comment.isEmpty){ + comment.substring(1) + newLine * offset + }else{ + comment + newLine * offset + } + + // 1 is added as Separator parser eats up the newLine char following @ + offset = offset + (comment.split(newLine, -1).length - 1) + + code.map(_.split(newLine, -1).length - 1).sum + 1 + blocks.append((ncomment, code)) + } + + Right(blocks.toIndexedSeq) + } + } + + def splitScriptWithStart( + rawCode: String, + fileName: String + ): Either[Parsed.Failure, IndexedSeq[(Int, String, Seq[(Int, String)])]] = { + Parsers.splitScriptWithStart(rawCode) match { + case f: Parsed.Failure => + Left(f) + + case Parsed.Success(value, _) => + val blocks = value.toVector.map { + case (startIdx, (comment, code)) => + (startIdx, comment, code) + } + Right(blocks) + } + } + + def scriptBlocksWithStartIndices( + rawCode: String, + fileName: String + ): Either[IParser.ScriptSplittingError, Seq[IParser.ScriptBlock]] = { + splitScriptWithStart(rawCode, fileName) match { + case Left(f) => + Left(new IParser.ScriptSplittingError( + formatFastparseError(fileName, rawCode, f), + f.index, + f.trace().failure.label + )) + case Right(blocks) => + val blocks0 = blocks.map { + case (startIdx, ncomment, code) => + IParser.ScriptBlock(startIdx, ncomment, code) + } + Right(blocks0) + } + } + + def defaultHighlight(buffer: Vector[Char], + comment: fansi.Attrs, + `type`: fansi.Attrs, + literal: fansi.Attrs, + keyword: fansi.Attrs, + reset: fansi.Attrs) = { + Highlighter.defaultHighlight0(Splitter(_), buffer, comment, `type`, literal, keyword, reset) + } + def defaultHighlightIndices(buffer: Vector[Char], + comment: fansi.Attrs, + `type`: fansi.Attrs, + literal: fansi.Attrs, + keyword: fansi.Attrs, + reset: fansi.Attrs) = Highlighter.defaultHighlightIndices0( + Splitter(_), buffer, comment, `type`, literal, keyword, reset + ) +} diff --git a/amm/interp/src/main/scala/ammonite/interp/Pressy.scala b/amm/compiler/src/main/scala/ammonite/compiler/Pressy.scala similarity index 99% rename from amm/interp/src/main/scala/ammonite/interp/Pressy.scala rename to amm/compiler/src/main/scala/ammonite/compiler/Pressy.scala index 70b215869..6ec681b4f 100644 --- a/amm/interp/src/main/scala/ammonite/interp/Pressy.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/Pressy.scala @@ -1,8 +1,8 @@ -package ammonite.interp +package ammonite.compiler import java.io.{OutputStream, PrintWriter} -import ammonite.interp.MakeReporter.makeReporter +import ammonite.compiler.MakeReporter.makeReporter import ammonite.util.Name import scala.reflect.internal.util.{BatchSourceFile, OffsetPosition, Position} diff --git a/amm/interp/src/main/scala/ammonite/interp/internal/CustomURLZipArchive.scala b/amm/compiler/src/main/scala/ammonite/compiler/internal/CustomURLZipArchive.scala similarity index 99% rename from amm/interp/src/main/scala/ammonite/interp/internal/CustomURLZipArchive.scala rename to amm/compiler/src/main/scala/ammonite/compiler/internal/CustomURLZipArchive.scala index 3710696a5..0459ad8f3 100644 --- a/amm/interp/src/main/scala/ammonite/interp/internal/CustomURLZipArchive.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/internal/CustomURLZipArchive.scala @@ -1,4 +1,4 @@ -package ammonite.interp.internal +package ammonite.compiler.internal import java.io.{ByteArrayInputStream, InputStream} import java.util.zip.{ZipEntry, ZipFile, ZipInputStream} diff --git a/amm/repl/src/main/scala/ammonite/repl/tools/HighlightJava.scala b/amm/compiler/src/main/scala/ammonite/compiler/tools/HighlightJava.scala similarity index 97% rename from amm/repl/src/main/scala/ammonite/repl/tools/HighlightJava.scala rename to amm/compiler/src/main/scala/ammonite/compiler/tools/HighlightJava.scala index 032230feb..c9b372f56 100644 --- a/amm/repl/src/main/scala/ammonite/repl/tools/HighlightJava.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/tools/HighlightJava.scala @@ -1,6 +1,6 @@ -package ammonite.repl.tools +package ammonite.compiler.tools -import ammonite.repl.Highlighter.flattenIndices +import ammonite.compiler.Highlighter.flattenIndices import ammonite.util.CodeColors import com.github.javaparser.GeneratedJavaParserConstants._ import com.github.javaparser.{GeneratedJavaParserConstants, ParseStart, StringProvider} diff --git a/amm/repl/src/main/scala/ammonite/repl/tools/SourceRuntime.scala b/amm/compiler/src/main/scala/ammonite/compiler/tools/SourceRuntime.scala similarity index 94% rename from amm/repl/src/main/scala/ammonite/repl/tools/SourceRuntime.scala rename to amm/compiler/src/main/scala/ammonite/compiler/tools/SourceRuntime.scala index 7b1e42c32..0857a88c1 100644 --- a/amm/repl/src/main/scala/ammonite/repl/tools/SourceRuntime.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/tools/SourceRuntime.scala @@ -1,13 +1,12 @@ -package ammonite.repl.tools +package ammonite.compiler.tools import javassist.{ByteArrayClassPath, CtClass, CtMethod} -import ammonite.repl.Highlighter -import ammonite.repl.api.Location +import ammonite.compiler.Highlighter import ammonite.runtime.tools.browse.Strings import ammonite.util.CodeColors -import ammonite.util.Util.newLine +import ammonite.util.Util.{Location, newLine} import scala.collection.mutable import scala.language.experimental.macros @@ -252,5 +251,23 @@ object SourceRuntime{ } } + def failLoudly[T](res: Either[String, T]): T = res match{ + case Left(s) => throw new Exception(s) + case Right(r) => r + } + + def desugarImpl(s: String)(implicit colors: ammonite.util.CodeColors): Desugared = { + + new Desugared( + ammonite.compiler.Parsers.defaultHighlight( + s.toVector, + colors.comment, + colors.`type`, + colors.literal, + colors.keyword, + fansi.Attr.Reset + ).mkString + ) + } } diff --git a/amm/repl/api/src/main/scala/ammonite/repl/tools/desugar.scala b/amm/compiler/src/main/scala/ammonite/compiler/tools/desugar.scala similarity index 72% rename from amm/repl/api/src/main/scala/ammonite/repl/tools/desugar.scala rename to amm/compiler/src/main/scala/ammonite/compiler/tools/desugar.scala index 4aea3f1e0..88150b6eb 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/tools/desugar.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/tools/desugar.scala @@ -1,4 +1,4 @@ -package ammonite.repl.tools +package ammonite.compiler.tools import sourcecode.Compat._ import scala.language.experimental.macros @@ -11,7 +11,7 @@ object desugar{ def transformer(c: Context)(expr: c.Expr[Any]): c.Expr[Desugared] = { import c.universe._ c.Expr[Desugared]( - q"ammonite.repl.api.SourceBridge.value.desugarImpl(${c.universe.showCode(expr.tree)})" + q"ammonite.compiler.tools.SourceRuntime.desugarImpl(${c.universe.showCode(expr.tree)})" ) } diff --git a/amm/repl/api/src/main/scala/ammonite/repl/tools/source.scala b/amm/compiler/src/main/scala/ammonite/compiler/tools/source.scala similarity index 96% rename from amm/repl/api/src/main/scala/ammonite/repl/tools/source.scala rename to amm/compiler/src/main/scala/ammonite/compiler/tools/source.scala index d00cc2a06..89c694f0d 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/tools/source.scala +++ b/amm/compiler/src/main/scala/ammonite/compiler/tools/source.scala @@ -1,8 +1,8 @@ -package ammonite.repl.tools +package ammonite.compiler.tools -import ammonite.repl.api.Location import ammonite.runtime.tools.browse.Strings import ammonite.util.CodeColors +import ammonite.util.Util.Location import sourcecode.Compat._ import scala.annotation.tailrec @@ -53,7 +53,7 @@ object source{ colors: c.Expr[CodeColors]): c.Expr[Unit] = { import c.universe._ val defaultBrowseExpr = c.Expr[Int => Strings]( - q"_root_.ammonite.repl.api.SourceBridge.value.browseSourceCommand" + q"${prefix(c)}.browseSourceCommand" ) applyCustomizeCommandMacro(c)(f, defaultBrowseExpr)(pprinter, colors) @@ -86,7 +86,7 @@ object source{ def prefix(c: Context) = { import c.universe._ - q"ammonite.repl.api.SourceBridge.value" + q"ammonite.compiler.tools.SourceRuntime" } /** * Attempts to up an expression, into either a LHS + methodcall + rhs. We diff --git a/amm/interp/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala b/amm/compiler/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala similarity index 96% rename from amm/interp/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala rename to amm/compiler/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala index b2dbb3673..7a94d07b9 100644 --- a/amm/interp/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala +++ b/amm/compiler/src/main/scala/scala/tools/nsc/CustomZipAndJarFileLookupFactory.scala @@ -3,7 +3,7 @@ package scala.tools.nsc import java.io.File import java.net.URL -import ammonite.interp.internal.CustomURLZipArchive +import ammonite.compiler.internal.CustomURLZipArchive import scala.reflect.io.AbstractFile import scala.tools.nsc.classpath.FileUtils.AbstractFileOps @@ -13,7 +13,7 @@ import scala.tools.nsc.util.{ClassPath, ClassRepresentation} // Originally based on // https://github.com/scala/scala/blob/329deac9ab4f39e5e766ec3ab3f3f4cddbc44aa1 // /src/compiler/scala/tools/nsc/classpath/ZipAndJarFileLookupFactory.scala#L50-L166, -// then adapted to rely on ammonite.interp.internal.CustomURLZipArchive (accepting URLs) +// then adapted to rely on ammonite.compiler.internal.CustomURLZipArchive (accepting URLs) // rather than FileZipArchive (only accepting files). object CustomZipAndJarFileLookupFactory { diff --git a/amm/interp/api/src/main/scala/ammonite/interp/api/InterpAPI.scala b/amm/interp/api/src/main/scala/ammonite/interp/api/InterpAPI.scala index d40b89664..8db33a3c6 100644 --- a/amm/interp/api/src/main/scala/ammonite/interp/api/InterpAPI.scala +++ b/amm/interp/api/src/main/scala/ammonite/interp/api/InterpAPI.scala @@ -60,18 +60,8 @@ trait InterpAPI { * exitValue before the repl exits */ val beforeExitHooks: mutable.Buffer[Any => Any] - /** - * Configures the current compiler, or if the compiler hasn't been initialized - * yet, registers the configuration callback and applies it to the compiler - * when it ends up being initialized later - */ - def configureCompiler(c: scala.tools.nsc.Global => Unit): Unit - /** - * Pre-configures the next compiler. Useful for tuning options that are - * used during parsing such as -Yrangepos - */ - def preConfigureCompiler(c: scala.tools.nsc.Settings => Unit): Unit + def _compilerManager: ammonite.compiler.iface.CompilerLifecycleManager } diff --git a/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala b/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala index b23943edf..d2e572974 100644 --- a/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala +++ b/amm/interp/src/main/scala/ammonite/interp/Interpreter.scala @@ -3,18 +3,22 @@ package ammonite.interp import java.io.{File, OutputStream, PrintStream} import java.util.regex.Pattern +import ammonite.compiler.iface.{ + CodeWrapper, + CompilerBuilder, + CompilerLifecycleManager, + Parser, + Preprocessor +} import ammonite.interp.api.{InterpAPI, InterpLoad, LoadJar} -import ammonite.interp.CodeWrapper import scala.collection.mutable import ammonite.runtime._ -import fastparse._ import annotation.tailrec import ammonite.runtime.tools.IvyThing -import ammonite.util.ImportTree import ammonite.util.Util._ -import ammonite.util._ +import ammonite.util.{Frame => _, _} import coursierapi.{Dependency, Fetch, Repository} /** @@ -22,7 +26,10 @@ import coursierapi.{Dependency, Fetch, Repository} * to interpret Scala code. Doesn't attempt to provide any * real encapsulation for now. */ -class Interpreter(val printer: Printer, +class Interpreter(compilerBuilder: CompilerBuilder, + // by-name, so that fastparse isn't loaded when we don't need it + parser: => Parser, + val printer: Printer, val storage: Storage, val wd: os.Path, colors: Ref[Colors], @@ -53,8 +60,8 @@ class Interpreter(val printer: Printer, def dependencyComplete: String => (Int, Seq[String]) = IvyThing.completer(repositories(), verbose = verboseOutput) - val compilerManager = new CompilerLifecycleManager( - storage, + val compilerManager = compilerBuilder.newManager( + storage.dirOpt.map(_.toNIO), headFrame, Some(dependencyComplete), classPathWhitelist, @@ -237,7 +244,7 @@ class Interpreter(val printer: Printer, Seq(Name("ammonite"), Name("$sess")), Some(wd/"(console)") ) - val (hookStmts, importTrees) = Parsers.parseImportHooks(codeSource, stmts) + val (hookStmts, importTrees) = parser.parseImportHooks(codeSource, stmts) for{ _ <- Catching { case ex => Res.Exception(ex, "") } @@ -278,10 +285,13 @@ class Interpreter(val printer: Printer, incrementLine: () => Unit): Res[(Evaluated, Tag)] = synchronized{ for{ _ <- Catching{ case e: ThreadDeath => Evaluator.interrupted(e) } - output <- compilerManager.compileClass( - processed, - printer, - fileName + output <- Res( + compilerManager.compileClass( + processed, + printer, + fileName + ), + "Compilation Failed" ) _ = incrementLine() res <- eval.processLine( @@ -314,8 +324,11 @@ class Interpreter(val printer: Printer, for { _ <- Catching{case e: Throwable => e.printStackTrace(); throw e} - output <- compilerManager.compileClass( - processed, printer, codeSource.fileName + output <- Res( + compilerManager.compileClass( + processed, printer, codeSource.fileName + ), + "Compilation Failed" ) cls <- eval.loadClass(fullyQualifiedName, output.classFiles) @@ -371,7 +384,7 @@ class Interpreter(val printer: Printer, // and none of it's blocks end up needing to be re-compiled. We don't know up // front if any blocks will need re-compilation, because it may import $file // another script which gets changed, and we'd only know when we reach that block - lazy val splittedScript = Preprocessor.splitScript( + lazy val splittedScript = parser.splitScript( Interpreter.skipSheBangLine(code), codeSource.fileName ) @@ -416,7 +429,7 @@ class Interpreter(val printer: Printer, val wrapperName = Name("cmd" + currentLine) val fileName = wrapperName.encoded + ".sc" for { - blocks <- Res(Preprocessor.splitScript(Interpreter.skipSheBangLine(code), fileName)) + blocks <- Res(parser.splitScript(Interpreter.skipSheBangLine(code), fileName)) metadata <- processAllScriptBlocks( blocks.map(_ => None), @@ -565,7 +578,7 @@ class Interpreter(val printer: Printer, for{ allSplittedChunks <- splittedScript (leadingSpaces, stmts) = allSplittedChunks(wrapperIndex - 1) - (hookStmts, importTrees) = Parsers.parseImportHooks(codeSource, stmts) + (hookStmts, importTrees) = parser.parseImportHooks(codeSource, stmts) hookInfo <- resolveImportHooks( importTrees, hookStmts, codeSource, scriptCodeWrapper.wrapperPath ) @@ -654,14 +667,6 @@ class Interpreter(val printer: Printer, def watch(p: os.Path) = interp.watch(p) def watchValue[T](v: => T): T = {interp.watchValue(v); v} - def configureCompiler(callback: scala.tools.nsc.Global => Unit) = { - compilerManager.configureCompiler(callback) - } - - def preConfigureCompiler(callback: scala.tools.nsc.Settings => Unit) = { - compilerManager.preConfigureCompiler(callback) - } - val beforeExitHooks = interp.beforeExitHooks val repositories = interp.repositories @@ -701,6 +706,8 @@ class Interpreter(val printer: Printer, } } + + def _compilerManager = interp.compilerManager } } @@ -713,9 +720,13 @@ object Interpreter{ "ammonite.interp.api.IvyConstructor.{ArtifactIdExt, GroupIdExt}", importType = ImportData.Type ), + ImportData("""ammonite.compiler.CompilerExtensions.{ + CompilerInterpAPIExtensions, + CompilerReplAPIExtensions + }"""), ImportData("ammonite.runtime.tools.{browse, grep, time}"), ImportData("ammonite.runtime.tools.tail", importType = ImportData.TermType), - ImportData("ammonite.repl.tools.{desugar, source}"), + ImportData("ammonite.compiler.tools.{desugar, source}"), ImportData("mainargs.{arg, main}"), ImportData("ammonite.repl.tools.Util.PathRead") ) diff --git a/amm/interp/src/main/scala/ammonite/interp/Parsers.scala b/amm/interp/src/main/scala/ammonite/interp/Parsers.scala deleted file mode 100644 index d451191a1..000000000 --- a/amm/interp/src/main/scala/ammonite/interp/Parsers.scala +++ /dev/null @@ -1,145 +0,0 @@ -package ammonite.interp - -import ammonite.util.ImportTree -import ammonite.util.Util.CodeSource - -import scala.collection.mutable - -object Parsers { - - import fastparse._ - - import ScalaWhitespace._ - import scalaparse.Scala._ - - // For some reason Scala doesn't import this by default - def `_`[_: P] = scalaparse.Scala.`_` - - - def ImportSplitter[_: P]: P[Seq[ammonite.util.ImportTree]] = { - def IdParser = P( (Id | `_` ).! ).map( - s => if (s(0) == '`') s.drop(1).dropRight(1) else s - ) - def Selector = P( IdParser ~ (`=>` ~/ IdParser).? ) - def Selectors = P( "{" ~/ Selector.rep(sep = ","./) ~ "}" ) - def BulkImport = P( `_`).map( - _ => Seq("_" -> None) - ) - def Prefix = P( IdParser.rep(1, sep = ".") ) - def Suffix = P( "." ~/ (BulkImport | Selectors) ) - def ImportExpr: P[ammonite.util.ImportTree] = { - // Manually use `WL0` parser here, instead of relying on WhitespaceApi, as - // we do not want the whitespace to be consumed even if the WL0 parser parses - // to the end of the input (which is the default behavior for WhitespaceApi) - P( Index ~~ Prefix ~~ (WL0 ~~ Suffix).? ~~ Index).map{ - case (start, idSeq, selectors, end) => - ammonite.util.ImportTree(idSeq, selectors, start, end) - } - } - P( `import` ~/ ImportExpr.rep(1, sep = ","./) ) - } - - def PatVarSplitter[_: P] = { - def Prefixes = P(Prelude ~ (`var` | `val`)) - def Lhs = P( Prefixes ~/ BindPattern.rep(1, "," ~/ Pass) ~ (`:` ~/ Type).? ) - P( Lhs.! ~ (`=` ~/ WL ~ StatCtx.Expr.!) ~ End ) - } - def patVarSplit(code: String) = { - val Parsed.Success((lhs, rhs), _) = parse(code, PatVarSplitter(_)) - (lhs, rhs) - } - - def Prelude[_: P] = P( (Annot ~ OneNLMax).rep ~ (Mod ~/ Pass).rep ) - - def TmplStat[_: P] = P( Import | Prelude ~ BlockDef | StatCtx.Expr ) - - - // Do this funny ~~WS thing to make sure we capture the whitespace - // together with each statement; otherwise, by default, it gets discarded. - // - // After each statement, there must either be `Semis`, a "}" marking the - // end of the block, or the `End` of the input - def StatementBlock[_: P](blockSep: => P0) = - P( Semis.? ~ (Index ~ (!blockSep ~ TmplStat ~~ WS ~~ (Semis | &("}") | End)).!).repX) - - def Splitter0[_: P] = P( StatementBlock(Fail) ) - def Splitter[_: P] = P( ("{" ~ Splitter0 ~ "}" | Splitter0) ~ End ) - - def ObjParser[_: P] = P( ObjDef ) - - /** - * Attempts to break a code blob into multiple statements. Returns `None` if - * it thinks the code blob is "incomplete" and requires more input - */ - def split(code: String): Option[Parsed[Seq[String]]] = { - // We use `instrument` to detect when the parser has reached the end of the - // input, any time during the parse. If it has done so, and failed, we - // consider the input incomplete. - var furthest = 0 - val instrument = new fastparse.internal.Instrument { - def beforeParse(parser: String, index: Int): Unit = () - def afterParse(parser: String, index: Int, success: Boolean): Unit = { - if (index > furthest) furthest = index - } - } - - parse(code, Splitter(_), instrument = instrument) match{ - case Parsed.Failure(_, index, extra) if furthest == code.length => None - case f @ Parsed.Failure(_, _, _) => Some(f) - case Parsed.Success(value, index) => Some(Parsed.Success(value.map(_._2), index)) - } - } - - def isObjDef(code: String): Boolean = { - parse(code, ObjParser(_)) - .fold((_, _, _) => false, (_, _) => true) - } - - def Separator[_: P] = P( WL ~ "@" ~~ CharIn(" \n\r").rep(1) ) - def CompilationUnit[_: P] = P( WL.! ~ StatementBlock(Separator) ~ WL ) - def ScriptSplitter[_: P] = P( CompilationUnit.repX(1, Separator) ~ End) - def splitScript(code: String) = parse(code, ScriptSplitter(_)) - def ScriptSplitterWithStart[_: P] = P( Start ~ (Index ~ CompilationUnit).repX(1, Separator) ~ End) - def splitScriptWithStart(code: String) = parse(code, ScriptSplitterWithStart(_)) - - def stringWrap(s: String) = "\"" + pprint.Util.literalize(s) + "\"" - def stringSymWrap(s: String) = { - def idToEnd[_: P] = P( scalaparse.syntax.Identifiers.Id ~ End ) - if (s == "") "'" - else parse(s, idToEnd(_)) match{ - case Parsed.Success(v, _) => "'" + s - case f: Parsed.Failure => stringWrap(s) - } - } - def parseImportHooks(source: CodeSource, stmts: Seq[String]) = - parseImportHooksWithIndices(source, stmts.map((0, _))) - def parseImportHooksWithIndices(source: CodeSource, stmts: Seq[(Int, String)]) = synchronized{ - val hookedStmts = mutable.Buffer.empty[String] - val importTrees = mutable.Buffer.empty[ImportTree] - for((startIdx, stmt) <- stmts) { - // Call `fastparse.ParserInput.fromString` explicitly, to avoid generating a - // lambda in the class body and making the we-do-not-load-fastparse-on-cached-scripts - // test fail - parse(fastparse.ParserInput.fromString(stmt), Parsers.ImportSplitter(_)) match{ - case f: Parsed.Failure => hookedStmts.append(stmt) - case Parsed.Success(parsedTrees, _) => - var currentStmt = stmt - for(importTree <- parsedTrees){ - if (importTree.prefix(0)(0) == '$') { - val length = importTree.end - importTree.start - currentStmt = currentStmt.patch( - importTree.start, (importTree.prefix(0) + ".$").padTo(length, ' '), length - ) - val importTree0 = importTree.copy( - start = startIdx + importTree.start, - end = startIdx + importTree.end - ) - importTrees.append(importTree0) - } - } - hookedStmts.append(currentStmt) - } - } - (hookedStmts.toSeq, importTrees.toSeq) - } -} diff --git a/amm/interp/src/main/scala/ammonite/interp/PredefInitialization.scala b/amm/interp/src/main/scala/ammonite/interp/PredefInitialization.scala index 6f0f54f80..cc734cbc7 100644 --- a/amm/interp/src/main/scala/ammonite/interp/PredefInitialization.scala +++ b/amm/interp/src/main/scala/ammonite/interp/PredefInitialization.scala @@ -1,7 +1,7 @@ package ammonite.interp -import ammonite.interp.api.{APIHolder, InterpAPI} +import ammonite.interp.api.InterpAPI import ammonite.runtime.{SpecialClassLoader, Storage} import ammonite.util.ScriptOutput.Metadata import ammonite.util.{ImportData, Imports, Name, PredefInfo, Res} diff --git a/amm/interp/src/main/scala/ammonite/interp/Preprocessor.scala b/amm/interp/src/main/scala/ammonite/interp/Preprocessor.scala deleted file mode 100644 index af8c60bc6..000000000 --- a/amm/interp/src/main/scala/ammonite/interp/Preprocessor.scala +++ /dev/null @@ -1,153 +0,0 @@ -package ammonite.interp - -import ammonite.util._ -import ammonite.util.Util.{CodeSource, newLine, normalizeNewlines, windowsPlatform} -import fastparse._ - -import scala.tools.nsc.{Global => G} -import collection.mutable -/** - * Responsible for all scala-source-code-munging that happens within the - * Ammonite REPL. - * - * Performs several tasks: - * - * - Takes top-level Scala expressions and assigns them to `res{1, 2, 3, ...}` - * values so they can be accessed later in the REPL - * - * - Wraps the code snippet with an wrapper `object` since Scala doesn't allow - * top-level expressions - * - * - Mangles imports from our [[ammonite.util.ImportData]] data structure into a source - * String - * - * - Combines all of these into a complete compilation unit ready to feed into - * the Scala compiler - */ -trait Preprocessor{ - def transform(stmts: Seq[String], - resultIndex: String, - leadingSpaces: String, - codeSource: CodeSource, - indexedWrapperName: Name, - imports: Imports, - printerTemplate: String => String, - extraCode: String, - skipEmpty: Boolean, - markScript: Boolean, - codeWrapper: CodeWrapper): Res[Preprocessor.Output] -} - -object Preprocessor{ - case class Output(code: String, - prefixCharLength: Int, - userCodeNestingLevel: Int) - - - def formatFastparseError(fileName: String, rawCode: String, f: Parsed.Failure) = { - - val lineColIndex = f.extra.input.prettyIndex(f.index) - val expected = f.trace().failure.label - val locationString = { - val (first, last) = rawCode.splitAt(f.index) - val lastSnippet = last.split(newLine).headOption.getOrElse("") - val firstSnippet = first.reverse - .split(newLine.reverse) - .lift(0).getOrElse("").reverse - firstSnippet + lastSnippet + newLine + (" " * firstSnippet.length) + "^" - } - s"$fileName:$lineColIndex expected $expected$newLine$locationString" - } - - - /** - * Splits up a script file into its constituent blocks, each of which - * is a tuple of (leading-whitespace, statements). Leading whitespace - * is returned separately so we can later manipulate the statements e.g. - * by adding `val res2 = ` without the whitespace getting in the way - */ - def splitScript( - rawCode: String, - fileName: String - ): Either[String, IndexedSeq[(String, Seq[String])]] = { - Parsers.splitScript(rawCode) match { - case f: Parsed.Failure => - Left(formatFastparseError(fileName, rawCode, f)) - - case s: Parsed.Success[Seq[(String, Seq[(Int, String)])]] => - - var offset = 0 - val blocks = mutable.ArrayBuffer[(String, Seq[String])]() - - // comment holds comments or empty lines above the code which is not caught along with code - for( (comment, codeWithStartIdx) <- s.value){ - val code = codeWithStartIdx.map(_._2) - - //ncomment has required number of newLines appended based on OS and offset - //since fastparse has hardcoded `\n`s, while parsing strings with `\r\n`s it - //gives out one extra `\r` after '@' i.e. block change - //which needs to be removed to get correct line number (It adds up one extra line) - //thats why the `comment.substring(1)` thing is necessary - val ncomment = - if(windowsPlatform && blocks.nonEmpty && !comment.isEmpty){ - comment.substring(1) + newLine * offset - }else{ - comment + newLine * offset - } - - // 1 is added as Separator parser eats up the newLine char following @ - offset = offset + (comment.split(newLine, -1).length - 1) + - code.map(_.split(newLine, -1).length - 1).sum + 1 - blocks.append((ncomment, code)) - } - - Right(blocks.toIndexedSeq) - } - } - - def splitScriptWithStart( - rawCode: String, - fileName: String - ): Either[Parsed.Failure, IndexedSeq[(Int, String, Seq[(Int, String)])]] = { - Parsers.splitScriptWithStart(rawCode) match { - case f: Parsed.Failure => - Left(f) - - case Parsed.Success(value, _) => - val blocks = value.toVector.map { - case (startIdx, (comment, code)) => - (startIdx, comment, code) - } - Right(blocks) - } - } - - - - def wrapCode(codeSource: CodeSource, - indexedWrapperName: Name, - code: String, - printCode: String, - imports: Imports, - extraCode: String, - markScript: Boolean, - codeWrapper: CodeWrapper) = { - - //we need to normalize topWrapper and bottomWrapper in order to ensure - //the snippets always use the platform-specific newLine - val extraCode0 = - if (markScript) extraCode + "/**/" - else extraCode - val (topWrapper, bottomWrapper, userCodeNestingLevel) = - codeWrapper(code, codeSource, imports, printCode, indexedWrapperName, extraCode0) - val (topWrapper0, bottomWrapper0) = - if (markScript) (topWrapper + "/**/ /**/" + bottomWrapper) - else (topWrapper, bottomWrapper) - val importsLen = topWrapper0.length - - (topWrapper0 + code + bottomWrapper0, importsLen, userCodeNestingLevel) - } - - -} - diff --git a/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala b/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala index 2e4555a5b..27b392acf 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/AmmoniteBuildServer.scala @@ -7,10 +7,11 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.{CompletableFuture, Executors, ThreadFactory} import java.util.UUID +import ammonite.compiler.iface.{CodeWrapper, CompilerBuilder, Parser} import ammonite.interp.api.InterpAPI -import ammonite.interp.{CodeWrapper, DependencyLoader} -import ammonite.runtime.{Classpath, ImportHook, Storage} -import ammonite.util.{Imports, Printer} +import ammonite.interp.DependencyLoader +import ammonite.runtime.{ImportHook, Storage} +import ammonite.util.{Classpath, Imports, Printer} import ch.epfl.scala.bsp4j.{Diagnostic => BDiagnostic, Position => BPosition, _} import coursierapi.{Dependency, Repository} import org.eclipse.lsp4j.jsonrpc.Launcher @@ -21,10 +22,12 @@ import scala.util.{Failure, Success} import scala.util.control.NonFatal class AmmoniteBuildServer( + compilerBuilder: CompilerBuilder, + parser: Parser, + codeWrapper: CodeWrapper, initialScripts: Seq[os.Path] = Nil, initialImports: Imports = AmmoniteBuildServer.defaultImports, defaultRepositories: Seq[Repository] = Repository.defaults().asScala.toList, - codeWrapper: CodeWrapper = CodeWrapper, importHooks: Map[Seq[String], ImportHook] = ImportHook.defaults ) extends BuildServer with ScalaBuildServer with DummyBuildServerImplems { @@ -52,12 +55,14 @@ class AmmoniteBuildServer( classOf[InterpAPI].getClassLoader else Thread.currentThread().getContextClassLoader - private def initialClassPath = Classpath.classpath(initialClassLoader, storage) - .map(_.toURI) + private def initialClassPath = + Classpath.classpath(initialClassLoader, storage.dirOpt.map(_.toNIO)).map(_.toURI) private lazy val proc = withRoot { root => ScriptProcessor( + parser, + codeWrapper, dependencyLoader, defaultRepositories, Seq( @@ -68,13 +73,13 @@ class AmmoniteBuildServer( ) ), root, - codeWrapper, importHooks ) } private lazy val compiler = withRoot { root => new ScriptCompiler( + compilerBuilder, storage, printer, codeWrapper, diff --git a/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompileResult.scala b/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompileResult.scala index 890f2bf41..f64c57b3a 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompileResult.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompileResult.scala @@ -1,8 +1,8 @@ package ammonite.interp.script -import ammonite.interp.{Compiler => AmmCompiler} +import ammonite.compiler.iface.Compiler.Output final case class ScriptCompileResult( diagnostics: Seq[Diagnostic], - errorOrOutput: Either[String, Seq[AmmCompiler.Output]] + errorOrOutput: Either[String, Seq[Output]] ) diff --git a/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompiler.scala b/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompiler.scala index 85897aea9..8d5d0ffac 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompiler.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/ScriptCompiler.scala @@ -2,22 +2,15 @@ package ammonite.interp.script import java.util.concurrent.ConcurrentHashMap -import ammonite.interp.{ - CodeWrapper, - Compiler => AmmCompiler, - DefaultPreprocessor, - Interpreter, - MakeReporter -} -import ammonite.runtime.{Classpath, Storage} -import ammonite.util.{Imports, Name, Printer} +import ammonite.compiler.iface.{CodeWrapper, Compiler => AmmCompiler, CompilerBuilder} +import ammonite.runtime.Storage +import ammonite.util.{Imports, Printer} import scala.collection.JavaConverters._ import scala.collection.mutable -import scala.reflect.io.VirtualDirectory -import scala.tools.nsc.Settings final class ScriptCompiler( + compilerBuilder: CompilerBuilder, storage: Storage, printer: Printer, // TODO Remove this codeWrapper: CodeWrapper, @@ -84,11 +77,7 @@ final class ScriptCompiler( module: Script, dependencies: Script.ResolvedDependencies ): ScriptCompileResult = - settingsOrError(module) match { - case Left(errors) => ScriptCompileResult(Nil, Left(errors.mkString(", "))) - case Right((settings, settingsArgs)) => - compileIfNeeded(settings, settingsArgs, module, dependencies) - } + compileIfNeeded(moduleSettings(module), module, dependencies) /** * Reads compilation output from cache. @@ -126,30 +115,11 @@ final class ScriptCompiler( else Nil - private def settingsOrError(module: Script): Either[Seq[String], (Settings, Seq[String])] = { - - val args = moduleSettings(module) - - val errors = new mutable.ListBuffer[String] - val settings0 = new Settings(err => errors += err) - val (_, unparsed) = settings0.processArguments(args, true) - for (arg <- unparsed) - errors += s"Unrecognized argument: $arg" - - if (errors.isEmpty) - Right((settings0, args)) - else - Left(errors.toList) - } - /** Writes on disk the source passed to scalac, corresponding to this script */ def preCompile(module: Script): Unit = { - val settings = settingsOrError(module) - .map(_._1) - .getOrElse(new Settings(_ => ())) - val compiler = new SingleScriptCompiler( + compilerBuilder, initialClassLoader, storage, printer, @@ -158,7 +128,7 @@ final class ScriptCompiler( codeWrapper, wd, generateSemanticDbs, - settings, + moduleSettings(module), module, Script.ResolvedDependencies(Nil, Nil, Nil), moduleTarget(module), @@ -215,7 +185,6 @@ final class ScriptCompiler( None private def compileIfNeeded( - settings0: Settings, settingsArgs: Seq[String], script: Script, dependencies: Script.ResolvedDependencies @@ -224,20 +193,21 @@ final class ScriptCompiler( val key = InMemoryCacheKey(settingsArgs, script, dependencies) Option(cache.get(key)).getOrElse { cleanUpCache() - val res = doCompile(settings0, script, dependencies) + val res = doCompile(settingsArgs, script, dependencies) Option(cache.putIfAbsent(key, res)) .getOrElse(res) } } else - doCompile(settings0, script, dependencies) + doCompile(settingsArgs, script, dependencies) private def doCompile( - settings0: Settings, + settingsArgs: Seq[String], module: Script, dependencies: Script.ResolvedDependencies ): ScriptCompileResult = { val compiler = new SingleScriptCompiler( + compilerBuilder, initialClassLoader, storage, printer, @@ -246,7 +216,7 @@ final class ScriptCompiler( codeWrapper, wd, generateSemanticDbs, - settings0, + settingsArgs, module, dependencies, moduleTarget(module), diff --git a/amm/interp/src/main/scala/ammonite/interp/script/ScriptProcessor.scala b/amm/interp/src/main/scala/ammonite/interp/script/ScriptProcessor.scala index c309618fc..7043d18da 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/ScriptProcessor.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/ScriptProcessor.scala @@ -2,7 +2,8 @@ package ammonite.interp.script import java.io.File -import ammonite.interp.{CodeWrapper, DependencyLoader, Interpreter, Parsers, Preprocessor} +import ammonite.compiler.iface.{CodeWrapper, Parser} +import ammonite.interp.{DependencyLoader, Interpreter} import ammonite.runtime.{Frame, ImportHook, Storage} import ammonite.util.{ImportTree, Name, Util} import ammonite.util.Util.CodeSource @@ -11,11 +12,12 @@ import coursierapi.{Dependency, Repository} import scala.collection.mutable final case class ScriptProcessor( + parser: Parser, + codeWrapper: CodeWrapper, dependencyLoader: DependencyLoader, defaultRepositories: Seq[Repository], extraPluginDependencies: Seq[Dependency] = Nil, wd: os.Path = os.pwd, - codeWrapper: CodeWrapper = CodeWrapper, importHooks: Map[Seq[String], ImportHook] = ImportHook.defaults ) { @@ -28,7 +30,7 @@ final case class ScriptProcessor( val rawCode = Interpreter.skipSheBangLine(code) lazy val offsetToPos = PositionOffsetConversion.offsetToPos(rawCode) - val splittedScript = Preprocessor.splitScriptWithStart( + val splittedScript = parser.scriptBlocksWithStartIndices( rawCode, codeSource.fileName ).left.map { f => @@ -38,8 +40,7 @@ final case class ScriptProcessor( val actualEndIdx = if (endIdx < 0) rawCode.length else endIdx offsetToPos(actualEndIdx) } - val expected = f.trace().failure.label - Seq(Diagnostic("ERROR", startPos, endPos, s"Expected $expected")) + Seq(Diagnostic("ERROR", startPos, endPos, s"Expected ${f.expected}")) } def hookFor(tree: ImportTree): Either[Diagnostic, (Seq[String], ImportHook)] = { @@ -82,9 +83,9 @@ final case class ScriptProcessor( val blocks = for { elems <- splittedScript.right.toSeq - (startIdx, leadingSpaces, statements) <- elems + Parser.ScriptBlock(startIdx, leadingSpaces, statements) <- elems } yield { - val (statements0, importTrees) = Parsers.parseImportHooksWithIndices(codeSource, statements) + val (statements0, importTrees) = parser.parseImportHooksWithIndices(codeSource, statements) val importResults = for { tree <- importTrees diff --git a/amm/interp/src/main/scala/ammonite/interp/script/SingleScriptCompiler.scala b/amm/interp/src/main/scala/ammonite/interp/script/SingleScriptCompiler.scala index 1e97b8d59..6445fc2a5 100644 --- a/amm/interp/src/main/scala/ammonite/interp/script/SingleScriptCompiler.scala +++ b/amm/interp/src/main/scala/ammonite/interp/script/SingleScriptCompiler.scala @@ -1,19 +1,12 @@ package ammonite.interp.script -import ammonite.interp.{ - CodeWrapper, - Compiler => AmmCompiler, - DefaultPreprocessor, - Interpreter, - MakeReporter -} -import ammonite.runtime.{Classpath, Frame, Storage} -import ammonite.util.{Imports, Name, Printer, Res} +import ammonite.compiler.iface.{CodeWrapper, Compiler, CompilerBuilder} +import ammonite.compiler.iface.Compiler.{Output => CompilerOutput} +import ammonite.interp.Interpreter +import ammonite.runtime.{Frame, Storage} +import ammonite.util.{Classpath, Imports, Name, Printer, Res} import scala.collection.mutable -import scala.reflect.internal.util.{NoPosition, Position => SPosition} -import scala.reflect.io.VirtualDirectory -import scala.tools.nsc.Settings /** * Helper class to compile a single script @@ -22,6 +15,7 @@ import scala.tools.nsc.Settings * discarded right after having called `apply` or `writeSources`. */ class SingleScriptCompiler( + compilerBuilder: CompilerBuilder, initialClassLoader: ClassLoader, storage: Storage, printer: Printer, @@ -30,28 +24,13 @@ class SingleScriptCompiler( codeWrapper: CodeWrapper, wd: Option[os.Path], generateSemanticDbs: Boolean, - settings: Settings, + settings: Seq[String], module: Script, dependencies: Script.ResolvedDependencies, moduleTarget: Option[os.Path], moduleSources: Option[os.Path] ) { - private val dynamicClasspath = { - val vd = new VirtualDirectory("(memory)", None) - AmmCompiler.addToClasspath(dependencies.byteCode, vd) - vd - } - - private val frame = { - val f = Frame.createInitial(initialClassLoader) - f.addClasspath(dependencies.jars.map(_.toNIO.toUri.toURL)) - f.addPluginClasspath(dependencies.pluginJars.map(_.toNIO.toUri.toURL)) - for ((clsName, byteCode) <- dependencies.byteCode) - f.classloader.addClassFile(clsName, byteCode) - f - } - private var messages = new mutable.ListBuffer[Diagnostic] private var newMessages = new mutable.ListBuffer[(String, Int, Int, String)] private def flushMessages(indexToPos: Int => Position): Unit = { @@ -64,43 +43,48 @@ class SingleScriptCompiler( newMessages.clear() } - private val compiler = { - - val reporter = { - def add(pos: SPosition, msg: String, severity: String) = - if (pos == NoPosition) - newMessages.append((severity, 0, 0, msg)) - else - newMessages.append((severity, pos.start, pos.end, msg)) - MakeReporter.makeReporter( - (pos, msg) => add(pos, msg, "ERROR"), - (pos, msg) => add(pos, msg, "WARNING"), - (pos, msg) => add(pos, msg, "INFO"), - settings - ) + private val compiler: Compiler = { + + val frame = { + val f = Frame.createInitial(initialClassLoader) + f.addClasspath(dependencies.jars.map(_.toNIO.toUri.toURL)) + f.addPluginClasspath(dependencies.pluginJars.map(_.toNIO.toUri.toURL)) + for ((clsName, byteCode) <- dependencies.byteCode) + f.classloader.addClassFile(clsName, byteCode) + f } - val initialClassPath = Classpath.classpath(initialClassLoader, storage) - val classPath = Classpath.classpath(frame.classloader, storage) + val reporter: CompilerBuilder.Message => Unit = { + msg => + newMessages.append((msg.severity, msg.start, msg.end, msg.message)) + } - AmmCompiler( + val initialClassPath = Classpath.classpath( + initialClassLoader, + storage.dirOpt.map(_.toNIO) + ) + val classPath = Classpath.classpath( + frame.classloader, + storage.dirOpt.map(_.toNIO) + ) + + compilerBuilder.create( + initialClassPath, classPath, - dynamicClasspath, + dependencies.byteCode, frame.classloader, frame.pluginClassloader, - () => (), Some(reporter), settings, classPathWhitelist, - initialClassPath, - lineNumberModifier = false + false ) } private val dependencyImports = initialImports ++ module.dependencyImports - private val preprocessor = new DefaultPreprocessor( - compiler.parse(module.codeSource.fileName, _), + private val preprocessor = compiler.preprocessor( + module.codeSource.fileName, markGeneratedSections = true ) @@ -189,7 +173,7 @@ class SingleScriptCompiler( scriptImports: Imports, block: Script.Block, blockIdx: Int - ): Res[(Imports, Int, String, AmmCompiler.Output)] = { + ): Res[(Imports, Int, String, Compiler.Output)] = { val indexedWrapperName = Interpreter.indexWrapperName( module.codeSource.wrapperName, @@ -257,8 +241,8 @@ class SingleScriptCompiler( } yield (scriptImports ++ output.imports, offset, processedCode, output) // :: acc) } - private def compileBlocks(): Res[Seq[(Int, String, AmmCompiler.Output)]] = { - val start = (Imports(), List.empty[(Int, String, AmmCompiler.Output)]) + private def compileBlocks(): Res[Seq[(Int, String, CompilerOutput)]] = { + val start = (Imports(), List.empty[(Int, String, CompilerOutput)]) val res = Res.fold(start, module.blocks.zipWithIndex) { case ((scriptImports, acc), (block, blockIdx)) => compileBlock(scriptImports, block, blockIdx).map { diff --git a/amm/repl/api/src/main/scala-2.12/ammonite/repl/api/History.scala b/amm/repl/api/src/main/scala-2.12/ammonite/repl/api/History.scala index 2efc88ea4..3030b2252 100644 --- a/amm/repl/api/src/main/scala-2.12/ammonite/repl/api/History.scala +++ b/amm/repl/api/src/main/scala-2.12/ammonite/repl/api/History.scala @@ -1,9 +1,5 @@ package ammonite.repl.api - -import ammonite.util._ -import ammonite.util.Util._ - import scala.collection.generic.CanBuildFrom import scala.collection.{IndexedSeqLike, mutable} diff --git a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala b/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala index a57b6028e..87431ca7a 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala +++ b/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala @@ -66,16 +66,6 @@ trait ReplAPI { */ def newCompiler(): Unit - /** - * Access the compiler to do crazy things if you really want to! - */ - def compiler: scala.tools.nsc.Global - - /** - * Access the presentation compiler to do even crazier things if you really want to! - */ - def interactiveCompiler: scala.tools.nsc.interactive.Global - /** * Shows all imports added that bring values into scope for the commands a * user runs; *includes* imports from the built-in predef and user predef files @@ -162,6 +152,8 @@ trait ReplAPI { def load: ReplLoad def clipboard: Clipboard + + def _compilerManager: ammonite.compiler.iface.CompilerLifecycleManager } trait ReplLoad{ /** diff --git a/amm/repl/api/src/main/scala/ammonite/repl/api/SourceAPI.scala b/amm/repl/api/src/main/scala/ammonite/repl/api/SourceAPI.scala deleted file mode 100644 index 54a1ea815..000000000 --- a/amm/repl/api/src/main/scala/ammonite/repl/api/SourceAPI.scala +++ /dev/null @@ -1,45 +0,0 @@ -package ammonite.repl.api - -import ammonite.interp.api.APIHolder -import ammonite.repl.tools.Desugared -import ammonite.util.CodeColors - -case class Location(fileName: String, lineNum: Int, fileContent: String) - -trait SourceAPI { - - def browseSourceCommand(targetLine: Int) = Seq("less", "+" + targetLine,"-RMN") - - def failLoudly[T](res: Either[String, T]): T = res match{ - case Left(s) => throw new Exception(s) - case Right(r) => r - } - - def loadObjectMemberInfo(symbolOwnerCls: Class[_], - value: Option[Any], - memberName: String, - returnType: Class[_], - argTypes: Class[_]*): Either[String, Location] - - def loadObjectInfo(value: Any): Either[String, Location] - - def browseObjectMember(symbolOwnerCls: Class[_], - value: Option[Any], - memberName: String, - pprinter: pprint.PPrinter, - colors: CodeColors, - command: Int => Seq[String], - returnType: Class[_], - argTypes: Class[_]*): Any - - def browseObject(value: Any, - pprinter: pprint.PPrinter, - colors: CodeColors, - command: Int => Seq[String]): Any - - def desugarImpl(s: String)(implicit colors: ammonite.util.CodeColors): Desugared - -} - -object SourceBridge extends APIHolder[SourceAPI] - diff --git a/amm/repl/src/main/scala/ammonite/repl/AmmoniteFrontEnd.scala b/amm/repl/src/main/scala/ammonite/repl/AmmoniteFrontEnd.scala index 1d9ab7408..357c4daef 100644 --- a/amm/repl/src/main/scala/ammonite/repl/AmmoniteFrontEnd.scala +++ b/amm/repl/src/main/scala/ammonite/repl/AmmoniteFrontEnd.scala @@ -8,8 +8,11 @@ import GUILikeFilters.SelectionFilter import ammonite.terminal._ import fastparse.Parsed import ammonite.util.{Colors, Res} -import ammonite.interp.{Parsers, Preprocessor} -case class AmmoniteFrontEnd(extraFilters: Filter = Filter.empty) extends FrontEnd{ +import ammonite.compiler.iface.Parser +case class AmmoniteFrontEnd( + parser: Parser, + extraFilters: Filter = Filter.empty +) extends FrontEnd{ def width = FrontEndUtils.width def height = FrontEndUtils.height @@ -26,13 +29,9 @@ case class AmmoniteFrontEnd(extraFilters: Filter = Filter.empty) extends FrontEn case None => Res.Exit(()) case Some(code) => addHistory(code) - fastparse.parse(code, Parsers.Splitter(_)) match{ - case Parsed.Success(value, idx) => - Res.Success((code, value.map(_._2))) - case f @ Parsed.Failure(_, index, extra) => - Res.Failure( - Preprocessor.formatFastparseError("(console)", code, f) - ) + parser.split(code, ignoreIncomplete = false).get match{ + case Right(value) => Res.Success((code, value)) + case Left(error) => Res.Failure(error) } } } @@ -55,7 +54,7 @@ case class AmmoniteFrontEnd(extraFilters: Filter = Filter.empty) extends FrontEn } val details2 = for (d <- details) yield { - Highlighter.defaultHighlight( + parser.defaultHighlight( d.toVector, colors.comment(), colors.`type`(), @@ -100,7 +99,7 @@ case class AmmoniteFrontEnd(extraFilters: Filter = Filter.empty) extends FrontEn // Enter val multilineFilter = Filter.action( SpecialKeys.NewLine, - ti => Parsers.split(ti.ts.buffer.mkString).isEmpty + ti => parser.split(ti.ts.buffer.mkString).isEmpty ){ case TermState(rest, b, c, _) => BasicFilters.injectNewLine(b, c, rest) } @@ -133,15 +132,14 @@ case class AmmoniteFrontEnd(extraFilters: Filter = Filter.empty) extends FrontEn displayTransform = { (buffer, cursor) => - val indices = Highlighter.defaultHighlightIndices( - buffer, + val highlighted = fansi.Str(parser.defaultHighlight( + buffer.toVector, colors.comment(), colors.`type`(), colors.literal(), colors.keyword(), fansi.Attr.Reset - ) - val highlighted = fansi.Str(Highlighter.flattenIndices(indices, buffer).mkString) + ).mkString) val (newBuffer, offset) = SelectionFilter.mangleBuffer( selectionFilter, highlighted, cursor, colors.selected() ) diff --git a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala index d21cd0034..d7bbba055 100644 --- a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala +++ b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala @@ -1,11 +1,10 @@ package ammonite.repl import ammonite.ops.Internals -import ammonite.repl.api.{Clipboard, FrontEnd, FrontEndAPI, Location, Session, SourceAPI} -import ammonite.repl.tools.{Desugared, SourceRuntime} +import ammonite.repl.api.{Clipboard, FrontEnd, FrontEndAPI, Session} import ammonite.runtime._ import ammonite.util.Util._ -import ammonite.util._ +import ammonite.util.{Frame => _, _} import java.awt.Toolkit import java.awt.datatransfer.{DataFlavor, StringSelection} @@ -138,76 +137,13 @@ object ClipboardImpl extends Clipboard { } } -trait SourceAPIImpl extends SourceAPI { - - def loadObjectMemberInfo(symbolOwnerCls: Class[_], - value: Option[Any], - memberName: String, - returnType: Class[_], - argTypes: Class[_]*): Either[String, Location] = - SourceRuntime.loadObjectMemberInfo( - symbolOwnerCls, - value, - memberName, - returnType, - argTypes: _* - ) - - def loadObjectInfo(value: Any): Either[String, Location] = - SourceRuntime.loadObjectInfo(value) - - def browseObjectMember(symbolOwnerCls: Class[_], - value: Option[Any], - memberName: String, - pprinter: pprint.PPrinter, - colors: CodeColors, - command: Int => Seq[String], - returnType: Class[_], - argTypes: Class[_]*): Any = - SourceRuntime.browseObjectMember( - symbolOwnerCls, - value, - memberName, - pprinter, - colors, - n => command(n), - returnType, - argTypes: _* - ) - - def browseObject(value: Any, - pprinter: pprint.PPrinter, - colors: CodeColors, - command: Int => Seq[String]): Any = - SourceRuntime.browseObject( - value, - pprinter, - colors, - n => command(n) - ) - - def desugarImpl(s: String)(implicit colors: ammonite.util.CodeColors): Desugared = { - - new Desugared( - ammonite.repl.Highlighter.defaultHighlight( - s.toVector, - colors.comment, - colors.`type`, - colors.literal, - colors.keyword, - fansi.Attr.Reset - ).mkString - ) - } - -} - trait FrontEndAPIImpl extends FrontEndAPI { + protected def parser: ammonite.compiler.iface.Parser def apply(name: String): FrontEnd = name.toLowerCase(Locale.ROOT) match { - case "ammonite" => AmmoniteFrontEnd() - case "windows" => FrontEnds.JLineWindows - case "unix" => FrontEnds.JLineUnix + case "ammonite" => AmmoniteFrontEnd(parser) + case "windows" => new FrontEnds.JLineWindows(parser) + case "unix" => new FrontEnds.JLineUnix(parser) case _ => throw new NoSuchElementException(s"Front-end $name") } } diff --git a/amm/repl/src/main/scala/ammonite/repl/FrontEndUtils.scala b/amm/repl/src/main/scala/ammonite/repl/FrontEndUtils.scala index 129e87060..5688cc0c5 100644 --- a/amm/repl/src/main/scala/ammonite/repl/FrontEndUtils.scala +++ b/amm/repl/src/main/scala/ammonite/repl/FrontEndUtils.scala @@ -6,11 +6,11 @@ import ammonite.util.Util.newLine * Created by haoyi on 8/29/15. */ object FrontEndUtils { - def width = - if (scala.util.Properties.isWin) ammonite.repl.FrontEnds.JLineWindows.width + def width = + if (scala.util.Properties.isWin) ammonite.repl.FrontEnds.width else ammonite.terminal.ConsoleDim.width() def height = - if (scala.util.Properties.isWin) ammonite.repl.FrontEnds.JLineWindows.width + if (scala.util.Properties.isWin) ammonite.repl.FrontEnds.height else ammonite.terminal.ConsoleDim.height() def tabulate(snippetsRaw: Seq[fansi.Str], width: Int): Iterator[String] = { val gap = 2 diff --git a/amm/repl/src/main/scala/ammonite/repl/FrontEnds.scala b/amm/repl/src/main/scala/ammonite/repl/FrontEnds.scala index a41f5fc3f..8f34d43a2 100644 --- a/amm/repl/src/main/scala/ammonite/repl/FrontEnds.scala +++ b/amm/repl/src/main/scala/ammonite/repl/FrontEnds.scala @@ -5,28 +5,28 @@ import java.io.{InputStream, OutputStream} import scala.collection.JavaConverters._ import fastparse.Parsed import fastparse.ParserInput -import org.jline.reader.{Highlighter => _, _} +import org.jline.reader._ import org.jline.reader.impl.history.DefaultHistory import org.jline.terminal._ import org.jline.utils.AttributedString +import ammonite.compiler.iface.{Parser => IParser} import ammonite.util.{Catching, Colors, Res} import ammonite.repl.api.FrontEnd -import ammonite.interp.{Parsers, Preprocessor} import org.jline.reader.impl.DefaultParser object FrontEnds { - object JLineUnix extends JLineTerm - object JLineWindows extends JLineTerm - class JLineTerm() extends FrontEnd { + class JLineUnix(codeParser: IParser) extends JLineTerm(codeParser) + class JLineWindows(codeParser: IParser) extends JLineTerm(codeParser) + class JLineTerm(codeParser: IParser) extends FrontEnd { private val term = TerminalBuilder.builder().build() private val readerBuilder = LineReaderBuilder.builder().terminal(term) - private val ammHighlighter = new AmmHighlighter() + private val ammHighlighter = new AmmHighlighter(codeParser) private val ammCompleter = new AmmCompleter(ammHighlighter) - private val ammParser = new AmmParser() + private val ammParser = new AmmParser(codeParser) readerBuilder.highlighter(ammHighlighter) readerBuilder.completer(ammCompleter) readerBuilder.parser(ammParser) @@ -79,9 +79,12 @@ object FrontEnds { } yield res } } + + def width = TerminalBuilder.builder().build().getWidth + def height = TerminalBuilder.builder().build().getHeight } -class AmmCompleter(highlighter: org.jline.reader.Highlighter) extends Completer { +class AmmCompleter(highlighter: Highlighter) extends Completer { // completion varies from action to action var compilerComplete: (Int, String) => (Int, Seq[String], Seq[String]) = (x, y) => (0, Seq.empty, Seq.empty) @@ -125,7 +128,7 @@ class AmmCompleter(highlighter: org.jline.reader.Highlighter) extends Completer } } -class AmmParser extends Parser { +class AmmParser(codeParser: IParser) extends Parser { class AmmoniteParsedLine(line: String, words: java.util.List[String], wordIndex: Int, wordCursor: Int, cursor: Int, val stmts: Seq[String] = Seq.empty // needed for interpreter @@ -142,9 +145,9 @@ class AmmParser extends Parser { val words = defParLine.words val wordIndex = defParLine.wordIndex // index of the current word in the list of words val wordCursor = defParLine.wordCursor // cursor position within the current word - - Parsers.split(line) match { - case Some(Parsed.Success(stmts, idx)) => + + codeParser.split(line) match { + case Some(Right(stmts)) => addHistory(line) // if ENTER and not at the end of input -> newline if (context == Parser.ParseContext.ACCEPT_LINE && cursor != line.length) { @@ -152,14 +155,12 @@ class AmmParser extends Parser { } else { new AmmoniteParsedLine(line, words, wordIndex, wordCursor, cursor, stmts) } - case Some(f @ Parsed.Failure(p, idx, extra)) => + case Some(Left(err)) => // we "accept the failure" only when ENTER is pressed, loops forever otherwise... // https://groups.google.com/d/msg/jline-users/84fPur0oHKQ/bRnjOJM4BAAJ if (context == Parser.ParseContext.ACCEPT_LINE) { addHistory(line) - throw new SyntaxError( - Preprocessor.formatFastparseError("(console)", line, f) - ) + throw new SyntaxError(err) } else { new AmmoniteParsedLine(line, words, wordIndex, wordCursor, cursor) } @@ -177,13 +178,13 @@ class AmmParser extends Parser { class SyntaxError(val msg: String) extends RuntimeException -class AmmHighlighter extends org.jline.reader.Highlighter { +class AmmHighlighter(codeParser: IParser) extends Highlighter { var colors: Colors = Colors.Default def setErrorIndex(x$1: Int): Unit = () def setErrorPattern(x$1: java.util.regex.Pattern): Unit = () override def highlight(reader: LineReader, buffer: String): AttributedString = { - val hl = Highlighter.defaultHighlight( + val hl = codeParser.defaultHighlight( buffer.toVector, colors.comment(), colors.`type`(), diff --git a/amm/repl/src/main/scala/ammonite/repl/Repl.scala b/amm/repl/src/main/scala/ammonite/repl/Repl.scala index 886a2cb81..e8d6826df 100644 --- a/amm/repl/src/main/scala/ammonite/repl/Repl.scala +++ b/amm/repl/src/main/scala/ammonite/repl/Repl.scala @@ -2,12 +2,13 @@ package ammonite.repl import java.io.{InputStream, InputStreamReader, OutputStream} -import ammonite.repl.api.{FrontEnd, FrontEndAPI, History, ReplLoad, SourceAPI} +import ammonite.repl.api.{FrontEnd, History, ReplLoad} import ammonite.runtime._ import ammonite.terminal.Filter import ammonite.util.Util.{newLine, normalizeNewlines} import ammonite.util._ -import ammonite.interp.{CodeWrapper, Interpreter, Parsers, Preprocessor} +import ammonite.compiler.iface.{CodeWrapper, CompilerBuilder, Parser} +import ammonite.interp.Interpreter import coursierapi.Dependency import scala.annotation.tailrec @@ -27,6 +28,8 @@ class Repl(input: InputStream, scriptCodeWrapper: CodeWrapper, alreadyLoadedDependencies: Seq[Dependency], importHooks: Map[Seq[String], ImportHook], + compilerBuilder: CompilerBuilder, + parser: Parser, initialClassLoader: ClassLoader = classOf[ammonite.repl.api.ReplAPI].getClassLoader, classPathWhitelist: Set[Seq[String]]) { repl => @@ -35,9 +38,9 @@ class Repl(input: InputStream, val frontEnd = Ref[FrontEnd]( if (scala.util.Properties.isWin) - ammonite.repl.FrontEnds.JLineWindows + new ammonite.repl.FrontEnds.JLineWindows(parser) else - AmmoniteFrontEnd(Filter.empty) + AmmoniteFrontEnd(parser, Filter.empty) ) var lastException: Throwable = null @@ -71,6 +74,8 @@ class Repl(input: InputStream, def usedEarlierDefinitions = frames().head.usedEarlierDefinitions val interp = new Interpreter( + compilerBuilder, + parser, printer, storage, wd, @@ -102,8 +107,6 @@ class Repl(input: InputStream, def fullHistory = storage.fullHistory() def history = repl.history def newCompiler() = interp.compilerManager.init(force = true) - def compiler = interp.compilerManager.compiler.compiler - def interactiveCompiler = interp.compilerManager.pressy.compiler def fullImports = repl.fullImports def imports = repl.imports def usedEarlierDefinitions = repl.usedEarlierDefinitions @@ -125,17 +128,16 @@ class Repl(input: InputStream, apply(normalizeNewlines(os.read(file))) } } + + def _compilerManager = interp.compilerManager } ), - ( - "ammonite.repl.api.SourceBridge", - "source", - new SourceAPIImpl {} - ), ( "ammonite.repl.api.FrontEndBridge", "frontEnd", - new FrontEndAPIImpl {} + new FrontEndAPIImpl { + def parser = repl.parser + } ) ) @@ -154,7 +156,7 @@ class Repl(input: InputStream, // running the user code directly. Could be made longer to better warm more // code paths, but then the fixed overhead gets larger so not really worth it val code = s"""val array = Seq.tabulate(10)(_*2).toArray.max""" - val stmts = Parsers.split(code).get.get.value + val stmts = parser.split(code).get.toOption.get interp.processLine(code, stmts, 9999999, silent = true, () => () /*donothing*/) } diff --git a/amm/repl/src/test/scala-2.12/ammonite/unit/SourceTests212.scala b/amm/repl/src/test/scala-2.12/ammonite/unit/SourceTests212.scala index 88547bed3..d40050427 100644 --- a/amm/repl/src/test/scala-2.12/ammonite/unit/SourceTests212.scala +++ b/amm/repl/src/test/scala-2.12/ammonite/unit/SourceTests212.scala @@ -1,17 +1,14 @@ package ammonite.unit -import ammonite.repl.SourceAPIImpl -import ammonite.repl.api.{Location, SourceBridge} import utest._ -import ammonite.repl.tools.source.load +import ammonite.compiler.tools.source.load import ammonite.util.Util +import ammonite.util.Util.Location //import fastparse.utils.{ElemSetHelper, Generator, IndexedParserInput} object SourceTests212 extends TestSuite{ val tests = Tests{ - if (SourceBridge.value0 == null) - SourceBridge.value0 = new SourceAPIImpl {} def check(loaded: Location, expectedFileName: String, expected: String, slop: Int = 10) = { diff --git a/amm/repl/src/test/scala/ammonite/DualTestRepl.scala b/amm/repl/src/test/scala/ammonite/DualTestRepl.scala index 22b481b0e..8726204eb 100644 --- a/amm/repl/src/test/scala/ammonite/DualTestRepl.scala +++ b/amm/repl/src/test/scala/ammonite/DualTestRepl.scala @@ -1,8 +1,6 @@ package ammonite -import ammonite.interp.CodeClassWrapper -import ammonite.repl.api.SourceBridge -import ammonite.repl.SourceAPIImpl +import ammonite.compiler.CodeClassWrapper import ammonite.util.{Evaluated, Res} /** @@ -10,9 +8,6 @@ import ammonite.util.{Evaluated, Res} */ class DualTestRepl { dual => - if (SourceBridge.value0 == null) - SourceBridge.value0 = new SourceAPIImpl {} - def predef: (String, Option[os.Path]) = ("", None) val repls = Seq( diff --git a/amm/repl/src/test/scala/ammonite/TestRepl.scala b/amm/repl/src/test/scala/ammonite/TestRepl.scala index 7a5c69640..718fc0ed9 100644 --- a/amm/repl/src/test/scala/ammonite/TestRepl.scala +++ b/amm/repl/src/test/scala/ammonite/TestRepl.scala @@ -2,7 +2,9 @@ package ammonite import java.io.PrintStream -import ammonite.interp.{CodeWrapper, Interpreter, Preprocessor} +import ammonite.compiler.DefaultCodeWrapper +import ammonite.compiler.iface.CodeWrapper +import ammonite.interp.Interpreter import ammonite.main.Defaults import ammonite.repl._ import ammonite.repl.api.{FrontEnd, History, ReplLoad} @@ -20,10 +22,10 @@ import ammonite.runtime.ImportHook * A test REPL which does not read from stdin or stdout files, but instead lets * you feed in lines or sessions programmatically and have it execute them. */ -class TestRepl { +class TestRepl { self => var allOutput = "" def predef: (String, Option[os.Path]) = ("", None) - def codeWrapper: CodeWrapper = CodeWrapper + def codeWrapper: CodeWrapper = DefaultCodeWrapper val tempDir = os.Path( java.nio.file.Files.createTempDirectory("ammonite-tester") @@ -61,9 +63,13 @@ class TestRepl { ) val customPredefs = Seq() + val parser = ammonite.compiler.Parsers + var currentLine = 0 val interp = try { new Interpreter( + ammonite.compiler.CompilerBuilder, + parser, printer0, storage = storage, wd = os.pwd, @@ -110,8 +116,6 @@ class TestRepl { def history = new History(Vector()) val colors = Ref(Colors.BlackWhite) def newCompiler() = interp.compilerManager.init(force = true) - def compiler = interp.compilerManager.compiler.compiler - def interactiveCompiler = interp.compilerManager.pressy.compiler def fullImports = interp.predefImports ++ imports def imports = frames().head.imports def usedEarlierDefinitions = frames().head.usedEarlierDefinitions @@ -153,12 +157,16 @@ class TestRepl { else super.print(value, ident, custom)(TPrint.implicitly[T], tcolors, classTagT) } + + def _compilerManager = interp.compilerManager } ), ( "ammonite.repl.api.FrontEndBridge", "frontEnd", - new FrontEndAPIImpl {} + new FrontEndAPIImpl { + def parser = self.parser + } ) ) @@ -215,7 +223,7 @@ class TestRepl { // ...except for the empty 0-line fragment, and the entire fragment, // both of which are complete. for (incomplete <- commandText.inits.toSeq.drop(1).dropRight(1)){ - assert(ammonite.interp.Parsers.split(incomplete.mkString(Util.newLine)).isEmpty) + assert(ammonite.compiler.Parsers.split(incomplete.mkString(Util.newLine)).isEmpty) } // Finally, actually run the complete command text through the @@ -320,7 +328,7 @@ class TestRepl { warningBuffer.clear() errorBuffer.clear() infoBuffer.clear() - val splitted = ammonite.interp.Parsers.split(input).get.get.value + val splitted = ammonite.compiler.Parsers.split(input).get.toOption.get val processed = interp.processLine( input, splitted, diff --git a/amm/repl/src/test/scala/ammonite/TestUtils.scala b/amm/repl/src/test/scala/ammonite/TestUtils.scala index fef31cd76..5ac954a55 100644 --- a/amm/repl/src/test/scala/ammonite/TestUtils.scala +++ b/amm/repl/src/test/scala/ammonite/TestUtils.scala @@ -2,7 +2,8 @@ package ammonite import java.io.PrintStream -import ammonite.interp.{CodeWrapper, Interpreter, Preprocessor} +import ammonite.compiler.DefaultCodeWrapper +import ammonite.interp.Interpreter import ammonite.main.Defaults import ammonite.runtime.{Frame, Storage} import ammonite.util._ @@ -23,6 +24,8 @@ object TestUtils { val startFrame = Frame.createInitial(initialClassLoader) val printStream = new PrintStream(System.out) val interp = new Interpreter( + ammonite.compiler.CompilerBuilder, + ammonite.compiler.Parsers, printer = Printer( printStream, new PrintStream(System.err), printStream, @@ -34,8 +37,8 @@ object TestUtils { getFrame = () => startFrame, createFrame = () => throw new Exception("unsupported"), initialClassLoader = initialClassLoader, - replCodeWrapper = CodeWrapper, - scriptCodeWrapper = CodeWrapper, + replCodeWrapper = DefaultCodeWrapper, + scriptCodeWrapper = DefaultCodeWrapper, alreadyLoadedDependencies = Defaults.alreadyLoadedDependencies("amm-test-dependencies.txt"), importHooks = ImportHook.defaults, classPathWhitelist = ammonite.repl.Repl.getClassPathWhitelist(thin = true) diff --git a/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala b/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala index e02b16f9a..0de5a9f81 100644 --- a/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/AdvancedTests.scala @@ -2,7 +2,7 @@ package ammonite.session import ammonite.TestUtils._ import ammonite.DualTestRepl -import ammonite.util.{Res, Util} +import ammonite.util.Res import utest._ @@ -373,7 +373,7 @@ object AdvancedTests extends TestSuite{ test("desugar"){ check.session(""" @ desugar{1 + 2 max 3} - res0: ammonite.repl.tools.Desugared = scala.Predef.intWrapper(3).max(3) + res0: compiler.tools.Desugared = scala.Predef.intWrapper(3).max(3) """) } test("loadingModulesInPredef"){ diff --git a/amm/repl/src/test/scala/ammonite/session/SerializationTests.scala b/amm/repl/src/test/scala/ammonite/session/SerializationTests.scala index 22f75d459..7cb544bb0 100644 --- a/amm/repl/src/test/scala/ammonite/session/SerializationTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/SerializationTests.scala @@ -1,7 +1,7 @@ package ammonite.session import ammonite.TestRepl -import ammonite.interp.CodeClassWrapper +import ammonite.compiler.CodeClassWrapper import utest._ object SerializationTests extends TestSuite{ diff --git a/amm/repl/src/test/scala/ammonite/unit/HighlightTests.scala b/amm/repl/src/test/scala/ammonite/unit/HighlightTests.scala index 125abc8de..5d9b6d131 100644 --- a/amm/repl/src/test/scala/ammonite/unit/HighlightTests.scala +++ b/amm/repl/src/test/scala/ammonite/unit/HighlightTests.scala @@ -1,11 +1,11 @@ package ammonite.unit -import ammonite.repl.Highlighter +import ammonite.compiler.Parsers import utest._ object HighlightTests extends TestSuite{ - def testHighlight(buffer: Vector[Char]) = Highlighter.defaultHighlight( + def testHighlight(buffer: Vector[Char]) = Parsers.defaultHighlight( buffer, fansi.Color.Blue, fansi.Color.Green, @@ -33,7 +33,7 @@ object HighlightTests extends TestSuite{ val paths = os.walk(os.pwd).filter(_.ext == "scala") for(path <- paths){ val code = os.read(path) - val out = Highlighter.defaultHighlight( + val out = Parsers.defaultHighlight( code.toVector, fansi.Underlined.On, fansi.Underlined.On, diff --git a/amm/repl/src/test/scala/ammonite/unit/ParserTests.scala b/amm/repl/src/test/scala/ammonite/unit/ParserTests.scala index 02c3a6b79..e3adcfe4c 100644 --- a/amm/repl/src/test/scala/ammonite/unit/ParserTests.scala +++ b/amm/repl/src/test/scala/ammonite/unit/ParserTests.scala @@ -1,6 +1,5 @@ package ammonite.unit -import ammonite.repl.Highlighter import ammonite.util.Util import utest._ @@ -68,14 +67,14 @@ object ParserTests extends TestSuite{ // Not nearly comprehensive, but hopefully if someone really borks this // somewhat-subtle logic around the parsers, one of these will catch it test("endOfCommandDetection"){ - def assertResult(x: String, pred: Option[fastparse.Parsed[_]] => Boolean) = { - val res = ammonite.interp.Parsers.split(x) + def assertResult(x: String, pred: Option[Either[String, _]] => Boolean) = { + val res = ammonite.compiler.Parsers.split(x) assert(pred(res)) } def assertIncomplete(x: String) = assertResult(x, _.isEmpty) def assertComplete(x: String) = assertResult(x, _.isDefined) def assertInvalid(x: String) = - assertResult(x, res => res.isDefined && res.get.isInstanceOf[fastparse.Parsed.Failure]) + assertResult(x, res => res.isDefined && res.get.isLeft) test("endOfCommand"){ test - assertComplete("{}") diff --git a/amm/repl/src/test/scala/ammonite/unit/SourceTests.scala b/amm/repl/src/test/scala/ammonite/unit/SourceTests.scala index 9f72626e2..be6df8e3e 100644 --- a/amm/repl/src/test/scala/ammonite/unit/SourceTests.scala +++ b/amm/repl/src/test/scala/ammonite/unit/SourceTests.scala @@ -1,19 +1,15 @@ package ammonite.unit -import ammonite.repl.SourceAPIImpl -import ammonite.repl.api.{Location, SourceBridge} import utest._ -import ammonite.repl.tools.source.load +import ammonite.compiler.tools.source.load import ammonite.util.Util +import ammonite.util.Util.Location import java.io.InputStream object SourceTests extends TestSuite{ val tests = Tests{ - if (SourceBridge.value0 == null) - SourceBridge.value0 = new SourceAPIImpl {} - def check(loaded: Location, expectedFileName: String, expected: String, slop: Int = 10) = { diff --git a/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala b/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala index da2bb5fb6..c9110af25 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala +++ b/amm/runtime/src/main/scala/ammonite/runtime/ClassLoaders.scala @@ -25,7 +25,7 @@ class Frame(val classloader: SpecialClassLoader, val pluginClassloader: SpecialClassLoader, private[this] var imports0: Imports, private[this] var classpath0: Seq[java.net.URL], - private[this] var usedEarlierDefinitions0: Seq[String]) extends ammonite.repl.api.Frame{ + private[this] var usedEarlierDefinitions0: Seq[String]) extends ammonite.util.Frame{ private var frozen0 = false def frozen = frozen0 def freeze(): Unit = { @@ -204,7 +204,7 @@ class SpecialClassLoader(parent: ClassLoader, parentSignature: Seq[(Either[String, java.net.URL], Long)], var specialLocalClasses: Set[String], urls: URL*) - extends ammonite.repl.api.ReplClassLoader(urls.toArray, parent){ + extends ammonite.util.ReplClassLoader(urls.toArray, parent){ /** * Files which have been compiled, stored so that our special diff --git a/amm/runtime/src/main/scala/ammonite/runtime/Evaluator.scala b/amm/runtime/src/main/scala/ammonite/runtime/Evaluator.scala index c62e4c31c..d9ad16183 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/Evaluator.scala +++ b/amm/runtime/src/main/scala/ammonite/runtime/Evaluator.scala @@ -5,7 +5,7 @@ import java.lang.reflect.InvocationTargetException import ammonite._ import ammonite.interp.api.AmmoniteExit import util.Util.{ClassFiles, newLine} -import ammonite.util._ +import ammonite.util.{Frame => _, _} import scala.util.Try diff --git a/amm/runtime/src/main/scala/ammonite/runtime/Storage.scala b/amm/runtime/src/main/scala/ammonite/runtime/Storage.scala index c200e45b9..a63165fa8 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/Storage.scala +++ b/amm/runtime/src/main/scala/ammonite/runtime/Storage.scala @@ -31,6 +31,8 @@ trait Storage{ tag: Tag): Unit def classFilesListLoad(filePathPrefix: os.SubPath, tag: Tag): Option[ScriptOutput] def getSessionId: Long + + def dirOpt: Option[os.Path] = None } object Storage{ @@ -339,5 +341,7 @@ object Storage{ try Some((os.read(predef), predef)) catch { case e: java.nio.file.NoSuchFileException => Some(("", predef))} } + + override def dirOpt: Option[os.Path] = Some(dir) } } diff --git a/amm/src/main/scala/ammonite/Main.scala b/amm/src/main/scala/ammonite/Main.scala index ea8cec587..a9867b030 100644 --- a/amm/src/main/scala/ammonite/Main.scala +++ b/amm/src/main/scala/ammonite/Main.scala @@ -4,11 +4,13 @@ import java.io.{InputStream, OutputStream, PrintStream} import java.net.URLClassLoader import java.nio.file.NoSuchFileException -import ammonite.interp.{Watchable, CodeClassWrapper, CodeWrapper, Interpreter, PredefInitialization} +import ammonite.compiler.{CodeClassWrapper, DefaultCodeWrapper} +import ammonite.compiler.iface.{CodeWrapper, CompilerBuilder, Parser} +import ammonite.interp.{Watchable, Interpreter, PredefInitialization} import ammonite.interp.script.AmmoniteBuildServer import ammonite.runtime.{Frame, Storage} import ammonite.main._ -import ammonite.repl.{FrontEndAPIImpl, Repl, SourceAPIImpl} +import ammonite.repl.{FrontEndAPIImpl, Repl} import ammonite.util.Util.newLine import ammonite.util._ @@ -69,11 +71,14 @@ case class Main(predefCode: String = "", verboseOutput: Boolean = true, remoteLogging: Boolean = true, colors: Colors = Colors.Default, - replCodeWrapper: CodeWrapper = CodeWrapper, - scriptCodeWrapper: CodeWrapper = CodeWrapper, + replCodeWrapper: CodeWrapper = DefaultCodeWrapper, + scriptCodeWrapper: CodeWrapper = DefaultCodeWrapper, alreadyLoadedDependencies: Seq[Dependency] = Defaults.alreadyLoadedDependencies(), importHooks: Map[Seq[String], ImportHook] = ImportHook.defaults, + compilerBuilder: CompilerBuilder = ammonite.compiler.CompilerBuilder, + // by-name, so that fastparse isn't loaded when we don't need it + parser: () => Parser = () => ammonite.compiler.Parsers, classPathWhitelist: Set[Seq[String]] = Set.empty){ def loadedPredefFile = predefFile match{ @@ -135,6 +140,8 @@ case class Main(predefCode: String = "", scriptCodeWrapper = scriptCodeWrapper, alreadyLoadedDependencies = alreadyLoadedDependencies, importHooks = importHooks, + compilerBuilder = compilerBuilder, + parser = parser(), initialClassLoader = initialClassLoader, classPathWhitelist = classPathWhitelist ) @@ -159,7 +166,10 @@ case class Main(predefCode: String = "", val customPredefs = predefFileInfoOpt.toSeq ++ Seq( PredefInfo(Name("CodePredef"), predefCode, false, None) ) + lazy val parser0 = parser() val interp = new Interpreter( + ammonite.compiler.CompilerBuilder, + parser0, printer, storageBackend, wd, @@ -175,15 +185,12 @@ case class Main(predefCode: String = "", classPathWhitelist = classPathWhitelist ) val bridges = Seq( - ( - "ammonite.repl.api.SourceBridge", - "source", - new SourceAPIImpl {} - ), ( "ammonite.repl.api.FrontEndBridge", "frontEnd", - new FrontEndAPIImpl {} + new FrontEndAPIImpl { + def parser = parser0 + } ) ) interp.initializePredef(Seq(), customPredefs, bridges, augmentedImports) match{ @@ -294,6 +301,9 @@ object Main{ case Right(cliConfig) => if (cliConfig.core.bsp.value) { val buildServer = new AmmoniteBuildServer( + ???, + ammonite.compiler.Parsers, + ammonite.compiler.DefaultCodeWrapper, initialScripts = cliConfig.rest.map(os.Path(_)), initialImports = PredefInitialization.initBridges( Seq("ammonite.interp.api.InterpBridge" -> "interp") @@ -452,9 +462,10 @@ class MainRunner(cliConfig: Config, new Storage.Folder(cliConfig.core.home, isRepl) } + lazy val parser = ammonite.compiler.Parsers val codeWrapper = if (cliConfig.repl.classBased.value) CodeClassWrapper - else CodeWrapper + else DefaultCodeWrapper Main( cliConfig.predef.predefCode, @@ -471,6 +482,7 @@ class MainRunner(cliConfig: Config, colors = colors, replCodeWrapper = codeWrapper, scriptCodeWrapper = codeWrapper, + parser = () => parser, alreadyLoadedDependencies = Defaults.alreadyLoadedDependencies(), classPathWhitelist = ammonite.repl.Repl.getClassPathWhitelist(cliConfig.core.thin.value) diff --git a/amm/src/main/scala/ammonite/main/Scripts.scala b/amm/src/main/scala/ammonite/main/Scripts.scala index 803fdfb6f..9a5cc6303 100644 --- a/amm/src/main/scala/ammonite/main/Scripts.scala +++ b/amm/src/main/scala/ammonite/main/Scripts.scala @@ -50,7 +50,7 @@ object Scripts { } scriptMains = interp.scriptCodeWrapper match{ - case ammonite.interp.CodeWrapper => + case ammonite.compiler.DefaultCodeWrapper => Some( interp .evalClassloader @@ -61,7 +61,7 @@ object Scripts { .apply() ) - case ammonite.interp.CodeClassWrapper => + case ammonite.compiler.CodeClassWrapper => val outer = interp .evalClassloader .loadClass(routeClsName) diff --git a/amm/src/test/scala/ammonite/interp/CachingTests.scala b/amm/src/test/scala/ammonite/interp/CachingTests.scala index 4f8faa182..85dfddeb7 100644 --- a/amm/src/test/scala/ammonite/interp/CachingTests.scala +++ b/amm/src/test/scala/ammonite/interp/CachingTests.scala @@ -159,8 +159,14 @@ object CachingTests extends TestSuite{ java.nio.file.Files.createTempDirectory("ammonite-tester-x") ) - val interp1 = createTestInterp(new Storage.Folder(tempDir)) - val interp2 = createTestInterp(new Storage.Folder(tempDir)) + val interp1 = createTestInterp( + new Storage.Folder(tempDir), + predefImports = Interpreter.predefImports + ) + val interp2 = createTestInterp( + new Storage.Folder(tempDir), + predefImports = Interpreter.predefImports + ) runScript(os.pwd, scriptPath/"cachedCompilerInit.sc", interp1) runScript(os.pwd, scriptPath/"cachedCompilerInit.sc", interp2) @@ -182,7 +188,7 @@ object CachingTests extends TestSuite{ """) val scriptFile = os.temp("""div("<('.'<)", y).render""") - def processAndCheckCompiler(f: ammonite.interp.Compiler => Boolean) ={ + def processAndCheckCompiler(f: ammonite.compiler.iface.Compiler => Boolean) ={ val interp = createTestInterp( new Storage.Folder(tempDir){ override val predef = predefFile diff --git a/amm/src/test/scala/ammonite/interp/CompilerSettingsTests.scala b/amm/src/test/scala/ammonite/interp/CompilerSettingsTests.scala index b07fd0396..00e8b2baf 100644 --- a/amm/src/test/scala/ammonite/interp/CompilerSettingsTests.scala +++ b/amm/src/test/scala/ammonite/interp/CompilerSettingsTests.scala @@ -23,7 +23,14 @@ object CompilerSettingsTests extends TestSuite { val interp = createTestInterp(storage) Scripts.runScript(os.pwd, scriptPath / "configureCompiler.sc", interp) - assert(interp.compilerManager.compiler.compiler.useOffsetPositions) + assert( + interp + .compilerManager + .asInstanceOf[ammonite.compiler.CompilerLifecycleManager] + .compiler + .compiler + .useOffsetPositions + ) } } @@ -32,10 +39,20 @@ object CompilerSettingsTests extends TestSuite { // which is called BEFORE the compiler instantiates, resulting in // useOffsetPositions initializing as false, as expected val storage = Storage.InMemory() - val interp = createTestInterp(storage) + val interp = createTestInterp( + storage, + predefImports = Interpreter.predefImports + ) Scripts.runScript(os.pwd, scriptPath / "preConfigureCompiler.sc", interp) - assert(!interp.compilerManager.compiler.compiler.useOffsetPositions) + assert( + !interp + .compilerManager + .asInstanceOf[ammonite.compiler.CompilerLifecycleManager] + .compiler + .compiler + .useOffsetPositions + ) } } } diff --git a/amm/src/test/scala/ammonite/interp/YRangeposTests.scala b/amm/src/test/scala/ammonite/interp/YRangeposTests.scala index 93c781e40..9e0cc3a51 100644 --- a/amm/src/test/scala/ammonite/interp/YRangeposTests.scala +++ b/amm/src/test/scala/ammonite/interp/YRangeposTests.scala @@ -6,8 +6,6 @@ import ammonite.runtime.Storage import ammonite.main._ import utest._ -import scala.tools.nsc.Global - object YRangeposTests extends TestSuite { val tests = Tests { println("YRangeposTests") @@ -25,7 +23,10 @@ object YRangeposTests extends TestSuite { // This tests shows that enabling Yrangepos does not mess with ammonite's // behaviour. The compiler not crashing is the test itself. val storage = Storage.InMemory() - val interp = createTestInterp(storage) + val interp = createTestInterp( + storage, + predefImports = Interpreter.predefImports + ) val res = Scripts.runScript(os.pwd, scriptFolderPath / "yRangepos.sc", interp) assert(res.isSuccess) } diff --git a/amm/src/test/scala/ammonite/interp/script/AmmoniteBuildServerTests.scala b/amm/src/test/scala/ammonite/interp/script/AmmoniteBuildServerTests.scala index d8b540874..429bc830f 100644 --- a/amm/src/test/scala/ammonite/interp/script/AmmoniteBuildServerTests.scala +++ b/amm/src/test/scala/ammonite/interp/script/AmmoniteBuildServerTests.scala @@ -670,7 +670,12 @@ object AmmoniteBuildServerTests extends TestSuite { def this(script: os.Path*) = this(wd, script) - val server = new AmmoniteBuildServer(initialScripts = script) + val server = new AmmoniteBuildServer( + ammonite.compiler.CompilerBuilder, + ammonite.compiler.Parsers, + ammonite.compiler.DefaultCodeWrapper, + initialScripts = script + ) val client = new TestBuildClient server.onConnectWithClient(client) diff --git a/amm/runtime/src/main/java/io/github/retronym/java9rtexport/Copy.java b/amm/util/src/main/java/io/github/retronym/java9rtexport/Copy.java similarity index 100% rename from amm/runtime/src/main/java/io/github/retronym/java9rtexport/Copy.java rename to amm/util/src/main/java/io/github/retronym/java9rtexport/Copy.java diff --git a/amm/runtime/src/main/java/io/github/retronym/java9rtexport/Export.java b/amm/util/src/main/java/io/github/retronym/java9rtexport/Export.java similarity index 100% rename from amm/runtime/src/main/java/io/github/retronym/java9rtexport/Export.java rename to amm/util/src/main/java/io/github/retronym/java9rtexport/Export.java diff --git a/amm/runtime/src/main/scala/ammonite/runtime/Classpath.scala b/amm/util/src/main/scala/ammonite/util/Classpath.scala similarity index 83% rename from amm/runtime/src/main/scala/ammonite/runtime/Classpath.scala rename to amm/util/src/main/scala/ammonite/util/Classpath.scala index 71c85f8f1..31ef8d239 100644 --- a/amm/runtime/src/main/scala/ammonite/runtime/Classpath.scala +++ b/amm/util/src/main/scala/ammonite/util/Classpath.scala @@ -1,7 +1,8 @@ -package ammonite.runtime +package ammonite.util import java.io.File import java.net.URL +import java.nio.file.{Path, Paths} import java.util.zip.{ZipFile, ZipInputStream} @@ -25,16 +26,14 @@ object Classpath { * memory but is better than reaching all over the filesystem every time we * want to do something. */ - def classpath(classLoader: ClassLoader, storage: Storage): Vector[URL] = { - def rtCacheDir(storage: Storage): Option[os.Path] = storage match { - case storage: Storage.Folder => - // no need to cache if the storage is in tmpdir - // because it is temporary - if (storage.dir.wrapped.startsWith( - java.nio.file.Paths.get(System.getProperty("java.io.tmpdir")))) - None - else Some(storage.dir) - case _ => None + def classpath( + classLoader: ClassLoader, + rtCacheDir: Option[Path] + ): Vector[URL] = { + lazy val actualRTCacheDir = rtCacheDir.filter { dir => + // no need to cache if the storage is in tmpdir + // because it is temporary + !dir.startsWith(Paths.get(System.getProperty("java.io.tmpdir"))) } var current = classLoader @@ -73,8 +72,8 @@ object Classpath { .loadClass("javax.script.ScriptEngineManager") } catch { case _: ClassNotFoundException => - rtCacheDir(storage) match { - case Some(path) => files.append(Export.rtAt(path.toIO).toURI.toURL) + actualRTCacheDir match { + case Some(path) => files.append(Export.rtAt(path.toFile).toURI.toURL) case _ => files.append(Export.rt().toURI.toURL) } } diff --git a/amm/repl/api/src/main/scala/ammonite/repl/api/Frame.scala b/amm/util/src/main/scala/ammonite/util/Frame.scala similarity index 77% rename from amm/repl/api/src/main/scala/ammonite/repl/api/Frame.scala rename to amm/util/src/main/scala/ammonite/util/Frame.scala index 79f733be7..8d0a8af46 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/api/Frame.scala +++ b/amm/util/src/main/scala/ammonite/util/Frame.scala @@ -1,4 +1,4 @@ -package ammonite.repl.api +package ammonite.util import java.net.URL @@ -7,4 +7,5 @@ trait Frame { def pluginClassloader: ReplClassLoader def classpath: Seq[URL] + def version: Int } diff --git a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplClassLoader.scala b/amm/util/src/main/scala/ammonite/util/ReplClassLoader.scala similarity index 88% rename from amm/repl/api/src/main/scala/ammonite/repl/api/ReplClassLoader.scala rename to amm/util/src/main/scala/ammonite/util/ReplClassLoader.scala index 08e5d76c7..9db1d33c8 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplClassLoader.scala +++ b/amm/util/src/main/scala/ammonite/util/ReplClassLoader.scala @@ -1,4 +1,4 @@ -package ammonite.repl.api +package ammonite.util import java.net.{URL, URLClassLoader} diff --git a/amm/util/src/main/scala/ammonite/util/Util.scala b/amm/util/src/main/scala/ammonite/util/Util.scala index 8321c9236..d353f9f31 100644 --- a/amm/util/src/main/scala/ammonite/util/Util.scala +++ b/amm/util/src/main/scala/ammonite/util/Util.scala @@ -126,4 +126,6 @@ object Util{ transpose(xs, Nil).reverse } -} \ No newline at end of file + + case class Location(fileName: String, lineNum: Int, fileContent: String) +} diff --git a/build.sc b/build.sc index aa9a0acd5..70809c978 100644 --- a/build.sc +++ b/build.sc @@ -103,12 +103,6 @@ trait AmmModule extends AmmInternalModule with PublishModule{ ) ) - def transitiveSources: T[Seq[PathRef]] = T{ - mill.define.Task.traverse(this +: moduleDeps)(m => - T.task{m.sources()} - )().flatten - } - def transitiveJars: T[Agg[PathRef]] = T{ mill.define.Task.traverse(this +: moduleDeps)(m => T.task{m.jar()} @@ -123,7 +117,6 @@ trait AmmModule extends AmmInternalModule with PublishModule{ } trait AmmDependenciesResourceFileModule extends JavaModule{ - def crossScalaVersion: String def dependencyResourceFileName: String def dependencyFileResources = T{ val deps0 = T.task{compileIvyDeps() ++ transitiveIvyDeps()}() @@ -136,7 +129,6 @@ trait AmmDependenciesResourceFileModule extends JavaModule{ Seq(PathRef(generateDependenciesFile( - crossScalaVersion, dependencyResourceFileName, res.minDependencies.toSeq ))) @@ -194,29 +186,59 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ ) } + object compiler extends Cross[CompilerModule](fullCrossScalaVersions:_*) { + object interface extends Cross[CompilerInterfaceModule](fullCrossScalaVersions:_*) + class CompilerInterfaceModule(val crossScalaVersion: String) extends AmmModule{ + def artifactName = "ammonite-compiler-interface" + def moduleDeps = Seq(amm.util()) + def exposedClassPath = T{ + runClasspath() ++ + externalSources() ++ + transitiveJars() ++ + transitiveSourceJars() + } + } + } + class CompilerModule(val crossScalaVersion: String) extends AmmModule{ + def moduleDeps = Seq(amm.compiler.interface(), amm.util(), amm.repl.api()) + def crossFullScalaVersion = true + def ivyDeps = T { + Agg( + ivy"org.scala-lang:scala-compiler:${scalaVersion()}", + ivy"com.lihaoyi::scalaparse:2.3.0", + ivy"org.scala-lang.modules::scala-xml:2.0.0-M3", + ivy"org.javassist:javassist:3.21.0-GA", + ivy"com.github.javaparser:javaparser-core:3.2.5" + ) + } + + def exposedClassPath = T{ + runClasspath() ++ + externalSources() ++ + transitiveJars() ++ + transitiveSourceJars() + } + } + object interp extends Cross[InterpModule](fullCrossScalaVersions:_*){ object api extends Cross[InterpApiModule](fullCrossScalaVersions:_*) class InterpApiModule(val crossScalaVersion: String) extends AmmModule with AmmDependenciesResourceFileModule{ - def moduleDeps = Seq(ops(), amm.util()) + def moduleDeps = Seq(amm.compiler.interface(), ops(), amm.util()) def crossFullScalaVersion = true def dependencyResourceFileName = "amm-interp-api-dependencies.txt" def ivyDeps = Agg( - ivy"org.scala-lang:scala-compiler:$crossScalaVersion", ivy"org.scala-lang:scala-reflect:$crossScalaVersion", ivy"io.get-coursier:interface:0.0.21" ) } } class InterpModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops(), amm.util(), amm.runtime()) + def moduleDeps = Seq(ops(), amm.util(), amm.runtime(), amm.compiler.interface()) def crossFullScalaVersion = true def ivyDeps = Agg( ivy"ch.epfl.scala:bsp4j:$bspVersion", ivy"org.scalameta::trees:4.4.6", - ivy"org.scala-lang:scala-compiler:$crossScalaVersion", ivy"org.scala-lang:scala-reflect:$crossScalaVersion", - ivy"com.lihaoyi::scalaparse:2.3.0", - ivy"org.javassist:javassist:3.21.0-GA", ivy"org.scala-lang.modules::scala-xml:1.2.0" ) } @@ -260,13 +282,13 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ def moduleDeps = Seq( ops(), amm.util(), amm.runtime(), amm.interp(), - terminal() + terminal(), + amm.compiler.interface() ) def ivyDeps = Agg( ivy"org.jline:jline-terminal:3.14.1", ivy"org.jline:jline-terminal-jna:3.14.1", ivy"org.jline:jline-reader:3.14.1", - ivy"com.github.javaparser:javaparser-core:3.2.5", // ivy"com.github.scopt::scopt:3.7.1" ) @@ -274,10 +296,12 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ def crossScalaVersion = ReplModule.this.crossScalaVersion def scalaVersion = ReplModule.this.crossScalaVersion def dependencyResourceFileName = "amm-test-dependencies.txt" + def moduleDeps = super.moduleDeps ++ Seq(amm.compiler()) def thinWhitelist = T{ generateApiWhitelist( amm.repl.api().exposedClassPath() ++ + amm.compiler().exposedClassPath() ++ Seq(compile().classes) ++ resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() ) @@ -293,7 +317,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ ReplModule.this.externalSources() ++ resolveDeps(ivyDeps, sources = true)()).distinct } - def ivyDeps = super.ivyDeps() ++ Agg( + def ivyDeps = super.ivyDeps() ++ amm.compiler().ivyDeps() ++ Agg( ivy"org.scalaz::scalaz-core:7.2.27" ) } @@ -314,7 +338,8 @@ class MainModule(val crossScalaVersion: String) amm.util(), amm.runtime(), amm.interp.api(), amm.repl.api(), - amm.interp(), amm.repl() + amm.interp(), amm.repl(), + amm.compiler() ) def runClasspath = @@ -342,7 +367,8 @@ class MainModule(val crossScalaVersion: String) def thinWhitelist = T{ generateApiWhitelist( - amm.repl.api().exposedClassPath() + amm.repl.api().exposedClassPath() ++ + amm.compiler().exposedClassPath() ) } def localClasspath = T{ @@ -394,6 +420,7 @@ class MainModule(val crossScalaVersion: String) def thinWhitelist = T{ generateApiWhitelist( amm.repl.api().exposedClassPath() ++ + amm.compiler().exposedClassPath() ++ Seq(amm.repl().test.compile().classes, compile().classes) ++ resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() ) @@ -455,6 +482,7 @@ class ShellModule(val crossScalaVersion: String) extends AmmModule{ def thinWhitelist = T{ generateApiWhitelist( amm.repl.api().exposedClassPath() ++ + amm.compiler().exposedClassPath() ++ Seq(amm.repl().test.compile().classes, compile().classes) ++ resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() ) @@ -548,8 +576,7 @@ def generateConstantsFile(version: String = buildVersion, ctx.dest/"Constants.scala" } -def generateDependenciesFile(scalaVersion: String, - fileName: String, +def generateDependenciesFile(fileName: String, deps: Seq[coursier.Dependency]) (implicit ctx: mill.util.Ctx.Dest) = { diff --git a/shell/src/main/scala/ammonite/shell/Configure.scala b/shell/src/main/scala/ammonite/shell/Configure.scala index e2c3f5b43..446d26625 100644 --- a/shell/src/main/scala/ammonite/shell/Configure.scala +++ b/shell/src/main/scala/ammonite/shell/Configure.scala @@ -10,10 +10,11 @@ object Configure { def apply(interp: InterpAPI, repl: ReplAPI, wd: => ammonite.ops.Path) = { if (scala.util.Properties.isWin) { - repl.frontEnd() = ammonite.repl.FrontEnds.JLineWindows + repl.frontEnd() = new ammonite.repl.FrontEnds.JLineWindows(ammonite.compiler.Parsers) interp.colors() = ammonite.util.Colors.BlackWhite } else { repl.frontEnd() = ammonite.repl.AmmoniteFrontEnd( + ammonite.compiler.Parsers, ammonite.shell.PathComplete.pathCompleteFilter(wd, interp.colors()) ) } diff --git a/shell/src/main/scala/ammonite/shell/PathComplete.scala b/shell/src/main/scala/ammonite/shell/PathComplete.scala index 33583dad1..5ee0735c5 100644 --- a/shell/src/main/scala/ammonite/shell/PathComplete.scala +++ b/shell/src/main/scala/ammonite/shell/PathComplete.scala @@ -4,9 +4,9 @@ import java.io.OutputStreamWriter import ammonite.terminal._ import Filter._ -import ammonite.repl.{FrontEndUtils, Highlighter} +import ammonite.repl.FrontEndUtils import ammonite.util.Colors -import ammonite.interp.Parsers +import ammonite.compiler.{Highlighter, Parsers} import ammonite.terminal._ import ammonite.terminal.LazyList.~: /**