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

kantan.repl.md.process.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021 Nicolas Rinaudo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kantan.repl.md.process

import dotty.tools.dotc.reporting.{Diagnostic, Message}
import kantan.repl.{Repl, RuntimeError}
import kantan.repl.md.markdown.{Block, Modifier}
import java.io.File
import scala.util.{Failure, Success, Try}

// - Error handling ----------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------

/** Errors that can occur while processing some input markdown: unexpected errors or unexpected successes. */
abstract class UnexpectedException extends Exception {
  def source: String
  def line: Int
  def column: Int
  def error: String

  override def getMessage = s"""|$source:$line:$column
                                |$error""".stripMargin
}

object UnexpectedException {
  case class Error(source: String, line: Int, column: Int, error: String) extends UnexpectedException

  case class Success(source: String, line: Int) extends UnexpectedException {
    override val column = 0
    override val error  = "Excepted an error but encountered none."
  }
}

// - Block type --------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------

/** Represents a markdown block after being processed through the repl. */
enum ProcessedBlock {
  case Repl(input: String, modifier: Modifier, output: String)
  case Other(content: String)
}

// - Logic -------------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------

private def formatCompilerOutput(diag: Diagnostic, header: String): String = {
  val aligner = " " * header.length

  val report = diag.pos.lineContent + diag.pos.startColumnPadding + "^"

  s"$report\n$header" + diag.message.trim().replace("\n", s"\n$aligner")
}

private def formatDiagnostic(diag: Diagnostic): String =
  diag match {
    case _: RuntimeError       => diag.message.trim()
    case _: Diagnostic.Error   => formatCompilerOutput(diag, "⛔ ")
    case _: Diagnostic.Warning => formatCompilerOutput(diag, "⚠ ")
    case _                     => diag.message.trim()
  }

private def formatOutput(diags: List[Diagnostic]): String = {
  val raw = diags.map(formatDiagnostic).mkString("\n")

  if raw.nonEmpty then "// " + raw.replace("\n", "\n// ")
  else raw
}

private def firstError(diags: List[Diagnostic]): Option[Diagnostic] = diags.find {
  case _: Diagnostic.Error => true
  case _                   => false
}

def evaluate(repl: Repl, source: String, blocks: List[Block]): Try[List[ProcessedBlock]] = Try {
  blocks.map {
    case Block.Other(content) =>
      ProcessedBlock.Other(content)

    case Block.Repl(code, modifier, startAt) =>
      if(modifier == Modifier.Reset)
        repl.resetToInitial()

      val diags = repl.run(code + "\n")

      firstError(diags) match {
        // We need to add 2 to the error line because:
        // - lines are 0-indexed internally, but should be reported 1-indexed
        // - we need to take the block declaration (```scala repl) into account.
        case Some(error) if modifier != Modifier.Fail =>
          throw new UnexpectedException.Error(
            source = source,
            line = startAt + error.pos.line + 2,
            column = error.pos.column,
            error = s"${error.pos.lineContent}${error.pos.startColumnPadding}^\n${error.message.trim()}"
          )

        case None if modifier == Modifier.Fail =>
          throw new UnexpectedException.Success(
            source = source,
            line = startAt + 2
          )

        case _ =>
      }

      ProcessedBlock.Repl(code, modifier, formatOutput(diags))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy