From f66f1ab3ac13864addc7fddbc3b6f165adee758e Mon Sep 17 00:00:00 2001 From: Jonny Date: Sun, 2 Mar 2025 03:48:37 +0000 Subject: [PATCH] refactor: compile function split up into smaller functions --- src/main/wacc/Main.scala | 81 ++++++++++++++++++++++-------- src/main/wacc/backend/writer.scala | 4 +- src/test/wacc/examples.scala | 3 +- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index 7afdd4f..a47c34c 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -4,7 +4,7 @@ import scala.collection.mutable import cats.data.{Chain, NonEmptyList} import parsley.{Failure, Success} -import java.nio.file.{Files, Path, Paths} +import java.nio.file.{Files, Path} import cats.syntax.all._ import cats.effect.IO @@ -83,38 +83,75 @@ def backend(typedProg: microWacc.Program): Chain[asm.AsmLine] = // TODO: filename being String seems unnatural due to Path refactor // TODO: this function is doing too much should refactor -def compile(filename: String, outputDir: Option[Path], log: Boolean): IO[Int] = +// def compile(filename: String, outputDir: Option[Path], log: Boolean): IO[Int] = +// val logAction: String => IO[Unit] = +// if (log) logger.info(_) +// else (_ => IO.unit) +// for { +// contents <- IO.delay( +// os.read(os.Path(filename)) +// ) // TODO: Is IO as a wrapper ok or do we require .delay - also, should it be .blocking? +// _ <- logAction(s"Compiling file: $filename") +// result <- frontend(contents) +// exitCode <- result.fold( +// code => logger.error(s"Compilation failed for $filename\nExit code: $code").as(code), +// typedProg => +// val outDir = outputDir.getOrElse(Paths.get(filename).getParent) +// IO.delay( +// Files.createDirectories(outDir) +// ) // TODO: Is IO as a wrapper ok or do we require .delay - also, should it be .blocking? +// val outputFile = outDir.resolve(filename.stripSuffix(".wacc") + ".s") +// writer.writeTo( +// backend(typedProg), +// outputFile +// ) *> // TODO: I dont think we need IO here if we look at the implementation of writer +// logAction(s"Compilation succeeded: $filename").as(0) +// ) +// } yield exitCode + +def compile(filePath: Path, outputDir: Option[Path], log: Boolean): IO[Int] = { val logAction: String => IO[Unit] = if (log) logger.info(_) else (_ => IO.unit) + + def readSourceFile: IO[String] = + IO.blocking(os.read(os.Path(filePath))) + + def ensureOutputDir(outDir: Path): IO[Path] = + IO.blocking { + Files.createDirectories(outDir) + outDir + } + + // TODO: path, file , the names are confusing (when Path is the type but we are working with files) + def writeOutputFile(typedProg: microWacc.Program, outputPath: Path): IO[Unit] = + writer.writeTo(backend(typedProg), outputPath) *> + logger.info(s"Success: ${outputPath.toAbsolutePath}") + + def processProgram(contents: String, outDir: Path): IO[Int] = + frontend(contents).flatMap { + case Left(code) => + logger.error(s"Compilation failed for $filePath\nExit code: $code").as(code) + + case Right(typedProg) => + val outputFile = outDir.resolve(filePath.getFileName.toString.stripSuffix(".wacc") + ".s") + writeOutputFile(typedProg, outputFile).as(0) + } + for { - contents <- IO.delay( - os.read(os.Path(filename)) - ) // TODO: Is IO as a wrapper ok or do we require .delay - also, should it be .blocking? - _ <- logAction(s"Compiling file: $filename") - result <- frontend(contents) - exitCode <- result.fold( - code => logger.error(s"Compilation failed for $filename\nExit code: $code").as(code), - typedProg => - val outDir = outputDir.getOrElse(Paths.get(filename).getParent) - IO.delay( - Files.createDirectories(outDir) - ) // TODO: Is IO as a wrapper ok or do we require .delay - also, should it be .blocking? - val outputFile = outDir.resolve(filename.stripSuffix(".wacc") + ".s") - writer.writeTo( - backend(typedProg), - outputFile - ) *> // TODO: I dont think we need IO here if we look at the implementation of writer - logAction(s"Compilation succeeded: $filename").as(0) - ) + contents <- readSourceFile + _ <- logAction(s"Compiling file: ${filePath.toAbsolutePath}") + outDir <- ensureOutputDir(outputDir.getOrElse(filePath.getParent)) + exitCode <- processProgram(contents, outDir) } yield exitCode +} // TODO: this is sequential, thus should be what occurs when --greedy is passed in val compileCommand: Opts[IO[ExitCode]] = (filesOpt, logOpt, outputOpt).mapN { (files, log, outDir) => files .traverse { file => - compile(file.toAbsolutePath.toString, outDir, log) + compile(file.toAbsolutePath, outDir, log) } .map { exitCodes => if (exitCodes.exists(_ != 0)) diff --git a/src/main/wacc/backend/writer.scala b/src/main/wacc/backend/writer.scala index cae7110..a339f55 100644 --- a/src/main/wacc/backend/writer.scala +++ b/src/main/wacc/backend/writer.scala @@ -36,7 +36,7 @@ object writer { /** Main function to write assembly to a file */ def writeTo(asmList: Chain[AsmLine], outputPath: Path)(using logger: Logger[IO]): IO[Unit] = - bufferedWriter(outputPath).use { writer => - writeLines(writer, asmList) *> logger.info(s"Success: ${outputPath.toAbsolutePath}") + bufferedWriter(outputPath).use { + writeLines(_, asmList) } } diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index ca1fe9d..11093d6 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -6,6 +6,7 @@ import org.scalatest.matchers.should.Matchers._ import org.scalatest.freespec.AsyncFreeSpec import cats.effect.testing.scalatest.AsyncIOSpec import java.io.File +import java.nio.file.Path import sys.process._ import scala.io.Source import cats.effect.IO @@ -32,7 +33,7 @@ class ParallelExamplesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAnd s"$filename" - { "should be compiled with correct result" in { - compileWacc(filename, outputDir = None, log = false).map { result => + compileWacc(Path.of(filename), outputDir = None, log = false).map { result => expectedResult should contain(result) } }