Skip to content

Commit 5bd08d4

Browse files
committed
Merge pull request #1182 from dotty-staging/repl-fixes
Repl fixes and tests
2 parents 8cafcb9 + ef8c196 commit 5bd08d4

File tree

14 files changed

+191
-63
lines changed

14 files changed

+191
-63
lines changed

src/dotty/tools/dotc/Driver.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import scala.util.control.NonFatal
1515
*/
1616
abstract class Driver extends DotClass {
1717

18-
val prompt = "\ndotc> "
19-
2018
protected def newCompiler(implicit ctx: Context): Compiler
2119

2220
protected def emptyReporter: Reporter = new StoreReporter(null)

src/dotty/tools/dotc/Resident.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class Resident extends Driver {
3131

3232
private val quit = ":q"
3333
private val reset = ":reset"
34+
private val prompt = "dotc> "
3435

3536
private def getLine() = {
3637
Console.print(prompt)

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ object Contexts {
544544
*/
545545
def initialize()(implicit ctx: Context): Unit = {
546546
_platform = newPlatform
547-
definitions.init
547+
definitions.init()
548548
}
549549

550550
def squashed(p: Phase): Phase = {

src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ class Definitions {
798798
private[this] var _isInitialized = false
799799
private def isInitialized = _isInitialized
800800

801-
def init(implicit ctx: Context) = {
801+
def init()(implicit ctx: Context) = {
802802
this.ctx = ctx
803803
if (!_isInitialized) {
804804
// force initialization of every symbol that is synthesized or hijacked by the compiler

src/dotty/tools/dotc/repl/CompilingInterpreter.scala

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
6060
import ast.untpd._
6161
import CompilingInterpreter._
6262

63+
ictx.base.initialize()(ictx)
64+
6365
/** directory to save .class files to */
6466
val virtualDirectory =
6567
if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None)
@@ -175,7 +177,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
175177
// if (prevRequests.isEmpty)
176178
// new Run(this) // initialize the compiler // (not sure this is needed)
177179
// parse
178-
parse(indentCode(line)) match {
180+
parse(line) match {
179181
case None => Interpreter.Incomplete
180182
case Some(Nil) => Interpreter.Error // parse error or empty input
181183
case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] =>
@@ -271,7 +273,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
271273
// header for the wrapper object
272274
code.println("object " + objectName + " {")
273275
code.print(importsPreamble)
274-
code.println(indentCode(toCompute))
276+
code.println(toCompute)
275277
handlers.foreach(_.extraCodeToEvaluate(this,code))
276278
code.println(importsTrailer)
277279
//end the wrapper object
@@ -477,7 +479,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
477479
addWrapper()
478480

479481
if (handler.statement.isInstanceOf[Import])
480-
preamble.append(handler.statement.toString + ";\n")
482+
preamble.append(handler.statement.show + ";\n")
481483

482484
// give wildcard imports a import wrapper all to their own
483485
if (handler.importsWildcard)
@@ -645,7 +647,7 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
645647

646648
private class ImportHandler(imp: Import) extends StatementHandler(imp) {
647649
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
648-
code.println("+ \"" + imp.toString + "\\n\"")
650+
code.println("+ \"" + imp.show + "\\n\"")
649651
}
650652

651653
def isWildcardSelector(tree: Tree) = tree match {
@@ -734,20 +736,6 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
734736
/** Clean up a string for output */
735737
private def clean(str: String)(implicit ctx: Context) =
736738
truncPrintString(stripWrapperGunk(str))
737-
738-
/** Indent some code by the width of the scala> prompt.
739-
* This way, compiler error messages read better.
740-
*/
741-
def indentCode(code: String) = {
742-
val spaces = " "
743-
744-
stringFrom(str =>
745-
for (line <- code.lines) {
746-
str.print(spaces)
747-
str.print(line + "\n")
748-
str.flush()
749-
})
750-
}
751739
}
752740

753741
/** Utility methods for the Interpreter. */

src/dotty/tools/dotc/repl/InterpreterLoop.scala

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import scala.concurrent.ExecutionContext.Implicits.global
2121
* @author Lex Spoon
2222
* @author Martin Odersky
2323
*/
24-
class InterpreterLoop(
25-
compiler: Compiler,
26-
private var in: InteractiveReader,
27-
out: PrintWriter)(implicit ctx: Context) {
24+
class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Context) {
25+
import config._
26+
27+
private var in = input
2828

2929
val interpreter = compiler.asInstanceOf[Interpreter]
3030

@@ -52,24 +52,20 @@ class InterpreterLoop(
5252
/** print a friendly help message */
5353
def printHelp(): Unit = {
5454
printWelcome()
55-
out.println("Type :load followed by a filename to load a Scala file.")
56-
out.println("Type :replay to reset execution and replay all previous commands.")
57-
out.println("Type :quit to exit the interpreter.")
55+
output.println("Type :load followed by a filename to load a Scala file.")
56+
output.println("Type :replay to reset execution and replay all previous commands.")
57+
output.println("Type :quit to exit the interpreter.")
5858
}
5959

6060
/** Print a welcome message */
6161
def printWelcome(): Unit = {
62-
out.println(s"Welcome to Scala$version " + " (" +
62+
output.println(s"Welcome to Scala$version " + " (" +
6363
System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." )
64-
out.println("Type in expressions to have them evaluated.")
65-
out.println("Type :help for more information.")
66-
out.flush()
64+
output.println("Type in expressions to have them evaluated.")
65+
output.println("Type :help for more information.")
66+
output.flush()
6767
}
6868

69-
/** Prompt to print when awaiting input */
70-
val prompt = "scala> "
71-
val continuationPrompt = " | "
72-
7369
val version = ".next (pre-alpha)"
7470

7571
/** The first interpreted command always takes a couple of seconds
@@ -92,7 +88,7 @@ class InterpreterLoop(
9288
val (keepGoing, finalLineOpt) = command(line)
9389
if (keepGoing) {
9490
finalLineOpt.foreach(addReplay)
95-
out.flush()
91+
output.flush()
9692
repl()
9793
}
9894
}
@@ -103,16 +99,16 @@ class InterpreterLoop(
10399
new FileReader(filename)
104100
} catch {
105101
case _: IOException =>
106-
out.println("Error opening file: " + filename)
102+
output.println("Error opening file: " + filename)
107103
return
108104
}
109105
val oldIn = in
110106
val oldReplay = replayCommandsRev
111107
try {
112108
val inFile = new BufferedReader(fileIn)
113-
in = new SimpleReader(inFile, out, false)
114-
out.println("Loading " + filename + "...")
115-
out.flush
109+
in = new SimpleReader(inFile, output, false)
110+
output.println("Loading " + filename + "...")
111+
output.flush
116112
repl()
117113
} finally {
118114
in = oldIn
@@ -124,10 +120,10 @@ class InterpreterLoop(
124120
/** create a new interpreter and replay all commands so far */
125121
def replay(): Unit = {
126122
for (cmd <- replayCommands) {
127-
out.println("Replaying: " + cmd)
128-
out.flush() // because maybe cmd will have its own output
123+
output.println("Replaying: " + cmd)
124+
output.flush() // because maybe cmd will have its own output
129125
command(cmd)
130-
out.println
126+
output.println
131127
}
132128
}
133129

@@ -138,12 +134,12 @@ class InterpreterLoop(
138134
def withFile(command: String)(action: String => Unit): Unit = {
139135
val spaceIdx = command.indexOf(' ')
140136
if (spaceIdx <= 0) {
141-
out.println("That command requires a filename to be specified.")
137+
output.println("That command requires a filename to be specified.")
142138
return
143139
}
144140
val filename = command.substring(spaceIdx).trim
145141
if (!new File(filename).exists) {
146-
out.println("That file does not exist")
142+
output.println("That file does not exist")
147143
return
148144
}
149145
action(filename)
@@ -169,7 +165,7 @@ class InterpreterLoop(
169165
else if (line matches replayRegexp)
170166
replay()
171167
else if (line startsWith ":")
172-
out.println("Unknown command. Type :help for help.")
168+
output.println("Unknown command. Type :help for help.")
173169
else
174170
shouldReplay = interpretStartingWith(line)
175171

@@ -188,7 +184,7 @@ class InterpreterLoop(
188184
case Interpreter.Error => None
189185
case Interpreter.Incomplete =>
190186
if (in.interactive && code.endsWith("\n\n")) {
191-
out.println("You typed two blank lines. Starting a new command.")
187+
output.println("You typed two blank lines. Starting a new command.")
192188
None
193189
} else {
194190
val nextLine = in.readLine(continuationPrompt)
@@ -207,7 +203,7 @@ class InterpreterLoop(
207203
val cmd = ":load " + filename
208204
command(cmd)
209205
replayCommandsRev = cmd :: replayCommandsRev
210-
out.println()
206+
output.println()
211207
}
212208
case _ =>
213209
}

src/dotty/tools/dotc/repl/REPL.scala

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,37 @@ import java.io.{BufferedReader, File, FileReader, PrintWriter}
2323
*/
2424
class REPL extends Driver {
2525

26-
/** The default input reader */
27-
def input(implicit ctx: Context): InteractiveReader = {
28-
val emacsShell = System.getProperty("env.emacs", "") != ""
29-
//println("emacsShell="+emacsShell) //debug
30-
if (ctx.settings.Xnojline.value || emacsShell) new SimpleReader()
31-
else InteractiveReader.createDefault()
32-
}
33-
34-
/** The defult output writer */
35-
def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true)
26+
lazy val config = new REPL.Config
3627

3728
override def newCompiler(implicit ctx: Context): Compiler =
38-
new repl.CompilingInterpreter(output, ctx)
29+
new repl.CompilingInterpreter(config.output, ctx)
3930

4031
override def sourcesRequired = false
4132

4233
override def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = {
4334
if (fileNames.isEmpty)
44-
new InterpreterLoop(compiler, input, output).run()
35+
new InterpreterLoop(compiler, config).run()
4536
else
4637
ctx.error(s"don't now what to do with $fileNames%, %")
4738
ctx.reporter
4839
}
4940
}
41+
42+
object REPL {
43+
class Config {
44+
val prompt = "scala> "
45+
val continuationPrompt = " | "
46+
val version = ".next (pre-alpha)"
47+
48+
/** The default input reader */
49+
def input(implicit ctx: Context): InteractiveReader = {
50+
val emacsShell = System.getProperty("env.emacs", "") != ""
51+
//println("emacsShell="+emacsShell) //debug
52+
if (ctx.settings.Xnojline.value || emacsShell) new SimpleReader()
53+
else InteractiveReader.createDefault()
54+
}
55+
56+
/** The default output writer */
57+
def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true)
58+
}
59+
}

test/dotc/tests.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class tests extends CompilerTest {
4646
val negDir = testsDir + "neg/"
4747
val runDir = testsDir + "run/"
4848
val newDir = testsDir + "new/"
49+
val replDir = testsDir + "repl/"
4950

5051
val sourceDir = "./src/"
5152
val dottyDir = sourceDir + "dotty/"
@@ -112,6 +113,7 @@ class tests extends CompilerTest {
112113
@Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes)
113114

114115
@Test def new_all = compileFiles(newDir, twice)
116+
@Test def repl_all = replFiles(replDir)
115117

116118
@Test def neg_all = compileFiles(negDir, verbose = true, compileSubDirs = false)
117119
@Test def neg_typedIdents() = compileDir(negDir, "typedIdents")

test/test/CompilerTest.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import dotty.tools.dotc.{Main, Bench, Driver}
55
import dotty.tools.dotc.reporting.Reporter
66
import dotty.tools.dotc.util.SourcePosition
77
import dotty.tools.dotc.config.CompilerCommand
8+
import dotty.tools.io.PlainFile
89
import scala.collection.mutable.ListBuffer
910
import scala.reflect.io.{ Path, Directory, File => SFile, AbstractFile }
1011
import scala.tools.partest.nest.{ FileManager, NestUI }
1112
import scala.annotation.tailrec
1213
import java.io.{ RandomAccessFile, File => JFile }
13-
import dotty.tools.io.PlainFile
1414

1515
import org.junit.Test
1616

@@ -205,7 +205,22 @@ abstract class CompilerTest {
205205
}
206206
}
207207

208+
def replFile(prefix: String, fileName: String): Unit = {
209+
val path = s"$prefix$fileName"
210+
val f = new PlainFile(path)
211+
val repl = new TestREPL(new String(f.toCharArray))
212+
repl.process(Array[String]())
213+
repl.check()
214+
}
208215

216+
def replFiles(path: String): Unit = {
217+
val dir = Directory(path)
218+
val fileNames = dir.files.toArray.map(_.jfile.getName).filter(_ endsWith ".check")
219+
for (name <- fileNames) {
220+
log(s"testing $path$name")
221+
replFile(path, name)
222+
}
223+
}
209224

210225
// ========== HELPERS =============
211226

test/test/TestREPL.scala

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package test
2+
3+
import dotty.tools.dotc.repl._
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import collection.mutable
6+
import java.io.StringWriter
7+
8+
/** A subclass of REPL used for testing.
9+
* It takes a transcript of a REPL session in `script`. The transcript
10+
* starts with the first input prompt `scala> ` and ends with `scala> :quit` and a newline.
11+
* Invoking `process()` on the `TestREPL` runs all input lines and
12+
* collects then interleaved with REPL output in a string writer `out`.
13+
* Invoking `check()` checks that the collected output matches the original
14+
* `script`.
15+
*/
16+
class TestREPL(script: String) extends REPL {
17+
18+
private val out = new StringWriter()
19+
20+
override lazy val config = new REPL.Config {
21+
override val output = new NewLinePrintWriter(out)
22+
23+
override def input(implicit ctx: Context) = new InteractiveReader {
24+
val lines = script.lines
25+
def readLine(prompt: String): String = {
26+
val line = lines.next
27+
if (line.startsWith(prompt) || line.startsWith(continuationPrompt)) {
28+
output.println(line)
29+
line.drop(prompt.length)
30+
}
31+
else readLine(prompt)
32+
}
33+
val interactive = false
34+
}
35+
}
36+
37+
def check() = {
38+
out.close()
39+
val printed = out.toString
40+
val transcript = printed.drop(printed.indexOf(config.prompt))
41+
if (transcript.toString != script) {
42+
println("input differs from transcript:")
43+
println(transcript)
44+
assert(false)
45+
}
46+
}
47+
}

tests/repl/import.check

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
scala> import collection.mutable._
2+
import collection.mutable._
3+
scala> val buf = new ListBuffer[Int]
4+
buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
5+
scala> buf += 22
6+
res0: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22)
7+
scala> buf ++= List(1, 2, 3)
8+
res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(22, 1, 2, 3)
9+
scala> buf.toList
10+
res2: scala.collection.immutable.List[Int] = List(22, 1, 2, 3)
11+
scala> :quit

0 commit comments

Comments
 (0)