hamzaremmal/amy

View on GitHub
compiler/src/main/scala/amyc/utils/Reporter.scala

Summary

Maintainability
A
0 mins
Test Coverage
C
78%
package amyc.utils

import amyc.core.Context
import amyc.ctx

import java.io.File
import scala.io.Source

// Reports errors and warnings during compilation
class Reporter {

  /** Issues some information from the compiler */
  def info(msg: Any, pos: Position = NoPosition): Unit = {
    report("[ Info  ]", msg, pos)
  }

  /** Issues a warning from the compiler */
  def warning(msg: Any, pos: Position = NoPosition): Unit = {
    report("[Warning]", msg, pos)
  }

  private var hasErrors = false

  /** Issues a recoverable error message */
  def error(msg: Any, pos: Position = NoPosition): Unit = {
    hasErrors = true
    report("[ Error ]", msg, pos)
  }

  /** Used for an unrecoverable error: Issues a message, then exits the compiler */
  def fatal[A](msg: Any, pos: Position = NoPosition): A = {
    report("[ Fatal ]", msg, pos)
    // Despite printing the message, we store it in the error for testing
    val errMsg = s"$pos: $msg"
    throw AmycFatalError(errMsg)
  }

  // Versions for Positioned
  def info(msg: Any, pos: Positioned): Unit = info(msg, pos.position)
  def warning(msg: Any, pos: Positioned): Unit = warning(msg, pos.position)
  def error(msg: Any, pos: Positioned): Unit = error(msg, pos.position)
  def fatal[A](msg: Any, pos: Positioned): A = fatal(msg, pos.position)


  /** Terminates the compiler if any errors have been detected. */
  def terminateIfErrors()(using Context) = {
    if (hasErrors) {
      fatal(s"There were errors at phase ${ctx.phase}.")
    }
  }

  private def err(msg: String): Unit = {
    Console.err.println(msg)
  }

  private def report(prefix: String, msg: Any, pos: Position): Unit = {
    if (pos.isDefined) {
      err(s"$prefix $pos: $msg")

      val lines = getLines(pos.file)

      if (pos.line > 0 && pos.line-1 < lines.size) {
        err(s"$prefix ${lines(pos.line-1)}")
        err(prefix + " " + " "*(pos.col - 1)+"^")
      } else {
        err(s"$prefix <line unavailable in source file>")
      }
    } else {
      err(s"$prefix $msg")
    }
  }

  private var filesToLines = Map[File, IndexedSeq[String]]()

  private def getLines(f: File): IndexedSeq[String] = {
    filesToLines.get(f) match {
      case Some(lines) =>
        lines

      case None =>
        val source = Source.fromFile(f).withPositioning(true)
        val lines = source.getLines().toIndexedSeq
        source.close()

        filesToLines += f -> lines

        lines
    }
  }
}