diff --git a/collection-strawman b/collection-strawman index abd20a60f730..052a7d26ff33 160000 --- a/collection-strawman +++ b/collection-strawman @@ -1 +1 @@ -Subproject commit abd20a60f730ef478551ee68ed4326f8bf582dd8 +Subproject commit 052a7d26ff337824fb865d6c59eecf3dfe174bb6 diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 89e6a868eed0..3bc5d8a280f4 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -11,8 +11,13 @@ import dotc.reporting._ import results._ +sealed trait Parsing + /** A parsing result from string input */ -sealed trait ParseResult +sealed trait ParseResult extends Parsing + +/** Suppress visual output when this is passed */ +case class Silent(underlying: ParseResult) extends Parsing /** An error free parsing resulting in a list of untyped trees */ case class Parsed(sourceCode: String, trees: List[untpd.Tree]) extends ParseResult diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 858e2338b9e0..0ec6024442d1 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -1,33 +1,31 @@ package dotty.tools package repl -import java.io.{ InputStream, PrintStream } +import java.io.{InputStream, PrintStream} import scala.annotation.tailrec - import dotc.reporting.MessageRendering import dotc.reporting.diagnostic.MessageContainer import dotc.ast.untpd import dotc.ast.tpd -import dotc.interactive.{ SourceTree, Interactive } +import dotc.interactive.{Interactive, SourceTree} import dotc.core.Contexts.Context -import dotc.{ CompilationUnit, Run } -import dotc.core.Mode +import dotc.{CompilationUnit, Run} +import dotc.core.{Denotations, Mode} import dotc.core.Flags._ import dotc.core.Types._ import dotc.core.StdNames._ import dotc.core.Names.Name import dotc.core.NameOps._ -import dotc.core.Symbols.{ Symbol, NoSymbol, defn } +import dotc.core.Symbols.{NoSymbol, Symbol, defn} import dotc.core.Denotations.Denotation -import dotc.core.Types.{ ExprType, ConstantType } +import dotc.core.Types.{ConstantType, ExprType} import dotc.core.NameKinds.SimpleNameKind import dotc.config.CompilerCommand -import dotc.{ Compiler, Driver } +import dotc.{Compiler, Driver} import dotc.printing.SyntaxHighlighting import dotc.util.Positions.Position import io._ - import AmmoniteReader._ import results._ @@ -79,7 +77,9 @@ case class Completions(cursor: Int, /** Main REPL instance, orchestrating input, compilation and presentation */ class ReplDriver(settings: Array[String], protected val out: PrintStream = System.out, - protected val classLoader: Option[ClassLoader] = None) extends Driver { + protected val classLoader: Option[ClassLoader] = None, + initialCommands: Option[String] = None, + cleanupCommands: Option[String] = None) extends Driver { /** Overridden to `false` in order to not have to give sources on the * commandline @@ -124,24 +124,30 @@ class ReplDriver(settings: Array[String], resetToInitial() /** Run REPL with `state` until `:quit` command found - * - * This method is the main entry point into the REPL. Its effects are not - * observable outside of the CLI, for this reason, most helper methods are - * `protected final` to facilitate testing. - */ - @tailrec final def runUntilQuit(state: State = initState): State = { - val res = readLine()(state) - - if (res == Quit) { - out.println() - state - } - else { - // readLine potentially destroys the run, so a new one is needed for the - // rest of the interpretation: - implicit val freshState = state.newRun(compiler, rootCtx) - runUntilQuit(interpret(res)) + * + * This method is the main entry point into the REPL. Its effects are not + * observable outside of the CLI, for this reason, most helper methods are + * `protected final` to facilitate testing. + */ + final def runUntilQuit(initialState: State = initState): State = { + @tailrec def run(state: State = initState): State = { + val res = readLine()(state) + + if (res == Quit) { + out.println() + state + } + else { + // readLine potentially destroys the run, so a new one is needed for the + // rest of the interpretation: + implicit val freshState = state.newRun(compiler, rootCtx) + run(interpret(res)) + } } + + val state = runBootstrapCommands(initialCommands)(initialState) + val userState = run(state) + runBootstrapCommands(cleanupCommands)(userState) } final def run(input: String)(implicit state: State): State = @@ -150,6 +156,12 @@ class ReplDriver(settings: Array[String], final def run(res: ParseResult)(implicit state: State): State = interpret(res) + final def runBootstrapCommands(cmds: Option[String])(implicit state: State): State = { + cmds.map(ParseResult.apply(_)(rootCtx)).map(Silent.apply(_)).foldLeft(state) { (s, cmd) => + interpret(cmd)(s) + } + } + /** Extract possible completions at the index of `cursor` in `expr` */ protected[this] final def completions(cursor: Int, expr: String, state0: State): Completions = { // TODO move some of this logic to `Interactive` @@ -184,10 +196,15 @@ class ReplDriver(settings: Array[String], private def extractImports(trees: List[untpd.Tree])(implicit context: Context): List[(untpd.Import, String)] = trees.collect { case imp: untpd.Import => (imp, imp.show) } - private def interpret(res: ParseResult)(implicit state: State): State = - res match { + private def interpret(res: Parsing)(implicit state: State): State = { + val (parseResult, isSilent) = res match { + case Silent(x) => (x, true) + case x: ParseResult => (x, false) + } + + parseResult match { case parsed: Parsed if parsed.trees.nonEmpty => - compile(parsed) + compile(parsed, isSilent) .withHistory(parsed.sourceCode :: state.history) .newRun(compiler, rootCtx) @@ -195,7 +212,7 @@ class ReplDriver(settings: Array[String], displayErrors(errs) state.withHistory(src :: state.history) - case cmd: Command => interpretCommand(cmd) + case cmd: Command => interpretCommand(cmd, isSilent) case SigKill => // TODO state @@ -203,9 +220,10 @@ class ReplDriver(settings: Array[String], case _ => // new line, empty tree state } + } /** Compile `parsed` trees and evolve `state` in accordance */ - protected[this] final def compile(parsed: Parsed)(implicit state: State): State = { + protected[this] final def compile(parsed: Parsed, silent: Boolean = false)(implicit state: State): State = { import dotc.ast.Trees.PackageDef import untpd.{ PackageDef => _, _ } def extractNewestWrapper(tree: Tree): Name = tree match { @@ -216,21 +234,50 @@ class ReplDriver(settings: Array[String], compiler .compile(parsed) .fold( - displayErrors, + { + case (errors: Errors) => + displayErrors(errors) + }, { case (unit: CompilationUnit, newState: State) => { val newestWrapper = extractNewestWrapper(unit.untpdTree) val newImports = newState.imports ++ extractImports(parsed.trees)(newState.run.runContext) val newStateWithImports = newState.copy(imports = newImports) - displayDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports) + val (decls, optIndex, symbol) = getDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports) + if (!silent) displayDefinitions(decls, symbol)(newStateWithImports) + + optIndex.map(i => newStateWithImports.copy(valIndex = i)).getOrElse(newStateWithImports) } } ) } - /** Display definitions from `tree` */ - private def displayDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): State = { + /** Display definitions from `symbol` */ + private def displayDefinitions(decls: Seq[String], symbol: Option[Symbol])(implicit state: State): Unit = { + implicit val ctx = state.run.runContext + + def displayMembers(decls: Seq[String]): Unit = { + decls.foreach(str => out.println(SyntaxHighlighting(str))) + } + + def isSyntheticCompanion(sym: Symbol) = + sym.is(Module) && sym.is(Synthetic) + + def displayTypeDefs(optSymbol: Option[Symbol]): Unit = optSymbol.foreach(sym => sym.info.memberClasses + .collect { + case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName => + x.symbol + } + .foreach { sym => + out.println(SyntaxHighlighting("// defined " + sym.showUser)) + }) + displayTypeDefs(symbol) + displayMembers(decls) + } + + /** get definitions from `tree` */ + private def getDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): (Seq[String], Option[Int], Option[Symbol]) = { implicit val ctx = state.run.runContext def resAndUnit(denot: Denotation) = { @@ -244,8 +291,7 @@ class ReplDriver(settings: Array[String], name.startsWith(str.REPL_RES_PREFIX) && hasValidNumber && sym.info == defn.UnitType } - def displayMembers(symbol: Symbol) = if (tree.symbol.info.exists) { - val info = symbol.info + def getDeclarationsAndIndex(info: Type): (Seq[String], Int) = { val defs = info.bounds.hi.finalResultType .membersBasedOnFlags(Method, Accessor | ParamAccessor | Synthetic | Private) @@ -263,54 +309,40 @@ class ReplDriver(settings: Array[String], val typeAliases = info.bounds.hi.typeMembers.filter(_.symbol.info.isInstanceOf[TypeAlias]) - ( - typeAliases.map("// defined alias " + _.symbol.showUser) ++ + val declarations = typeAliases.map("// defined alias " + _.symbol.showUser) ++ defs.map(rendering.renderMethod) ++ vals.map(rendering.renderVal).flatten - ).foreach(str => out.println(SyntaxHighlighting(str))) - state.copy(valIndex = state.valIndex - vals.filter(resAndUnit).length) + (declarations, state.valIndex - vals.count(resAndUnit)) } - else state - - def isSyntheticCompanion(sym: Symbol) = - sym.is(Module) && sym.is(Synthetic) - - def displayTypeDefs(sym: Symbol) = sym.info.memberClasses - .collect { - case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName => - x.symbol - } - .foreach { sym => - out.println(SyntaxHighlighting("// defined " + sym.showUser)) - } - ctx.atPhase(ctx.typerPhase.next) { implicit ctx => - // Display members of wrapped module: + // get members of wrapped module: tree.symbol.info.memberClasses .find(_.symbol.name == newestWrapper.moduleClassName) .map { wrapperModule => - displayTypeDefs(wrapperModule.symbol) - displayMembers(wrapperModule.symbol) + if (tree.symbol.info.exists) { + val (decls, index) = getDeclarationsAndIndex(wrapperModule.symbol.info) + (decls, Some(index), Some(wrapperModule.symbol)) + } else (Seq.empty, None, None) } .getOrElse { // user defined a trait/class/object, so no module needed - state + (Seq.empty, None, None) } } } /** Interpret `cmd` to action and propagate potentially new `state` */ - private def interpretCommand(cmd: Command)(implicit state: State): State = cmd match { + private def interpretCommand(cmd: Command, silent: Boolean)(implicit state: State): State = cmd match { case UnknownCommand(cmd) => { - out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""") + if (!silent) out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""") state.withHistory(s"$cmd") } case Help => { - out.println(Help.text) + if (!silent) out.println(Help.text) state.withHistory(Help.command) } @@ -320,7 +352,7 @@ class ReplDriver(settings: Array[String], } case Imports => { - state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) } + if (!silent) state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) } state.withHistory(Imports.command) } @@ -330,23 +362,20 @@ class ReplDriver(settings: Array[String], if (file.exists) { val contents = scala.io.Source.fromFile(path).mkString ParseResult(contents)(state.run.runContext) match { - case parsed: Parsed => - compile(parsed).withHistory(loadCmd) - case SyntaxErrors(_, errors, _) => - displayErrors(errors).withHistory(loadCmd) - case _ => - state.withHistory(loadCmd) + case parsed: Parsed => compile(parsed, silent) + case SyntaxErrors(_, errors, _) => displayErrors(errors) + case _ => state } - } + }.withHistory(loadCmd) else { out.println(s"""Couldn't find file "${file.getCanonicalPath}"""") - state.withHistory(loadCmd) - } + state + }.withHistory(loadCmd) case TypeOf(expr) => { compiler.typeOf(expr).fold( displayErrors, - res => out.println(SyntaxHighlighting(res)) + res => if (!silent) out.println(SyntaxHighlighting(res)) ) state.withHistory(s"${TypeOf.command} $expr") } diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.scala b/sbt-bridge/src/xsbt/ConsoleInterface.scala index a98de74ff76a..4785869ddf95 100644 --- a/sbt-bridge/src/xsbt/ConsoleInterface.scala +++ b/sbt-bridge/src/xsbt/ConsoleInterface.scala @@ -19,24 +19,6 @@ class ConsoleInterface { def run(args: Array[String], bootClasspathString: String, classpathString: String, - // TODO: initial commands needs to be run under some form of special - // "silent" mode in the REPL. I.e. the effects should be had without - // any visual output. - // - // To do this we can use the `run` interface to the `ReplDriver` and - // pass it a special instance of `ParseResult` like `Silently(res: ParseResult)` - // and then observe the effects without printing to `ReplDriver#out` - // - // This way, the REPL can offer feedback on invalid commands but - // still function without stringly logic. - // - // This same principle can be applied to `cleanupCommands` and - // `bindValues` - // - // Steps: - // - // 1. Introduce `case class Silent(res: ParseResult) extends ParseResult` - // 2. Perform all steps in `interpret` as usual without printing to `out` initialCommands: String, cleanupCommands: String, loader: ClassLoader, @@ -51,6 +33,6 @@ class ConsoleInterface { } ++ Array("-classpath", classpathString) - new ReplDriver(completeArgs, classLoader = Some(loader)).runUntilQuit() + new ReplDriver(completeArgs, classLoader = Some(loader), initialCommands = Some(initialCommands), cleanupCommands = Some(cleanupCommands)).runUntilQuit() } } diff --git a/scala-backend b/scala-backend index 9dfe905943d0..83b68e5a1fd8 160000 --- a/scala-backend +++ b/scala-backend @@ -1 +1 @@ -Subproject commit 9dfe905943d0fc946677f75caf38d10915c185ae +Subproject commit 83b68e5a1fd80afff7f628e813796f5d81a59cc3