Skip to content

Commit 720d291

Browse files
author
Rajesh Veeranki
committed
Using Silent case class to support suppressing output of repl commands
1 parent f728134 commit 720d291

File tree

5 files changed

+114
-98
lines changed

5 files changed

+114
-98
lines changed

collection-strawman

Submodule collection-strawman updated 231 files

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

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

1212
import results._
1313

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

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

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

Lines changed: 103 additions & 74 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,24 +124,30 @@ 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) {
136-
out.println()
137-
state
138-
}
139-
else {
140-
// readLine potentially destroys the run, so a new one is needed for the
141-
// rest of the interpretation:
142-
implicit val freshState = state.newRun(compiler, rootCtx)
143-
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) {
137+
out.println()
138+
state
139+
}
140+
else {
141+
// readLine potentially destroys the run, so a new one is needed for the
142+
// rest of the interpretation:
143+
implicit val freshState = state.newRun(compiler, rootCtx)
144+
run(interpret(res))
145+
}
144146
}
147+
148+
val state = runBootstrapCommands(initialCommands)(initialState)
149+
val userState = run(state)
150+
runBootstrapCommands(cleanupCommands)(userState)
145151
}
146152

147153
final def run(input: String)(implicit state: State): State =
@@ -150,6 +156,12 @@ class ReplDriver(settings: Array[String],
150156
final def run(res: ParseResult)(implicit state: State): State =
151157
interpret(res)
152158

159+
final def runBootstrapCommands(cmds: Array[String])(implicit state: State): State = {
160+
cmds.map(ParseResult.apply(_)(rootCtx)).map(Silent.apply(_)).foldLeft(state) { (s, cmd) =>
161+
interpret(cmd)(s)
162+
}
163+
}
164+
153165
/** Extract possible completions at the index of `cursor` in `expr` */
154166
protected[this] final def completions(cursor: Int, expr: String, state0: State): Completions = {
155167
// TODO move some of this logic to `Interactive`
@@ -184,28 +196,34 @@ class ReplDriver(settings: Array[String],
184196
private def extractImports(trees: List[untpd.Tree])(implicit context: Context): List[(untpd.Import, String)] =
185197
trees.collect { case imp: untpd.Import => (imp, imp.show) }
186198

187-
private def interpret(res: ParseResult)(implicit state: State): State =
188-
res match {
199+
private def interpret(res: Parsing)(implicit state: State): State = {
200+
val (parseResult, isSilent) = res match {
201+
case Silent(x) => (x, true)
202+
case x: ParseResult => (x, false)
203+
}
204+
205+
parseResult match {
189206
case parsed: Parsed if parsed.trees.nonEmpty =>
190-
compile(parsed)
207+
compile(parsed, isSilent)
191208
.withHistory(parsed.sourceCode :: state.history)
192209
.newRun(compiler, rootCtx)
193210

194211
case SyntaxErrors(src, errs, _) =>
195212
displayErrors(errs)
196213
state.withHistory(src :: state.history)
197214

198-
case cmd: Command => interpretCommand(cmd)
215+
case cmd: Command => interpretCommand(cmd, isSilent)
199216

200217
case SigKill => // TODO
201218
state
202219

203220
case _ => // new line, empty tree
204221
state
205222
}
223+
}
206224

207225
/** Compile `parsed` trees and evolve `state` in accordance */
208-
protected[this] final def compile(parsed: Parsed)(implicit state: State): State = {
226+
protected[this] final def compile(parsed: Parsed, silent: Boolean = false)(implicit state: State): State = {
209227
import dotc.ast.Trees.PackageDef
210228
import untpd.{ PackageDef => _, _ }
211229
def extractNewestWrapper(tree: Tree): Name = tree match {
@@ -216,21 +234,50 @@ class ReplDriver(settings: Array[String],
216234
compiler
217235
.compile(parsed)
218236
.fold(
219-
displayErrors,
237+
{
238+
case (errors: Errors) =>
239+
displayErrors(errors)
240+
},
220241
{
221242
case (unit: CompilationUnit, newState: State) => {
222243
val newestWrapper = extractNewestWrapper(unit.untpdTree)
223244
val newImports = newState.imports ++ extractImports(parsed.trees)(newState.run.runContext)
224245
val newStateWithImports = newState.copy(imports = newImports)
225246

226-
displayDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
247+
val (decls, optIndex, symbol) = getDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
248+
if (!silent) displayDefinitions(decls, symbol)(newStateWithImports)
249+
250+
optIndex.map(i => newStateWithImports.copy(valIndex = i)).getOrElse(newStateWithImports)
227251
}
228252
}
229253
)
230254
}
231255

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

236283
def resAndUnit(denot: Denotation) = {
@@ -244,8 +291,7 @@ class ReplDriver(settings: Array[String],
244291
name.startsWith(str.REPL_RES_PREFIX) && hasValidNumber && sym.info == defn.UnitType
245292
}
246293

247-
def displayMembers(symbol: Symbol) = if (tree.symbol.info.exists) {
248-
val info = symbol.info
294+
def getDeclarationsAndIndex(info: Type): (Seq[String], Int) = {
249295
val defs =
250296
info.bounds.hi.finalResultType
251297
.membersBasedOnFlags(Method, Accessor | ParamAccessor | Synthetic | Private)
@@ -263,54 +309,40 @@ class ReplDriver(settings: Array[String],
263309
val typeAliases =
264310
info.bounds.hi.typeMembers.filter(_.symbol.info.isInstanceOf[TypeAlias])
265311

266-
(
267-
typeAliases.map("// defined alias " + _.symbol.showUser) ++
312+
val declarations = typeAliases.map("// defined alias " + _.symbol.showUser) ++
268313
defs.map(rendering.renderMethod) ++
269314
vals.map(rendering.renderVal).flatten
270-
).foreach(str => out.println(SyntaxHighlighting(str)))
271315

272-
state.copy(valIndex = state.valIndex - vals.filter(resAndUnit).length)
316+
(declarations, state.valIndex - vals.count(resAndUnit))
273317
}
274-
else state
275-
276-
def isSyntheticCompanion(sym: Symbol) =
277-
sym.is(Module) && sym.is(Synthetic)
278-
279-
def displayTypeDefs(sym: Symbol) = sym.info.memberClasses
280-
.collect {
281-
case x if !isSyntheticCompanion(x.symbol) && !x.symbol.name.isReplWrapperName =>
282-
x.symbol
283-
}
284-
.foreach { sym =>
285-
out.println(SyntaxHighlighting("// defined " + sym.showUser))
286-
}
287-
288318

289319
ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
290320

291-
// Display members of wrapped module:
321+
// get members of wrapped module:
292322
tree.symbol.info.memberClasses
293323
.find(_.symbol.name == newestWrapper.moduleClassName)
294324
.map { wrapperModule =>
295-
displayTypeDefs(wrapperModule.symbol)
296-
displayMembers(wrapperModule.symbol)
325+
if (tree.symbol.info.exists) {
326+
val (decls, index) = getDeclarationsAndIndex(wrapperModule.symbol.info)
327+
(decls, Some(index), Some(wrapperModule.symbol))
328+
} else (Seq.empty, None, None)
297329
}
298330
.getOrElse {
299331
// user defined a trait/class/object, so no module needed
300-
state
332+
(Seq.empty, None, None)
301333
}
302334
}
303335
}
304336

305337
/** Interpret `cmd` to action and propagate potentially new `state` */
306-
private def interpretCommand(cmd: Command)(implicit state: State): State = cmd match {
338+
private def interpretCommand(cmd: Command, silent: Boolean)(implicit state: State): State = cmd match {
307339
case UnknownCommand(cmd) => {
308-
out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""")
340+
if (!silent) out.println(s"""Unknown command: "$cmd", run ":help" for a list of commands""")
309341
state.withHistory(s"$cmd")
310342
}
311343

312344
case Help => {
313-
out.println(Help.text)
345+
if (!silent) out.println(Help.text)
314346
state.withHistory(Help.command)
315347
}
316348

@@ -320,7 +352,7 @@ class ReplDriver(settings: Array[String],
320352
}
321353

322354
case Imports => {
323-
state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) }
355+
if (!silent) state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) }
324356
state.withHistory(Imports.command)
325357
}
326358

@@ -330,23 +362,20 @@ class ReplDriver(settings: Array[String],
330362
if (file.exists) {
331363
val contents = scala.io.Source.fromFile(path).mkString
332364
ParseResult(contents)(state.run.runContext) match {
333-
case parsed: Parsed =>
334-
compile(parsed).withHistory(loadCmd)
335-
case SyntaxErrors(_, errors, _) =>
336-
displayErrors(errors).withHistory(loadCmd)
337-
case _ =>
338-
state.withHistory(loadCmd)
365+
case parsed: Parsed => compile(parsed, silent)
366+
case SyntaxErrors(_, errors, _) => displayErrors(errors)
367+
case _ => state
339368
}
340-
}
369+
}.withHistory(loadCmd)
341370
else {
342371
out.println(s"""Couldn't find file "${file.getCanonicalPath}"""")
343-
state.withHistory(loadCmd)
344-
}
372+
state
373+
}.withHistory(loadCmd)
345374

346375
case TypeOf(expr) => {
347376
compiler.typeOf(expr).fold(
348377
displayErrors,
349-
res => out.println(SyntaxHighlighting(res))
378+
res => if (!silent) out.println(SyntaxHighlighting(res))
350379
)
351380
state.withHistory(s"${TypeOf.command} $expr")
352381
}

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)