All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.repl.ParseResult.scala Maven / Gradle / Ivy

package dotty.tools
package repl

import dotc.CompilationUnit
import dotc.ast.untpd
import dotc.core.Contexts.*
import dotc.core.StdNames.str
import dotc.parsing.Parsers.Parser
import dotc.parsing.Tokens
import dotc.reporting.{Diagnostic, StoreReporter}
import dotc.util.SourceFile

import scala.annotation.internal.sharable

/** A parsing result from string input */
sealed trait ParseResult

/** An error free parsing resulting in a list of untyped trees */
case class Parsed(source: SourceFile, trees: List[untpd.Tree], reporter: StoreReporter) extends ParseResult

/** A parsing result containing syntax `errors` */
case class SyntaxErrors(sourceCode: String,
                        errors: List[Diagnostic],
                        trees: List[untpd.Tree]) extends ParseResult

/** Parsed result is simply a newline */
case object Newline extends ParseResult

/** `ctrl-c` obtained from input string */
case object SigKill extends ParseResult

/** A command is on the format:
 *
 *  ```none
 *  :commandName 
 *  ```
 *  The `Command` trait denotes these commands
 */
sealed trait Command extends ParseResult

/** An unknown command that will not be handled by the REPL */
case class UnknownCommand(cmd: String) extends Command

/** An ambiguous prefix that matches multiple commands */
case class AmbiguousCommand(cmd: String, matchingCommands: List[String]) extends Command

/** `:load ` interprets a scala file as if entered line-by-line into
 *  the REPL
 */
case class Load(path: String) extends Command
object Load {
  val command: String = ":load"
}

/** To find out the type of an expression you may simply do:
 *
 * ```
 * scala> :type (1 * 54).toString
 * String
 * ```
 */
case class TypeOf(expr: String) extends Command
object TypeOf {
  val command: String = ":type"
}

/**
 * A command that is used to display the documentation associated with
 * the given expression.
 */
case class DocOf(expr: String) extends Command
object DocOf {
  val command: String = ":doc"
}

/** `:imports` lists the imports that have been explicitly imported during the
 *  session
 */
case object Imports extends Command {
  val command: String = ":imports"
}

case class Settings(arg: String) extends Command
object Settings {
  val command: String = ":settings"
}

/** Reset the session to the initial state from when the repl program was
 *  started
 */
case class Reset(arg: String) extends Command
object Reset {
  val command: String = ":reset"
}

/** `:quit` exits the repl */
case object Quit extends Command {
  val command: String = ":quit"
  val alias: String = ":exit"
}

/** `:help` shows the different commands implemented by the Dotty repl */
case object Help extends Command {
  val command: String = ":help"
  val text: String =
    """The REPL has several commands available:
      |
      |:help                    print this summary
      |:load              interpret lines in a file
      |:quit                    exit the interpreter
      |:type        evaluate the type of the given expression
      |:doc         print the documentation for the given expression
      |:imports                 show import history
      |:reset [options]         reset the repl to its initial state, forgetting all session entries
      |:settings       update compiler options, if possible
    """.stripMargin
}

object ParseResult {

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

  private def parseStats(using Context): List[untpd.Tree] = {
    val parser = new Parser(ctx.source)
    val stats = parser.blockStatSeq(outermost = true)
    parser.accept(Tokens.EOF)
    stats
  }

  private[repl] val commands: List[(String, String => ParseResult)] = List(
    Quit.command -> (_ => Quit),
    Quit.alias -> (_ => Quit),
    Help.command -> (_  => Help),
    Reset.command -> (arg  => Reset(arg)),
    Imports.command -> (_  => Imports),
    Load.command -> (arg => Load(arg)),
    TypeOf.command -> (arg => TypeOf(arg)),
    DocOf.command -> (arg => DocOf(arg)),
    Settings.command -> (arg => Settings(arg)),
  )

  def apply(source: SourceFile)(using state: State): ParseResult = {
    val sourceCode = source.content().mkString
    sourceCode match {
      case "" => Newline
      case CommandExtract(cmd, arg) => {
        val matchingCommands = commands.filter((command, _) => command.startsWith(cmd))
        matchingCommands match {
          case Nil => UnknownCommand(cmd)
          case (_, f) :: Nil => f(arg)
          case multiple => AmbiguousCommand(cmd, multiple.map(_._1))
        }
      }
      case _ =>
        inContext(state.context) {
          val reporter = newStoreReporter
          val stats = parseStats(using state.context.fresh.setReporter(reporter).withSource(source))

          if (reporter.hasErrors)
            SyntaxErrors(
              sourceCode,
              reporter.removeBufferedMessages,
              stats)
          else
            Parsed(source, stats, reporter)
        }
    }
  }

  def apply(sourceCode: String)(using state: State): ParseResult =
    maybeIncomplete(sourceCode, maybeIncomplete = true)

  def complete(sourceCode: String)(using state: State): ParseResult =
    maybeIncomplete(sourceCode, maybeIncomplete = false)

  private def maybeIncomplete(sourceCode: String, maybeIncomplete: Boolean)(using state: State): ParseResult =
    apply(SourceFile.virtual(str.REPL_SESSION_LINE + (state.objectIndex + 1), sourceCode, maybeIncomplete = maybeIncomplete))

  /** Check if the input is incomplete.
   *
   *  This can be used in order to check if a newline can be inserted without
   *  having to evaluate the expression.
   */
  def isIncomplete(sourceCode: String)(using Context): Boolean =
    sourceCode match {
      case CommandExtract(_) | "" => false
      case _ => {
        val reporter = newStoreReporter
        val source   = SourceFile.virtual("", sourceCode, maybeIncomplete = true)
        val unit     = CompilationUnit(source, mustExist = false)
        val localCtx = ctx.fresh
                          .setCompilationUnit(unit)
                          .setReporter(reporter)
        var needsMore = false
        reporter.withIncompleteHandler((_, _) => needsMore = true) {
          parseStats(using localCtx)
        }
        !reporter.hasErrors && needsMore
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy