Skip to content

Commit

Permalink
renderDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
mio-19 committed Jan 18, 2025
1 parent a15aed9 commit 611312e
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 53 deletions.
8 changes: 5 additions & 3 deletions core/src/main/scala/chester/repl/REPLEngine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package chester.repl
import cats.implicits.*
import chester.doc.*
import chester.doc.const.{Colors, ReplaceBracketsWithWord}
import chester.error.unreachable
import chester.error.*
import chester.reader.{ParseError, ReaderREPL}
import chester.syntax.concrete.Expr
import chester.syntax.core.*
Expand Down Expand Up @@ -145,15 +145,17 @@ def REPLEngine[F[_]](using
er: Vector[chester.error.TyckError],
wr: Vector[chester.error.TyckWarning] = Vector()
): F[Unit] = {
given sourceReader: SourceReader = SourceReader.default

for {
_ <- er.traverse(x => {
InTerminal.writeln(
FansiPrettyPrinter.render(x.renderWithLocation, maxWidth)
FansiPrettyPrinter.render(x.renderDoc, maxWidth)
)
})
_ <- wr.traverse(x => {
InTerminal.writeln(
FansiPrettyPrinter.render(x.renderWithLocation, maxWidth)
FansiPrettyPrinter.render(x.renderDoc, maxWidth)
)
})
} yield ()
Expand Down
37 changes: 1 addition & 36 deletions err/src/main/scala/chester/error/TyckProblem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,49 +21,14 @@ sealed trait TyckProblem extends Problem derives ReadWriter {

def hint: ToDoc = empty

override def toDoc(using
options: PrettierOptions
): Doc
override def toDoc(using options: PrettierOptions): Doc

def cause: Term | Expr

override def sourcePos: Option[SourcePos] = cause match {
case x: WithPos => x.sourcePos
case _ => None
}

def renderWithLocation(using
options: PrettierOptions
): Doc = {
val baseMessage = Doc.text(t"Error") <+> this

val locationInfo = sourcePos match {
case Some(pos) =>
val lines = pos.getLinesInRange match {
case Some(lines) =>
lines.map { case (lineNumber, line) =>
Doc.text(t"$lineNumber") <+> Doc.text(line, Styling.BoldOn)
}
case None => Vector.empty
}
val locationHeader = Doc.text(t"Location") <+>
Doc.text(
t"${pos.fileName} [${pos.range.start.line + 1}:${pos.range.start.column.i + 1}] to [${pos.range.end.line + 1}:${pos.range.end.column.i + 1}]",
Styling.BoldOn
)

val codeBlock = Doc.group(Doc.concat(lines.map(_.end)*))

locationHeader <|> codeBlock

case None =>
val causeHeader = Doc.text(t"Cause", Styling.BoldOn)
val causeText = cause
causeHeader <|> causeText
}

baseMessage <|> locationInfo
}
}

sealed trait TyckError extends TyckProblem derives ReadWriter {
Expand Down
60 changes: 47 additions & 13 deletions syntax/shared/src/main/scala/chester/error/Problem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chester.error

import chester.utils.doc.*
import upickle.default.*
import chester.i18n.*

object Problem {
enum Stage derives ReadWriter {
Expand Down Expand Up @@ -66,7 +67,12 @@ private def renderFullDescription(desc: FullDescription)(using options: Prettier
val explanationsDoc = desc.explanations.map { elem =>
val elemDoc = elem.doc.toDoc
elem.sourcePos.flatMap(sourceReader.apply) match {
case Some(source) => elemDoc </> Doc.text(source)
case Some(lines) =>
val sourceLines = lines.map { case (lineNumber, line) =>
Doc.text(t"$lineNumber") <+> Doc.text(line, Styling.BoldOn)
}
val codeBlock = Doc.group(Doc.concat(sourceLines.map(_.end)*))
elemDoc </> codeBlock
case None => elemDoc
}
}
Expand All @@ -82,27 +88,55 @@ private def renderFullDescription(desc: FullDescription)(using options: Prettier
}

private def renderToDocWithSource(p: Problem)(using options: PrettierOptions, sourceReader: SourceReader): Doc = {
val baseDoc = p.toDoc
p.sourcePos.flatMap(sourceReader.apply) match {
case Some(source) =>
baseDoc <> Doc.line <> Doc.text(source)
val severityDoc = p.severity match {
case Problem.Severity.Error => Doc.text(t"Error")
case Problem.Severity.Warning => Doc.text(t"Warning")
case Problem.Severity.Goal => Doc.text(t"Goal")
case Problem.Severity.Info => Doc.text(t"Info")
}

val baseDoc = severityDoc <+> p.toDoc

p.sourcePos match {
case Some(pos) =>
val locationHeader = Doc.text(t"Location") <+>
Doc.text(
t"${pos.fileName} [${pos.range.start.line + 1}:${pos.range.start.column.i + 1}] to [${pos.range.end.line + 1}:${pos.range.end.column.i + 1}]",
Styling.BoldOn
)

val sourceLines = sourceReader(pos).map { lines =>
lines.map { case (lineNumber, line) =>
Doc.text(t"$lineNumber") <+> Doc.text(line, Styling.BoldOn)
}
}.getOrElse(Vector.empty)

val codeBlock = Doc.group(Doc.concat(sourceLines.map(_.end)*))

baseDoc <|> locationHeader <|> codeBlock

case None =>
baseDoc
}
}

case class SourceReader(readSource: SourcePos => Option[String]) {
def apply(pos: SourcePos): Option[String] = readSource(pos)
/** A reader for source code that provides line-numbered content.
*
* @param readSource A function that takes a SourcePos and returns line-numbered content.
* The returned Vector contains tuples of (lineNumber, lineContent) where:
* - lineNumber: 1-based line numbers (e.g., lines 3,4,5)
* - lineContent: The actual text content of that line
* Note: While internal line tracking is 0-based, this API returns 1-based line numbers for display
*/
case class SourceReader(readSource: SourcePos => Option[Vector[(Int, String)]]) {
def apply(pos: SourcePos): Option[Vector[(Int, String)]] = readSource(pos)
}

object SourceReader {
def fromFileContent(content: FileContent): SourceReader = {
SourceReader { pos =>
pos.getLinesInRange.map { lines =>
lines.map { case (_, line) => line }.mkString("\n")
}
}
SourceReader { pos => pos.getLinesInRange }
}

def default: SourceReader = SourceReader { pos => pos.getLinesInRange }

def empty: SourceReader = SourceReader(_ => None)
}
9 changes: 8 additions & 1 deletion syntax/shared/src/main/scala/chester/error/SourcePos.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ case class SourcePos(source: SourceOffset, range: RangeInFile) derives ReadWrite
)
val fileName = source.fileName

// Method to extract all lines within the range with line numbers
/** Extracts all lines within the range with their line numbers.
*
* @return Option containing a Vector of (lineNumber, lineContent) tuples where:
* - lineNumber: 1-based line numbers for display (e.g., if range spans lines 3-5,
* returns exactly [(3,"line3"), (4,"line4"), (5,"line5")])
* - lineContent: The actual text content of that line
* Note: While internal line tracking is 0-based, this API returns 1-based line numbers for display
*/
def getLinesInRange: Option[Vector[(Int, String)]] = fileContent map { fileContent =>
val startLine = range.start.line - fileContent.lineOffset
val endLine = range.end.line - fileContent.lineOffset
Expand Down

0 comments on commit 611312e

Please sign in to comment.