Skip to content

Commit f262f1d

Browse files
author
Rajesh Veeranki
committed
Using Silent case class to support suppressing output of repl commands
1 parent 8f15b2a commit f262f1d

File tree

3 files changed

+109
-93
lines changed

3 files changed

+109
-93
lines changed

compiler/src/dotty/tools/repl/ParseResult.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ import dotc.reporting._
1010

1111
import results._
1212

13+
sealed trait Parsing
14+
1315
/** A parsing result from string input */
14-
sealed trait ParseResult
16+
sealed trait ParseResult extends Parsing
17+
18+
/** Suppress visual output when this is passed */
19+
case class Silent(underlying: ParseResult) extends Parsing
1520

1621
/** An error free parsing resulting in a list of untyped trees */
1722
case class Parsed(sourceCode: String, trees: List[untpd.Tree]) extends ParseResult

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 100 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
11
package dotty.tools
22
package repl
33

4-
import java.io.{ InputStream, PrintStream }
4+
import java.io.{InputStream, PrintStream}
55

66
import scala.annotation.tailrec
7-
87
import dotc.reporting.MessageRendering
98
import dotc.reporting.diagnostic.MessageContainer
109
import dotc.ast.untpd
1110
import dotc.ast.tpd
12-
import dotc.interactive.{ SourceTree, Interactive }
11+
import dotc.interactive.{Interactive, SourceTree}
1312
import dotc.core.Contexts.Context
14-
import dotc.{ CompilationUnit, Run }
15-
import dotc.core.Mode
13+
import dotc.{CompilationUnit, Run}
14+
import dotc.core.{Denotations, Mode}
1615
import dotc.core.Flags._
1716
import dotc.core.Types._
1817
import dotc.core.StdNames._
1918
import dotc.core.Names.Name
2019
import dotc.core.NameOps._
21-
import dotc.core.Symbols.{ Symbol, NoSymbol, defn }
20+
import dotc.core.Symbols.{NoSymbol, Symbol, defn}
2221
import dotc.core.Denotations.Denotation
23-
import dotc.core.Types.{ ExprType, ConstantType }
22+
import dotc.core.Types.{ConstantType, ExprType}
2423
import dotc.core.NameKinds.SimpleNameKind
2524
import dotc.config.CompilerCommand
26-
import dotc.{ Compiler, Driver }
25+
import dotc.{Compiler, Driver}
2726
import dotc.printing.SyntaxHighlighting
2827
import dotc.util.Positions.Position
2928
import io._
30-
3129
import AmmoniteReader._
3230
import results._
3331

@@ -79,7 +77,9 @@ case class Completions(cursor: Int,
7977
/** Main REPL instance, orchestrating input, compilation and presentation */
8078
class ReplDriver(settings: Array[String],
8179
protected val out: PrintStream = System.out,
82-
protected val classLoader: Option[ClassLoader] = None) extends Driver {
80+
protected val classLoader: Option[ClassLoader] = None,
81+
initialCommands: Array[String] = Array.empty,
82+
cleanupCommands: Array[String] = Array.empty) extends Driver {
8383

8484
/** Overridden to `false` in order to not have to give sources on the
8585
* commandline
@@ -124,21 +124,27 @@ class ReplDriver(settings: Array[String],
124124
resetToInitial()
125125

126126
/** Run REPL with `state` until `:quit` command found
127-
*
128-
* This method is the main entry point into the REPL. Its effects are not
129-
* observable outside of the CLI, for this reason, most helper methods are
130-
* `protected final` to facilitate testing.
131-
*/
132-
@tailrec final def runUntilQuit(state: State = initState): State = {
133-
val res = readLine()(state)
134-
135-
if (res == Quit) state
136-
else {
137-
// readLine potentially destroys the run, so a new one is needed for the
138-
// rest of the interpretation:
139-
implicit val freshState = state.newRun(compiler, rootCtx)
140-
runUntilQuit(interpret(res))
127+
*
128+
* This method is the main entry point into the REPL. Its effects are not
129+
* observable outside of the CLI, for this reason, most helper methods are
130+
* `protected final` to facilitate testing.
131+
*/
132+
final def runUntilQuit(initialState: State = initState): State = {
133+
@tailrec def run(state: State = initState): State = {
134+
val res = readLine()(state)
135+
136+
if (res == Quit) state
137+
else {
138+
// readLine potentially destroys the run, so a new one is needed for the
139+
// rest of the interpretation:
140+
implicit val freshState = state.newRun(compiler, rootCtx)
141+
run(interpret(res))
142+
}
141143
}
144+
145+
val state = runBootstrapCommands(initialCommands)(initialState)
146+
val userState = run(state)
147+
runBootstrapCommands(cleanupCommands)(userState)
142148
}
143149

144150
final def run(input: String)(implicit state: State): State =
@@ -147,6 +153,12 @@ class ReplDriver(settings: Array[String],
147153
final def run(res: ParseResult)(implicit state: State): State =
148154
interpret(res)
149155

156+
final def runBootstrapCommands(cmds: Array[String])(implicit state: State): State = {
157+
cmds.map(ParseResult.apply(_)(rootCtx)).map(Silent.apply(_)).foldLeft(state) { (s, cmd) =>
158+
interpret(cmd)(s)
159+
}
160+
}
161+
150162
/** Extract possible completions at the index of `cursor` in `expr` */
151163
protected[this] final def completions(cursor: Int, expr: String, state0: State): Completions = {
152164
// TODO move some of this logic to `Interactive`
@@ -181,10 +193,15 @@ class ReplDriver(settings: Array[String],
181193
private def extractImports(trees: List[untpd.Tree])(implicit context: Context): List[(untpd.Import, String)] =
182194
trees.collect { case imp: untpd.Import => (imp, imp.show) }
183195

184-
private def interpret(res: ParseResult)(implicit state: State): State =
185-
res match {
196+
private def interpret(res: Parsing)(implicit state: State): State = {
197+
val (parseResult, isSilent) = res match {
198+
case Silent(x) => (x, true)
199+
case x: ParseResult => (x, false)
200+
}
201+
202+
parseResult match {
186203
case parsed: Parsed =>
187-
compile(parsed)
204+
compile(parsed, isSilent)
188205
.withHistory(parsed.sourceCode :: state.history)
189206
.newRun(compiler, rootCtx)
190207

@@ -194,11 +211,12 @@ class ReplDriver(settings: Array[String],
194211

195212
case Newline | SigKill => state
196213

197-
case cmd: Command => interpretCommand(cmd)
214+
case cmd: Command => interpretCommand(cmd, isSilent)
198215
}
216+
}
199217

200218
/** Compile `parsed` trees and evolve `state` in accordance */
201-
protected[this] final def compile(parsed: Parsed)(implicit state: State): State = {
219+
protected[this] final def compile(parsed: Parsed, silent: Boolean = false)(implicit state: State): State = {
202220
import dotc.ast.Trees.PackageDef
203221
import untpd.{ PackageDef => _, _ }
204222
def extractNewestWrapper(tree: Tree): Name = tree match {
@@ -209,21 +227,50 @@ class ReplDriver(settings: Array[String],
209227
compiler
210228
.compile(parsed)
211229
.fold(
212-
displayErrors,
230+
{
231+
case (errors: Errors) =>
232+
displayErrors(errors)
233+
},
213234
{
214235
case (unit: CompilationUnit, newState: State) => {
215236
val newestWrapper = extractNewestWrapper(unit.untpdTree)
216237
val newImports = newState.imports ++ extractImports(parsed.trees)(newState.run.runContext)
217238
val newStateWithImports = newState.copy(imports = newImports)
218239

219-
displayDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
240+
val (decls, optIndex, symbol) = getDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
241+
if (!silent) displayDefinitions(decls, symbol)(newStateWithImports)
242+
243+
optIndex.map(i => newStateWithImports.copy(valIndex = i)).getOrElse(newStateWithImports)
220244
}
221245
}
222246
)
223247
}
224248

225-
/** Display definitions from `tree` */
226-
private def displayDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): State = {
249+
/** Display definitions from `symbol` */
250+
private def displayDefinitions(decls: Seq[String], symbol: Option[Symbol])(implicit state: State): Unit = {
251+
implicit val ctx = state.run.runContext
252+
253+
def displayMembers(decls: Seq[String]): Unit = {
254+
decls.foreach(str => out.println(SyntaxHighlighting(str)))
255+
}
256+
257+
def isSyntheticCompanion(sym: Symbol) =
258+
sym.is(Module) && sym.is(Synthetic)
259+
260+
def displayTypeDefs(optSymbol: Option[Symbol]): Unit = optSymbol.foreach(sym => sym.info.memberClasses
261+
.collect {
262+
case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName =>
263+
x.symbol
264+
}
265+
.foreach { sym =>
266+
out.println(SyntaxHighlighting("// defined " + sym.showUser))
267+
})
268+
displayTypeDefs(symbol)
269+
displayMembers(decls)
270+
}
271+
272+
/** get definitions from `tree` */
273+
private def getDefinitions(tree: tpd.Tree, newestWrapper: Name)(implicit state: State): (Seq[String], Option[Int], Option[Symbol]) = {
227274
implicit val ctx = state.run.runContext
228275

229276
def resAndUnit(denot: Denotation) = {
@@ -237,8 +284,7 @@ class ReplDriver(settings: Array[String],
237284
name.startsWith(str.REPL_RES_PREFIX) && hasValidNumber && sym.info == defn.UnitType
238285
}
239286

240-
def displayMembers(symbol: Symbol) = if (tree.symbol.info.exists) {
241-
val info = symbol.info
287+
def getDeclarationsAndIndex(info: Type): (Seq[String], Int) = {
242288
val defs =
243289
info.bounds.hi.finalResultType
244290
.membersBasedOnFlags(Method, Accessor | ParamAccessor | Synthetic | Private)
@@ -256,54 +302,40 @@ class ReplDriver(settings: Array[String],
256302
val typeAliases =
257303
info.bounds.hi.typeMembers.filter(_.symbol.info.isInstanceOf[TypeAlias])
258304

259-
(
260-
typeAliases.map("// defined alias " + _.symbol.showUser) ++
305+
val declarations = typeAliases.map("// defined alias " + _.symbol.showUser) ++
261306
defs.map(rendering.renderMethod) ++
262307
vals.map(rendering.renderVal).flatten
263-
).foreach(str => out.println(SyntaxHighlighting(str)))
264308

265-
state.copy(valIndex = state.valIndex - vals.filter(resAndUnit).length)
309+
(declarations, state.valIndex - vals.count(resAndUnit))
266310
}
267-
else state
268-
269-
def isSyntheticCompanion(sym: Symbol) =
270-
sym.is(Module) && sym.is(Synthetic)
271-
272-
def displayTypeDefs(sym: Symbol) = sym.info.memberClasses
273-
.collect {
274-
case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName =>
275-
x.symbol
276-
}
277-
.foreach { sym =>
278-
out.println(SyntaxHighlighting("// defined " + sym.showUser))
279-
}
280-
281311

282312
ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
283313

284-
// Display members of wrapped module:
314+
// get members of wrapped module:
285315
tree.symbol.info.memberClasses
286316
.find(_.symbol.name == newestWrapper.moduleClassName)
287317
.map { wrapperModule =>
288-
displayTypeDefs(wrapperModule.symbol)
289-
displayMembers(wrapperModule.symbol)
318+
if (tree.symbol.info.exists) {
319+
val (decls, index) = getDeclarationsAndIndex(wrapperModule.symbol.info)
320+
(decls, Some(index), Some(wrapperModule.symbol))
321+
} else (Seq.empty, None, None)
290322
}
291323
.getOrElse {
292324
// user defined a trait/class/object, so no module needed
293-
state
325+
(Seq.empty, None, None)
294326
}
295327
}
296328
}
297329

298330
/** Interpret `cmd` to action and propagate potentially new `state` */
299-
private def interpretCommand(cmd: Command)(implicit state: State): State = cmd match {
331+
private def interpretCommand(cmd: Command, silent: Boolean)(implicit state: State): State = cmd match {
300332
case UnknownCommand(cmd) => {
301-
out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""")
333+
if (!silent) out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""")
302334
state.withHistory(s"$cmd")
303335
}
304336

305337
case Help => {
306-
out.println(Help.text)
338+
if (!silent) out.println(Help.text)
307339
state.withHistory(Help.command)
308340
}
309341

@@ -313,7 +345,7 @@ class ReplDriver(settings: Array[String],
313345
}
314346

315347
case Imports => {
316-
state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) }
348+
if (!silent) state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) }
317349
state.withHistory(Imports.command)
318350
}
319351

@@ -323,23 +355,20 @@ class ReplDriver(settings: Array[String],
323355
if (file.exists) {
324356
val contents = scala.io.Source.fromFile(path).mkString
325357
ParseResult(contents)(state.run.runContext) match {
326-
case parsed: Parsed =>
327-
compile(parsed).withHistory(loadCmd)
328-
case SyntaxErrors(_, errors, _) =>
329-
displayErrors(errors).withHistory(loadCmd)
330-
case _ =>
331-
state.withHistory(loadCmd)
358+
case parsed: Parsed => compile(parsed, silent)
359+
case SyntaxErrors(_, errors, _) => displayErrors(errors)
360+
case _ => state
332361
}
333-
}
362+
}.withHistory(loadCmd)
334363
else {
335364
out.println(s"""Couldn't find file "${file.getCanonicalPath}"""")
336-
state.withHistory(loadCmd)
337-
}
365+
state
366+
}.withHistory(loadCmd)
338367

339368
case TypeOf(expr) => {
340369
compiler.typeOf(expr).fold(
341370
displayErrors,
342-
res => out.println(SyntaxHighlighting(res))
371+
res => if (!silent) out.println(SyntaxHighlighting(res))
343372
)
344373
state.withHistory(s"${TypeOf.command} $expr")
345374
}

sbt-bridge/src/xsbt/ConsoleInterface.scala

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,8 @@ class ConsoleInterface {
1919
def run(args: Array[String],
2020
bootClasspathString: String,
2121
classpathString: String,
22-
// TODO: initial commands needs to be run under some form of special
23-
// "silent" mode in the REPL. I.e. the effects should be had without
24-
// any visual output.
25-
//
26-
// To do this we can use the `run` interface to the `ReplDriver` and
27-
// pass it a special instance of `ParseResult` like `Silently(res: ParseResult)`
28-
// and then observe the effects without printing to `ReplDriver#out`
29-
//
30-
// This way, the REPL can offer feedback on invalid commands but
31-
// still function without stringly logic.
32-
//
33-
// This same principle can be applied to `cleanupCommands` and
34-
// `bindValues`
35-
//
36-
// Steps:
37-
//
38-
// 1. Introduce `case class Silent(res: ParseResult) extends ParseResult`
39-
// 2. Perform all steps in `interpret` as usual without printing to `out`
40-
initialCommands: String,
41-
cleanupCommands: String,
22+
initialCommands: Array[String],
23+
cleanupCommands: Array[String],
4224
loader: ClassLoader,
4325
bindNames: Array[String],
4426
bindValues: Array[Any],
@@ -51,6 +33,6 @@ class ConsoleInterface {
5133
} ++
5234
Array("-classpath", classpathString)
5335

54-
new ReplDriver(completeArgs, classLoader = Some(loader)).runUntilQuit()
36+
new ReplDriver(completeArgs, classLoader = Some(loader), initialCommands = initialCommands, cleanupCommands = cleanupCommands).runUntilQuit()
5537
}
5638
}

0 commit comments

Comments
 (0)