Skip to content

Simplify REPL #3560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ trait MessageRendering {
/** The whole message rendered from `msg` */
def messageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): String = {
val sb = mutable.StringBuilder.newBuilder
sb.append(posStr(pos, diagnosticLevel, msg)).append('\n')
val posString = posStr(pos, diagnosticLevel, msg)
if (posString.nonEmpty) sb.append(posString).append('\n')
if (pos.exists) {
val (srcBefore, srcAfter, offset) = sourceLines(pos)
val marker = columnMarker(pos, offset)
Expand Down
13 changes: 5 additions & 8 deletions compiler/src/dotty/tools/repl/ParseResult.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ object ParseResult {

@sharable private[this] val CommandExtract = """(:[\S]+)\s*(.*)""".r

private def parseStats(parser: Parser): List[untpd.Tree] = {
private def parseStats(sourceCode: String)(implicit ctx: Context): List[untpd.Tree] = {
val source = new SourceFile("<console>", sourceCode.toCharArray)
val parser = new Parser(source)
val stats = parser.blockStatSeq()
parser.accept(Tokens.EOF)
stats
Expand All @@ -118,10 +120,7 @@ object ParseResult {
case _ => UnknownCommand(cmd)
}
case _ => {
val source = new SourceFile("<console>", sourceCode.toCharArray)
val parser = new Parser(source)

val stats = parseStats(parser)
val stats = parseStats(sourceCode)

if (ctx.reporter.hasErrors) {
SyntaxErrors(sourceCode,
Expand All @@ -145,9 +144,7 @@ object ParseResult {
val reporter = storeReporter
var needsMore = false
reporter.withIncompleteHandler(_ => _ => needsMore = true) {
val source = new SourceFile("<console>", sourceCode.toCharArray)
val parser = new Parser(source)(ctx.fresh.setReporter(reporter))
parseStats(parser)
parseStats(sourceCode)(ctx.fresh.setReporter(reporter))
!reporter.hasErrors && needsMore
}
}
Expand Down
96 changes: 45 additions & 51 deletions compiler/src/dotty/tools/repl/ReplCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {

/** A GenBCode phase that outputs to a virtual directory */
private class REPLGenBCode extends GenBCode {
override def phaseName = "replGenBCode"
override def phaseName = "genBCode"
override def outputDir(implicit ctx: Context) = directory
}

Expand All @@ -51,6 +51,25 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
)
}

def newRun(initCtx: Context, objectIndex: Int) = new Run(this, initCtx) {
override protected[this] def rootContext(implicit ctx: Context) =
addMagicImports(super.rootContext.fresh.setReporter(storeReporter))

private def addMagicImports(initCtx: Context): Context = {
def addImport(path: TermName)(implicit ctx: Context) = {
val importInfo = ImportInfo.rootImport { () =>
ctx.requiredModuleRef(path)
}
ctx.fresh.setNewScope.setImportInfo(importInfo)
}

(1 to objectIndex)
.foldLeft(addImport("dotty.Show".toTermName)(initCtx)) { (ictx, i) =>
addImport(nme.EMPTY_PACKAGE ++ "." ++ objectNames(i))(ictx)
}
}
}

private[this] var objectNames = Map.empty[Int, TermName]
private def objectName(state: State) =
objectNames.get(state.objectIndex).getOrElse {
Expand All @@ -59,9 +78,9 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
newName
}

sealed case class Definitions(stats: List[untpd.Tree], state: State)
private case class Definitions(stats: List[untpd.Tree], state: State)

def definitions(trees: List[untpd.Tree], state: State): Result[Definitions] = {
private def definitions(trees: List[untpd.Tree], state: State): Definitions = {
import untpd._

implicit val ctx: Context = state.run.runContext
Expand All @@ -80,7 +99,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {

def createPatDefShows(patDef: PatDef) = {
def createDeepShows(tree: untpd.Tree) = {
object PatFolder extends UntypedDeepFolder[List[DefDef]] (
class PatFolder extends UntypedDeepFolder[List[DefDef]] (
(acc, tree) => tree match {
case Ident(name) if name.isVariableName && name != nme.WILDCARD =>
createShow(name.toTermName, tree.pos) :: acc
Expand All @@ -90,7 +109,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
acc
}
)
PatFolder.apply(Nil, tree).reverse
(new PatFolder).apply(Nil, tree).reverse
}

// cannot fold over the whole tree because we need to generate show methods
Expand Down Expand Up @@ -130,12 +149,12 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
}

Definitions(
state.imports.map(_._1) ++ defs,
state.imports ++ defs,
state.copy(
objectIndex = state.objectIndex + (if (defs.isEmpty) 0 else 1),
valIndex = valIdx
)
).result
)
}

/** Wrap trees in an object and add imports from the previous compilations
Expand All @@ -153,65 +172,40 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
* }
* ```
*/
def wrapped(defs: Definitions, sourceCode: String): untpd.PackageDef = {
private def wrapped(defs: Definitions): untpd.PackageDef = {
import untpd._

implicit val ctx: Context = defs.state.run.runContext
assert(defs.stats.nonEmpty)

val module = {
val tmpl = Template(emptyConstructor(ctx), Nil, EmptyValDef, defs.stats)
List(
ModuleDef(objectName(defs.state), tmpl)
.withMods(new Modifiers(Module | Final))
.withPos(Position(0, sourceCode.length))
)
}
implicit val ctx: Context = defs.state.run.runContext

PackageDef(Ident(nme.EMPTY_PACKAGE), module)
}
val tmpl = Template(emptyConstructor, Nil, EmptyValDef, defs.stats)
val module = ModuleDef(objectName(defs.state), tmpl)
.withMods(new Modifiers(Module | Final))
.withPos(Position(0, defs.stats.last.pos.end))

def newRun(initCtx: Context, objectIndex: Int) = new Run(this, initCtx) {
override protected[this] def rootContext(implicit ctx: Context) =
addMagicImports(super.rootContext.fresh.setReporter(storeReporter), objectIndex)
PackageDef(Ident(nme.EMPTY_PACKAGE), List(module))
}

def createUnit(defs: Definitions, sourceCode: String): Result[CompilationUnit] = {
private def createUnit(defs: Definitions, sourceCode: String): CompilationUnit = {
val unit = new CompilationUnit(new SourceFile(objectName(defs.state).toString, sourceCode))
unit.untpdTree = wrapped(defs, sourceCode)
unit.result
unit.untpdTree = wrapped(defs)
unit
}

def runCompilation(unit: CompilationUnit, state: State): Result[State] = {
private def runCompilationUnit(unit: CompilationUnit, state: State): Result[(CompilationUnit, State)] = {
val run = state.run
val reporter = state.run.runContext.reporter
run.compileUnits(unit :: Nil)

if (!reporter.hasErrors) state.result
if (!reporter.hasErrors) (unit, state).result
else run.runContext.flushBufferedMessages().errors
}

def compile(parsed: Parsed)(implicit state: State): Result[(CompilationUnit, State)] = {
for {
defs <- definitions(parsed.trees, state)
unit <- createUnit(defs, parsed.sourceCode)
state <- runCompilation(unit, defs.state)
} yield (unit, state)
}

private[this] def addMagicImports(initCtx: Context, objectIndex: Int): Context = {
def addImport(path: TermName)(implicit ctx: Context) = {
val ref = tpd.ref(ctx.requiredModuleRef(path.toTermName))
val symbol = ctx.newImportSymbol(ctx.owner, ref)
val importInfo =
new ImportInfo(implicit ctx => symbol, untpd.Ident(nme.WILDCARD) :: Nil, None)
ctx.fresh.setNewScope.setImportInfo(importInfo)
}

List
.range(1, objectIndex + 1)
.foldLeft(addImport("dotty.Show".toTermName)(initCtx)) { (ictx, i) =>
addImport(nme.EMPTY_PACKAGE ++ "." ++ objectNames(i))(ictx)
}
val defs = definitions(parsed.trees, state)
val unit = createUnit(defs, parsed.sourceCode)
runCompilationUnit(unit, defs.state)
}

def typeOf(expr: String)(implicit state: State): Result[String] =
Expand All @@ -223,7 +217,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
case _ =>
"""Couldn't compute the type of your expression, so sorry :(
|
|Please report this to my masters at Github.com/lampepfl/dotty
|Please report this to my masters at github.com/lampepfl/dotty
""".stripMargin
}
}
Expand All @@ -240,7 +234,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
val tmpl = Template(emptyConstructor,
List(Ident(tpnme.Any)),
EmptyValDef,
state.imports.map(_._1) :+ valdef)
state.imports :+ valdef)

PackageDef(Ident(nme.EMPTY_PACKAGE),
TypeDef("EvaluateExpr".toTypeName, tmpl)
Expand Down Expand Up @@ -286,7 +280,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler {
val src = new SourceFile(s"EvaluateExpr", expr)
val runCtx =
run.runContext.fresh
.setSetting(run.runContext.settings.YstopAfter, List("replFrontEnd"))
.setSetting(run.runContext.settings.YstopAfter, List("frontend"))

wrapped(expr, src, state)(runCtx).flatMap { pkg =>
val unit = new CompilationUnit(src)
Expand Down
26 changes: 9 additions & 17 deletions compiler/src/dotty/tools/repl/ReplDriver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import dotc.core.NameKinds.SimpleNameKind
import dotc.config.CompilerCommand
import dotc.{ Compiler, Driver }
import dotc.printing.SyntaxHighlighting
import dotc.reporting.diagnostic.Message
import dotc.util.Positions.Position
import dotc.util.SourcePosition

import io._

import AmmoniteReader._
Expand Down Expand Up @@ -56,7 +59,7 @@ import results._
case class State(objectIndex: Int,
valIndex: Int,
history: History,
imports: List[(untpd.Import, String)],
imports: List[untpd.Import],
run: Run) {

def withHistory(newEntry: String) = copy(history = newEntry :: history)
Expand Down Expand Up @@ -184,8 +187,8 @@ class ReplDriver(settings: Array[String],
private def readLine()(implicit state: State): ParseResult =
AmmoniteReader(out, state.history, completions(_, _, state))(state.run.runContext).prompt

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 extractImports(trees: List[untpd.Tree]): List[untpd.Import] =
trees.collect { case imp: untpd.Import => imp }

private def interpret(res: ParseResult)(implicit state: State): State =
res match {
Expand Down Expand Up @@ -223,7 +226,7 @@ class ReplDriver(settings: Array[String],
{
case (unit: CompilationUnit, newState: State) => {
val newestWrapper = extractNewestWrapper(unit.untpdTree)
val newImports = newState.imports ++ extractImports(parsed.trees)(newState.run.runContext)
val newImports = newState.imports ++ extractImports(parsed.trees)
val newStateWithImports = newState.copy(imports = newImports)

displayDefinitions(unit.tpdTree, newestWrapper)(newStateWithImports)
Expand Down Expand Up @@ -323,7 +326,7 @@ class ReplDriver(settings: Array[String],
}

case Imports => {
state.imports foreach { case (_, i) => println(SyntaxHighlighting(i)) }
state.imports.foreach(i => out.println(SyntaxHighlighting(i.show(state.run.runContext))))
state.withHistory(Imports.command)
}

Expand Down Expand Up @@ -361,18 +364,7 @@ class ReplDriver(settings: Array[String],

/** A `MessageRenderer` without file positions */
private val messageRenderer = new MessageRendering {
import dotc.reporting.diagnostic._
import dotc.util._
override def messageAndPos(msg: Message, pos: SourcePosition, diagnosticLevel: String)(implicit ctx: Context): String = {
val sb = scala.collection.mutable.StringBuilder.newBuilder
if (pos.exists) {
val (srcBefore, srcAfter, offset) = sourceLines(pos)
val marker = columnMarker(pos, offset)
val err = errorMsg(pos, msg.msg, offset)
sb.append((srcBefore ::: marker :: err :: outer(pos, " " * (offset - 1)) ::: srcAfter).mkString("\n"))
}
sb.toString
}
override def posStr(pos: SourcePosition, diagnosticLevel: String, message: Message)(implicit ctx: Context): String = ""
}

/** Render messages using the `MessageRendering` trait */
Expand Down
18 changes: 8 additions & 10 deletions compiler/src/dotty/tools/repl/ReplFrontEnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,17 @@ import dotc.core.Contexts.Context
* compiler pipeline.
*/
private[repl] class REPLFrontEnd extends FrontEnd {
override def phaseName = "replFrontEnd"
override def phaseName = "frontend"

override def isRunnable(implicit ctx: Context) = true

override def runOn(units: List[CompilationUnit])(implicit ctx: Context) = {
val unitContexts = for (unit <- units) yield ctx.fresh.setCompilationUnit(unit)
var remaining = unitContexts
while (remaining.nonEmpty) {
enterSyms(remaining.head)
remaining = remaining.tail
}
unitContexts.foreach(enterAnnotations(_))
unitContexts.foreach(typeCheck(_))
unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper)
assert(units.size == 1) // REPl runs one compilation unit at a time

val unitContext = ctx.fresh.setCompilationUnit(units.head)
enterSyms(unitContext)
enterAnnotations(unitContext)
typeCheck(unitContext)
List(unitContext.compilationUnit)
}
}
4 changes: 2 additions & 2 deletions compiler/test-resources/repl/patdef
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ val x: Int = 1
scala> val List(_ @ List(x)) = List(List(2))
val x: Int = 2
scala> val B @ List(), C: List[Int] = List()
val B: List[Int] = Nil
val C: List[Int] = Nil
val B: List[Int] = List()
val C: List[Int] = List()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

2 changes: 1 addition & 1 deletion dist/bin/dotr
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ if [ $run_repl == true ] || [ ${#residual_args[@]} -eq 0 ]; then
if [ "$CLASS_PATH" ]; then
cp_arg="-classpath $CLASS_PATH"
fi
echo "Starting dotty REPL"
echo "Starting dotty REPL..."
eval "$PROG_HOME/bin/dotc $cp_arg -repl ${residual_args[@]}"
else
cp_arg="-classpath $DOTTY_LIB$PSEP$SCALA_LIB"
Expand Down
35 changes: 18 additions & 17 deletions library/src/dotty/Show.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,22 @@ object Show {
implicit val stringShow: Show[String] = new Show[String] {
// From 2.12 spec, `charEscapeSeq`:
// ‘\‘ (‘b‘ | ‘t‘ | ‘n‘ | ‘f‘ | ‘r‘ | ‘"‘ | ‘'‘ | ‘\‘)
def show(str: String) =
"\"" + {
val sb = new StringBuilder
str.foreach {
case '\b' => sb.append("\\b")
case '\t' => sb.append("\\t")
case '\n' => sb.append("\\n")
case '\f' => sb.append("\\f")
case '\r' => sb.append("\\r")
case '\'' => sb.append("\\'")
case '\"' => sb.append("\\\"")
case c => sb.append(c)
}
sb.toString
} + "\""
def show(str: String) = {
val sb = new StringBuilder
sb.append("\"")
str.foreach {
case '\b' => sb.append("\\b")
case '\t' => sb.append("\\t")
case '\n' => sb.append("\\n")
case '\f' => sb.append("\\f")
case '\r' => sb.append("\\r")
case '\'' => sb.append("\\'")
case '\"' => sb.append("\\\"")
case c => sb.append(c)
}
sb.append("\"")
sb.toString
}
}

implicit val intShow: Show[Int] = new Show[Int] {
Expand Down Expand Up @@ -72,12 +73,12 @@ object Show {

implicit def showList[T](implicit st: Show[T]): Show[List[T]] = new Show[List[T]] {
def show(xs: List[T]) =
if (xs.isEmpty) "Nil"
if (xs.isEmpty) "List()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

else "List(" + xs.map(_.show).mkString(", ") + ")"
}

implicit val showNil: Show[Nil.type] = new Show[Nil.type] {
def show(xs: Nil.type) = "Nil"
def show(xs: Nil.type) = "List()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is what scalac does:

scala> List()
res0: List[Nothing] = List()

scala> List(1, 2)
res1: List[Int] = List(1, 2)

I think it is more consistent

Copy link
Contributor

@felixmulder felixmulder Nov 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯\__(ツ)_/¯

Sounds like a debate for the ages. I'm definitely fine with the change, just wanted to know if there was some other motive beside the superficial one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purely superficial =)

}

implicit def showOption[T](implicit st: Show[T]): Show[Option[T]] = new Show[Option[T]] {
Expand Down