diff --git a/project.scala b/project.scala index b6fb20a..e4f1c0f 100644 --- a/project.scala +++ b/project.scala @@ -9,6 +9,8 @@ //> using dep org.typelevel::cats-effect::3.5.7 //> using dep com.monovore::decline::2.5.0 //> using dep com.monovore::decline-effect::2.5.0 +//> using dep org.typelevel::log4cats-slf4j::2.7.0 +//> using dep org.slf4j:slf4j-simple:2.0.17 //> using test.dep org.scalatest::scalatest::3.2.19 //> using dep org.typelevel::cats-effect-testing-scalatest::1.6.0 diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index 35479bd..20acd19 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -4,7 +4,6 @@ import scala.collection.mutable import cats.data.Chain import parsley.{Failure, Success} import java.io.File -import java.io.PrintStream import cats.implicits.* import cats.effect.IO @@ -14,6 +13,9 @@ import com.monovore.decline._ import com.monovore.decline.effect._ import com.monovore.decline.Argument +import org.typelevel.log4cats.slf4j.Slf4jLogger +import org.typelevel.log4cats.Logger + import assemblyIR as asm given Argument[File] = Argument.from("file") { str => @@ -32,30 +34,33 @@ val cliCommand: Command[File] = Opts.argument[File]("file") } +given logger: Logger[IO] = Slf4jLogger.getLogger[IO] + def frontend( contents: String -)(using stdout: PrintStream): IO[Either[Int, microWacc.Program]] = { - IO(parser.parse(contents)).map { +): IO[Either[Int, microWacc.Program]] = { + IO(parser.parse(contents)).flatMap { case Failure(msg) => - stdout.println(msg) - Left(100) // Syntax error + logger.error(s"Syntax error: $msg").as(Left(100)) case Success(prog) => given errors: mutable.Builder[Error, List[Error]] = List.newBuilder - given errorContent: String = contents val (names, funcs) = renamer.rename(prog) given ctx: typeChecker.TypeCheckerCtx = typeChecker.TypeCheckerCtx(names, funcs, errors) val typedProg = typeChecker.check(prog) - if (errors.result.isEmpty) Right(typedProg) + if (errors.result.isEmpty) IO.pure(Right(typedProg)) else { - errors.result.foreach(printError) - Left(errors.result.view.map { + val exitCode = errors.result.view.map { case _: Error.InternalError => 201 case _ => 200 - }.max) + }.max + + logger.error(s"Semantic errors:\n${errors.result.mkString("\n")}") *> IO.pure( + Left(exitCode) + ) } } } @@ -64,21 +69,18 @@ val s = "enter an integer to echo" def backend(typedProg: microWacc.Program): Chain[asm.AsmLine] = asmGenerator.generateAsm(typedProg) -def compile(filename: String, outFile: Option[File] = None)(using - stdout: PrintStream = Console.out -): IO[Int] = +def compile(filename: String, outFile: Option[File] = None): IO[Int] = for { contents <- IO(os.read(os.Path(filename))) + _ <- logger.info(s"Compiling file: $filename") result <- frontend(contents) exitCode <- result.fold( - IO.pure, // Return error code (handles Left case) + code => logger.error(s"Compilation failed for $filename\nExit code: $code").as(code), typedProg => - IO { - writer.writeTo( - backend(typedProg), - PrintStream(outFile.getOrElse(File(filename.stripSuffix(".wacc") + ".s"))) - ) - }.as(0) // Compilation succeeded + val outputFile = outFile.getOrElse(File(filename.stripSuffix(".wacc") + ".s")) + writer.writeTo(backend(typedProg), outputFile) *> logger + .info(s"Compilation succeeded: $filename") + .as(0) ) } yield exitCode diff --git a/src/main/wacc/backend/writer.scala b/src/main/wacc/backend/writer.scala index 3c8dcfd..b87ec01 100644 --- a/src/main/wacc/backend/writer.scala +++ b/src/main/wacc/backend/writer.scala @@ -1,12 +1,27 @@ package wacc -import java.io.PrintStream +import cats.effect.Resource +import java.nio.charset.StandardCharsets +import java.io.File +import java.io.BufferedWriter +import java.io.FileWriter import cats.data.Chain +import cats.effect.IO + +import org.typelevel.log4cats.Logger object writer { import assemblyIR._ - def writeTo(asmList: Chain[AsmLine], printStream: PrintStream): Unit = { - asmList.iterator.foreach(printStream.println) - } + def writeTo(asmList: Chain[AsmLine], outputFile: File)(using logger: Logger[IO]): IO[Unit] = + Resource + .fromAutoCloseable { + IO(BufferedWriter(FileWriter(outputFile, StandardCharsets.UTF_8))) + } + .use { writer => + IO { + asmList.iterator.foreach(line => writer.write(line.toString + "\n")) + writer.flush() // TODO: NECESSARY OR NOT? + } *> logger.info(s"Wrote assembly to ${outputFile.getAbsolutePath}") + } } diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index cd79161..76e84ed 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -7,7 +7,6 @@ import org.scalatest.freespec.AsyncFreeSpec import cats.effect.testing.scalatest.AsyncIOSpec import java.io.File import sys.process._ -import java.io.PrintStream import scala.io.Source import cats.effect.IO import wacc.{compile as compileWacc} @@ -30,7 +29,6 @@ class ParallelExamplesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAnd forEvery(files) { (filename, expectedResult) => val baseFilename = filename.stripSuffix(".wacc") - given stdout: PrintStream = PrintStream(File(baseFilename + ".out")) s"$filename" - { "should be compiled with correct result" in {