aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
blob: b3b7ab13c98e7287c0f7cdef746c2bd106a75bb6 (plain) (tree)





























































                                                                                               

                              















                                                                                                                       

                                          






                                        









                                                           











                                                       

                                                                  











                                                                                        












































































                                                                                        
                       









                                                                                      
                          
                                                                             
                                              









                                                     









































































                                                                                        
                               












































































































































































































                                                                                                                
                                                         






































































































































































                                                                                               
                                                 




































































                                                                             

                                                                              







                            



                                                


                                     
                                                         
                                           




























































                                                              
package dotty.tools
package dotc
package repl

import java.io.{File, PrintWriter, StringWriter, Writer}
import java.lang.{Class, ClassLoader}
import java.net.{URL, URLClassLoader}

import scala.collection.immutable.ListSet
import scala.collection.mutable
import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}

//import ast.parser.SyntaxAnalyzer
import io.{PlainFile, VirtualDirectory}
import scala.reflect.io.{PlainDirectory, Directory}
import reporting.{ConsoleReporter, Reporter}
import core.Flags
import util.{SourceFile, NameTransformer}
import io.ClassPath
import ast.Trees._
import parsing.Parsers._
import core._
import dotty.tools.backend.jvm.GenBCode
import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._
import Decorators._
import scala.util.control.NonFatal

/** An interpreter for Scala code which is based on the `dotc` compiler.
 *
 *  The overall approach is based on compiling the requested code and then
 *  using a Java classloader and Java reflection to run the code
 *  and access its results.
 *
 *  In more detail, a single compiler instance is used
 *  to accumulate all successfully compiled or interpreted Scala code.  To
 *  "interpret" a line of code, the compiler generates a fresh object that
 *  includes the line of code and which has public definition(s) to export
 *  all variables defined by that code.  To extract the result of an
 *  interpreted line to show the user, a second "result object" is created
 *  which imports the variables exported by the above object and then
 *  exports a single definition named "result".  To accommodate user expressions
 *  that read from variables or methods defined in previous statements, "import"
 *  statements are used.
 *
 * This interpreter shares the strengths and weaknesses of using the
 *  full compiler-to-Java.  The main strength is that interpreted code
 *  behaves exactly as does compiled code, including running at full speed.
 *  The main weakness is that redefining classes and methods is not handled
 *  properly, because rebinding at the Java level is technically difficult.
 *
 * @author Moez A. Abdel-Gawad
 * @author Lex Spoon
 * @author Martin Odersky
 *
 * @param out   The output to use for diagnostics
 * @param ictx  The context to use for initialization of the interpreter,
 *              needed to access the current classpath.
 */
class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler with Interpreter {
  import ast.untpd._
  import CompilingInterpreter._

  ictx.base.initialize()(ictx)

  /** directory to save .class files to */
  val virtualDirectory =
    if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None)
    else new PlainDirectory(new Directory(new java.io.File(ictx.settings.d.value(ictx)))) // for now, to help debugging

  /** A GenBCode phase that uses `virtualDirectory` for its output */
  private class REPLGenBCode extends GenBCode {
    override def outputDir(implicit ctx: Context) = virtualDirectory
  }

  /** Phases of this compiler use `REPLGenBCode` instead of `GenBCode`. */
  override def phases = Phases.replace(
    classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases)

  /** whether to print out result lines */
  private var printResults: Boolean = true
  private var delayOutput: Boolean = false

  var previousOutput: List[String] = Nil

  override def lastOutput() = {
    val prev = previousOutput
    previousOutput = Nil
    prev
  }

  override def delayOutputDuring[T](operation: => T): T = {
    val old = delayOutput
    try {
      delayOutput = true
      operation
    } finally {
      delayOutput = old
    }
  }

  /** Temporarily be quiet */
  override def beQuietDuring[T](operation: => T): T = {
    val wasPrinting = printResults
    try {
      printResults = false
      operation
    } finally {
      printResults = wasPrinting
    }
  }

  private def newReporter = new ConsoleReporter(Console.in, out) {
    override def printMessage(msg: String) = {
      if (!delayOutput) {
        out.print(/*clean*/(msg) + "\n")
          // Suppress clean for now for compiler messages
          // Otherwise we will completely delete all references to
          // line$object$ module classes. The previous interpreter did not
          // have the project because the module class was written without the final `$'
          // and therefore escaped the purge. We can turn this back on once
          // we drop the final `$' from module classes.
        out.flush()
      } else {
        previousOutput = (/*clean*/(msg) + "\n") :: previousOutput
      }
    }
  }

  /** the previous requests this interpreter has processed */
  private val prevRequests = new ArrayBuffer[Request]()

  /** the compiler's classpath, as URL's */
  val compilerClasspath: List[URL] = ictx.platform.classPath(ictx).asURLs

  protected def parentClassLoader: ClassLoader = classOf[Interpreter].getClassLoader

  /* A single class loader is used for all commands interpreted by this Interpreter.
     It would also be possible to create a new class loader for each command
     to interpret.  The advantages of the current approach are:

       - Expressions are only evaluated one time.  This is especially
         significant for I/O, e.g. "val x = Console.readLine"

     The main disadvantage is:

       - Objects, classes, and methods cannot be rebound.  Instead, definitions
         shadow the old ones, and old code objects refer to the old
         definitions.
  */
  /** class loader used to load compiled code */
  val classLoader: ClassLoader = {
    val parent = new URLClassLoader(compilerClasspath.toArray, parentClassLoader)
    new AbstractFileClassLoader(virtualDirectory, parent)
  }

  // Set the current Java "context" class loader to this interpreter's class loader
  Thread.currentThread.setContextClassLoader(classLoader)

  /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */
  private def parse(line: String)(implicit ctx: Context): Option[List[Tree]] = {
    var justNeedsMore = false
    val reporter = newReporter
    reporter.withIncompleteHandler { _ => _ => justNeedsMore = true } {
      // simple parse: just parse it, nothing else
      def simpleParse(code: String)(implicit ctx: Context): List[Tree] = {
        val source = new SourceFile("<console>", code.toCharArray())
        val parser = new Parser(source)
        val (selfDef, stats) = parser.templateStatSeq
        stats
      }
      val trees = simpleParse(line)(ctx.fresh.setReporter(reporter))
      if (reporter.hasErrors) {
        Some(Nil) // the result did not parse, so stop
      } else if (justNeedsMore) {
        None
      } else {
        Some(trees)
      }
    }
  }

  /** Compile a SourceFile.  Returns the root context of the run that compiled the file.
   */
  def compileSources(sources: List[SourceFile])(implicit ctx: Context): Context = {
    val reporter = newReporter
    val run = newRun(ctx.fresh.setReporter(reporter))
    run.compileSources(sources)
    run.runContext
  }

  /** Compile a string.  Returns true if there are no
   *  compilation errors, or false otherwise.
   */
  def compileString(code: String)(implicit ctx: Context): Boolean = {
    val runCtx = compileSources(List(new SourceFile("<script>", code.toCharArray)))
    !runCtx.reporter.hasErrors
  }

  override def interpret(line: String)(implicit ctx: Context): Interpreter.Result = {
    // if (prevRequests.isEmpty)
    //  new Run(this) // initialize the compiler // (not sure this is needed)
    // parse
    parse(line) match {
      case None => Interpreter.Incomplete
      case Some(Nil) => Interpreter.Error // parse error or empty input
      case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] =>
        interpret(s"val $newVarName =\n$line")
      case Some(trees) =>
        val req = new Request(line, newLineName)
        if (!req.compile())
          Interpreter.Error // an error happened during compilation, e.g. a type error
        else {
          val (interpreterResultString, succeeded) = req.loadAndRun()
          if (delayOutput)
            previousOutput = clean(interpreterResultString) :: previousOutput
          else if (printResults || !succeeded)
            out.print(clean(interpreterResultString))
          if (succeeded) {
            prevRequests += req
            Interpreter.Success
          }
          else Interpreter.Error
        }
    }
  }

  /** Trait collecting info about one of the statements of an interpreter request */
  private trait StatementInfo {
    /** The statement */
    def statement: Tree

    /** The names defined previously and referred to in the statement */
    def usedNames: List[Name]

    /** The names defined in the statement */
    val boundNames: List[Name]

    /** Statement is an import that contains a wildcard */
    val importsWildcard: Boolean

    /** The names imported by the statement (if it is an import clause) */
    val importedNames: Seq[Name]

    /** Statement defines an implicit calue or method */
    val definesImplicit: Boolean
  }

  /** One line of code submitted by the user for interpretation */
  private class Request(val line: String, val lineName: String)(implicit ctx: Context) {
    private val trees = parse(line) match {
      case Some(ts) => ts
      case None => Nil
    }

    /** name to use for the object that will compute "line" */
    private def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX

    /** name of the object that retrieves the result from the above object */
    private def resultObjectName = "RequestResult$" + objectName

    private def chooseHandler(stat: Tree): StatementHandler = stat match {
      case stat: DefDef => new DefHandler(stat)
      case stat: ValDef => new ValHandler(stat)
      case stat @ Assign(Ident(_), _) => new AssignHandler(stat)
      case stat: ModuleDef => new ModuleHandler(stat)
      case stat: TypeDef if stat.isClassDef => new ClassHandler(stat)
      case stat: TypeDef => new TypeAliasHandler(stat)
      case stat: Import => new ImportHandler(stat)
//    case DocDef(_, documented) => chooseHandler(documented)
      case stat => new GenericHandler(stat)
    }

    private val handlers: List[StatementHandler] = trees.map(chooseHandler)

    /** all (public) names defined by these statements */
    private val boundNames = ListSet(handlers.flatMap(_.boundNames): _*).toList

    /** list of names used by this expression */
    private val usedNames: List[Name] = handlers.flatMap(_.usedNames)

    private val (importsPreamble, importsTrailer, accessPath) =
      importsCode(usedNames.toSet)

    /** Code to access a variable with the specified name */
    private def fullPath(vname: String): String = s"$objectName$accessPath.`$vname`"

    /** Code to access a variable with the specified name */
    private def fullPath(vname: Name): String = fullPath(vname.toString)

    /** the line of code to compute */
    private def toCompute = line

    /** generate the source code for the object that computes this request
     *  TODO Reformulate in a functional way
     */
    private def objectSourceCode: String =
      stringFrom { code =>
        // header for the wrapper object
        code.println("object " + objectName + " {")
        code.print(importsPreamble)
        code.println(toCompute)
        handlers.foreach(_.extraCodeToEvaluate(this,code))
        code.println(importsTrailer)
        //end the wrapper object
        code.println(";}")
      }

    /** Types of variables defined by this request.  They are computed
        after compilation of the main object */
    private var typeOf: Map[Name, String] = _

    /** generate source code for the object that retrieves the result
        from objectSourceCode */
    private def resultObjectSourceCode: String =
      stringFrom(code => {
        code.println("object " + resultObjectName)
        code.println("{ val result: String = {")
        code.println(objectName + accessPath + ";")  // evaluate the object, to make sure its constructor is run
        code.print("(\"\"")  // print an initial empty string, so later code can
                            // uniformly be: + morestuff
        handlers.foreach(_.resultExtractionCode(this, code))
        code.println("\n)}")
        code.println(";}")
      })


    /** Compile the object file.  Returns whether the compilation succeeded.
     *  If all goes well, the "types" map is computed. */
    def compile(): Boolean = {
      val compileCtx = compileSources(
        List(new SourceFile("<console>", objectSourceCode.toCharArray)))
      !compileCtx.reporter.hasErrors && {
        this.typeOf = findTypes(compileCtx)
        val resultCtx = compileSources(
          List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
        !resultCtx.reporter.hasErrors
      }
    }

    /** Dig the types of all bound variables out of the compiler run.
     *  TODO: Change the interface so that we typecheck, and then transform
     *  directly. Treating the compiler as less of a blackbox will require
     *  much less magic here.
     */
    private def findTypes(implicit ctx: Context): Map[Name, String] = {
      def valAndVarNames = handlers.flatMap(_.valAndVarNames)
      def defNames = handlers.flatMap(_.defNames)

      def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = {
        /** the outermost wrapper object */
        val outerResObjSym: Symbol =
          defn.EmptyPackageClass.info.decl(objectName.toTermName).symbol

        /** the innermost object inside the wrapper, found by
          * following accessPath into the outer one. */
        val resObjSym =
          (accessPath.split("\\.")).foldLeft(outerResObjSym) { (sym,str) =>
            if (str.isEmpty) sym
            else
              ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
                sym.info.member(str.toTermName).symbol
              }
          }

        names.foldLeft(Map.empty[Name,String]) { (map, name) =>
          val rawType =
            ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
              resObjSym.info.member(name).info
            }

          // the types are all =>T; remove the =>
          val cleanedType = rawType.widenExpr

          map + (name ->
            ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
              cleanedType.show
            })
        }
      }

      val names1 = getTypes(valAndVarNames, n => n.toTermName.fieldName)
      val names2 = getTypes(defNames, identity)
      names1 ++ names2
    }

    /** load and run the code using reflection.
     *  @return  A pair consisting of the run's result as a string, and
     *           a boolean indicating whether the run succeeded without throwing
     *           an exception.
     */
    def loadAndRun(): (String, Boolean) = {
      val interpreterResultObject: Class[_] =
        Class.forName(resultObjectName, true, classLoader)
      val resultValMethod: java.lang.reflect.Method =
        interpreterResultObject.getMethod("result")
      try {
        (resultValMethod.invoke(interpreterResultObject).toString, true)
      } catch {
        case NonFatal(ex) =>
          def cause(ex: Throwable): Throwable =
            if (ex.getCause eq null) ex else cause(ex.getCause)
          val orig = cause(ex)
          (stringFrom(str => orig.printStackTrace(str)), false)
      }
    }

    /** Compute imports that allow definitions from previous
     *  requests to be visible in a new request.  Returns
     *  three pieces of related code as strings:
     *
     *  1. A _preamble_: An initial code fragment that should go before
     *  the code of the new request.
     *
     *  2. A _trailer_: A code fragment that should go after the code
     *  of the new request.
     *
     *  3. An _access path_ which can be traversed to access
     *  any bindings inside code wrapped by #1 and #2 .
     *
     *  The argument is a set of Names that need to be imported.
     *
     *  Limitations: This method is not as precise as it could be.
     *  (1) It does not process wildcard imports to see what exactly
     *  they import.
     *  (2) If it imports any names from a request, it imports all
     *  of them, which is not really necessary.
     *  (3) It imports multiple same-named implicits, but only the
     *  last one imported is actually usable.
     */
    private def importsCode(wanted: Set[Name]): (String, String, String) = {
      /** Narrow down the list of requests from which imports
       *  should be taken.  Removes requests which cannot contribute
       *  useful imports for the specified set of wanted names.
       */
      def reqsToUse: List[(Request, StatementInfo)] = {
        /** Loop through a list of StatementHandlers and select
         *  which ones to keep.  'wanted' is the set of
         *  names that need to be imported.
         */
        def select(reqs: List[(Request, StatementInfo)], wanted: Set[Name]): List[(Request, StatementInfo)] = {
          reqs match {
            case Nil => Nil

            case (req, handler) :: rest =>
              val keepit =
                (handler.definesImplicit ||
                  handler.importsWildcard ||
                  handler.importedNames.exists(wanted.contains(_)) ||
                  handler.boundNames.exists(wanted.contains(_)))

              val newWanted =
                if (keepit) {
                  (wanted
                    ++ handler.usedNames
                    -- handler.boundNames
                    -- handler.importedNames)
                } else {
                  wanted
                }

              val restToKeep = select(rest, newWanted)

              if (keepit)
                (req, handler) :: restToKeep
              else
                restToKeep
          }
        }

        val rhpairs = for {
          req <- prevRequests.toList.reverse
          handler <- req.handlers
        } yield (req, handler)

        select(rhpairs, wanted).reverse
      }

      val preamble = new StringBuffer
      val trailingBraces = new StringBuffer
      val accessPath = new StringBuffer
      val impname = INTERPRETER_IMPORT_WRAPPER
      val currentImps = mutable.Set[Name]()

      // add code for a new object to hold some imports
      def addWrapper(): Unit = {
        preamble.append("object " + impname + "{\n")
        trailingBraces.append("}\n")
        accessPath.append("." + impname)
        currentImps.clear
      }

      addWrapper()

      // loop through previous requests, adding imports
      // for each one
      for ((req, handler) <- reqsToUse) {
        // If the user entered an import, then just use it

        // add an import wrapping level if the import might
        // conflict with some other import
        if (handler.importsWildcard ||
          currentImps.exists(handler.importedNames.contains))
          if (!currentImps.isEmpty)
            addWrapper()

        if (handler.statement.isInstanceOf[Import])
          preamble.append(handler.statement.show + ";\n")

        // give wildcard imports a import wrapper all to their own
        if (handler.importsWildcard)
          addWrapper()
        else
          currentImps ++= handler.importedNames

        // For other requests, import each bound variable.
        // import them explicitly instead of with _, so that
        // ambiguity errors will not be generated. Also, quote
        // the name of the variable, so that we don't need to
        // handle quoting keywords separately.
        for (imv <- handler.boundNames) {
          if (currentImps.contains(imv))
            addWrapper()
          preamble.append("import ")
          preamble.append(req.objectName + req.accessPath + ".`" + imv + "`;\n")
          currentImps += imv
        }
      }

      addWrapper() // Add one extra wrapper, to prevent warnings
      // in the frequent case of redefining
      // the value bound in the last interpreter
      // request.

      (preamble.toString, trailingBraces.toString, accessPath.toString)
    }

    // ------ Handlers ------------------------------------------

    /** Class to handle one statement among all the statements included
     *  in a single interpreter request.
     */
    private sealed abstract class StatementHandler(val statement: Tree) extends StatementInfo {
      val usedNames: List[Name] = {
        val ivt = new UntypedTreeAccumulator[mutable.Set[Name]] {
          override def apply(ns: mutable.Set[Name], tree: Tree)(implicit ctx: Context) =
            tree match {
              case Ident(name) => ns += name
              case _ => foldOver(ns, tree)
            }
        }
        ivt.foldOver(HashSet(), statement).toList
      }
      val boundNames: List[Name] = Nil
      def valAndVarNames: List[Name] = Nil
      def defNames: List[Name] = Nil
      val importsWildcard = false
      val importedNames: Seq[Name] = Nil
      val definesImplicit = statement match {
        case tree: MemberDef => tree.mods.is(Flags.Implicit)
        case _ => false
      }

      def extraCodeToEvaluate(req: Request, code: PrintWriter) = {}
      def resultExtractionCode(req: Request, code: PrintWriter) = {}
    }

    private class GenericHandler(statement: Tree) extends StatementHandler(statement)

    private class ValHandler(statement: ValDef) extends StatementHandler(statement) {
      override val boundNames = List(statement.name)
      override def valAndVarNames = boundNames

      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        val vname = statement.name
        if (!statement.mods.is(Flags.AccessFlags) &&
          !(isGeneratedVarName(vname.toString) &&
            req.typeOf(vname.encode) == "Unit")) {
          val prettyName = vname.decode
          code.print(" + \"" + prettyName + ": " +
            string2code(req.typeOf(vname)) +
            " = \" + " +
            " (if(" +
            req.fullPath(vname) +
            ".asInstanceOf[AnyRef] != null) " +
            " ((if(" +
            req.fullPath(vname) +
            ".toString().contains('\\n')) " +
            " \"\\n\" else \"\") + " +
            req.fullPath(vname) + ".toString() + \"\\n\") else \"null\\n\") ")
        }
      }
    }

    private class DefHandler(defDef: DefDef) extends StatementHandler(defDef) {
      override val boundNames = List(defDef.name)
      override def defNames = boundNames

      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        if (!defDef.mods.is(Flags.AccessFlags))
          code.print("+\"" + string2code(defDef.name.toString) + ": " +
            string2code(req.typeOf(defDef.name)) + "\\n\"")
      }
    }

    private class AssignHandler(statement: Assign) extends StatementHandler(statement) {
      val lhs = statement.lhs.asInstanceOf[Ident] // an unfortunate limitation

      val helperName = newInternalVarName().toTermName
      override val valAndVarNames = List(helperName)

      override def extraCodeToEvaluate(req: Request, code: PrintWriter): Unit = {
        code.println("val " + helperName + " = " + statement.lhs + ";")
      }

      /** Print out lhs instead of the generated varName */
      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        code.print(" + \"" + lhs + ": " +
          string2code(req.typeOf(helperName.encode)) +
          " = \" + " +
          string2code(req.fullPath(helperName))
          + " + \"\\n\"")
      }
    }

    private class ModuleHandler(module: ModuleDef) extends StatementHandler(module) {
      override val boundNames = List(module.name)

      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        code.println(" + \"defined module " +
          string2code(module.name.toString)
          + "\\n\"")
      }
    }

    private class ClassHandler(classdef: TypeDef)
      extends StatementHandler(classdef) {
      override val boundNames =
        List(classdef.name) :::
          (if (classdef.mods.is(Flags.Case))
            List(classdef.name.toTermName)
          else
            Nil)

      // TODO: MemberDef.keyword does not include "trait";
      // otherwise it could be used here
      def keyword: String =
        if (classdef.mods.is(Flags.Trait)) "trait" else "class"

      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        code.print(
          " + \"defined " +
            keyword +
            " " +
            string2code(classdef.name.toString) +
            "\\n\"")
      }
    }

    private class TypeAliasHandler(typeDef: TypeDef)
      extends StatementHandler(typeDef) {
      override val boundNames =
        if (!typeDef.mods.is(Flags.AccessFlags) && !typeDef.rhs.isInstanceOf[TypeBoundsTree])
          List(typeDef.name)
        else
          Nil

      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        code.println(" + \"defined type alias " +
          string2code(typeDef.name.toString) + "\\n\"")
      }
    }

    private class ImportHandler(imp: Import) extends StatementHandler(imp) {
      override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
        code.println("+ \"" + imp.show + "\\n\"")
      }

      def isWildcardSelector(tree: Tree) = tree match {
        case Ident(nme.USCOREkw) => true
        case _ => false
      }

      /** Whether this import includes a wildcard import */
      override val importsWildcard = imp.selectors.exists(isWildcardSelector)

      /** The individual names imported by this statement */
      override val importedNames: Seq[Name] =
        imp.selectors.filterNot(isWildcardSelector).flatMap {
          case sel: RefTree => List(sel.name.toTypeName, sel.name.toTermName)
          case _ => Nil
        }
    }

  } // end Request

  // ------- String handling ----------------------------------

  /** next line number to use */
  private var nextLineNo = 0

  /** allocate a fresh line name */
  private def newLineName = {
    val num = nextLineNo
    nextLineNo += 1
    INTERPRETER_LINE_PREFIX + num
  }

  /** next result variable number to use */
  private var nextVarNameNo = 0

  /** allocate a fresh variable name */
  private def newVarName = {
    val num = nextVarNameNo
    nextVarNameNo += 1
    INTERPRETER_VAR_PREFIX + num
  }

  /** next internal variable number to use */
  private var nextInternalVarNo = 0

  /** allocate a fresh internal variable name */
  private def newInternalVarName() = {
    val num = nextVarNameNo
    nextVarNameNo += 1
    INTERPRETER_SYNTHVAR_PREFIX + num
  }

  /** Check if a name looks like it was generated by newVarName */
  private def isGeneratedVarName(name: String): Boolean =
    name.startsWith(INTERPRETER_VAR_PREFIX) && {
      val suffix = name.drop(INTERPRETER_VAR_PREFIX.length)
      suffix.forall(_.isDigit)
    }

  /** generate a string using a routine that wants to write on a stream */
  private def stringFrom(writer: PrintWriter => Unit): String = {
    val stringWriter = new StringWriter()
    val stream = new NewLinePrintWriter(stringWriter)
    writer(stream)
    stream.close
    stringWriter.toString
  }

  /** Truncate a string if it is longer than settings.maxPrintString */
  private def truncPrintString(str: String)(implicit ctx: Context): String = {
    val maxpr = ctx.settings.XreplLineWidth.value

    if (maxpr <= 0)
      return str

    if (str.length <= maxpr)
      return str

    val trailer = "..."
    if (maxpr >= trailer.length-1)
      str.substring(0, maxpr-3) + trailer + "\n"
    else
      str.substring(0, maxpr-1)
  }

  /** Clean up a string for output */
  private def clean(str: String)(implicit ctx: Context) =
    truncPrintString(stripWrapperGunk(str))
}

/** Utility methods for the Interpreter. */
object CompilingInterpreter {
  val INTERPRETER_WRAPPER_SUFFIX = "$object"
  val INTERPRETER_LINE_PREFIX = "line"
  val INTERPRETER_VAR_PREFIX = "res"
  val INTERPRETER_IMPORT_WRAPPER = "$iw"
  val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$"

  /** Delete a directory tree recursively.  Use with care!
   */
  private[repl] def deleteRecursively(path: File): Unit = {
    path match  {
      case _ if !path.exists =>
        ()
      case _ if path.isDirectory =>
        for (p <- path.listFiles)
          deleteRecursively(p)
        path.delete
      case _ =>
        path.delete
    }
  }

  /** Heuristically strip interpreter wrapper prefixes
   *  from an interpreter output string.
   */
  def stripWrapperGunk(str: String): String = {
    val wrapregex = "(line[0-9]+\\$object[$.])?(\\$iw[$.])*"
    str.replaceAll(wrapregex, "")
  }

  /** Convert a string into code that can recreate the string.
   *  This requires replacing all special characters by escape
   *  codes. It does not add the surrounding " marks.  */
  def string2code(str: String): String = {
    /** Convert a character to a backslash-u escape */
    def char2uescape(c: Char): String = {
      var rest = c.toInt
      val buf = new StringBuilder
      for (i <- 1 to 4) {
	      buf ++= (rest % 16).toHexString
	      rest = rest / 16
      }
      "\\" + "u" + buf.toString.reverse
    }
    val res = new StringBuilder
    for (c <- str) {
      if ("'\"\\" contains c) {
	      res += '\\'
	      res += c
      } else if (!c.isControl) {
	      res += c
      } else {
	      res ++= char2uescape(c)
      }
    }
    res.toString
  }
}