refactor: compile function split up into smaller functions

This commit is contained in:
Jonny
2025-03-02 03:48:37 +00:00
parent abb43b560d
commit f66f1ab3ac
3 changed files with 63 additions and 25 deletions

View File

@@ -4,7 +4,7 @@ import scala.collection.mutable
import cats.data.{Chain, NonEmptyList} import cats.data.{Chain, NonEmptyList}
import parsley.{Failure, Success} import parsley.{Failure, Success}
import java.nio.file.{Files, Path, Paths} import java.nio.file.{Files, Path}
import cats.syntax.all._ import cats.syntax.all._
import cats.effect.IO 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: filename being String seems unnatural due to Path refactor
// TODO: this function is doing too much should 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] = val logAction: String => IO[Unit] =
if (log) logger.info(_) if (log) logger.info(_)
else (_ => IO.unit) 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 { for {
contents <- IO.delay( contents <- readSourceFile
os.read(os.Path(filename)) _ <- logAction(s"Compiling file: ${filePath.toAbsolutePath}")
) // TODO: Is IO as a wrapper ok or do we require .delay - also, should it be .blocking? outDir <- ensureOutputDir(outputDir.getOrElse(filePath.getParent))
_ <- logAction(s"Compiling file: $filename") exitCode <- processProgram(contents, outDir)
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 } yield exitCode
}
// TODO: this is sequential, thus should be what occurs when --greedy is passed in // TODO: this is sequential, thus should be what occurs when --greedy is passed in
val compileCommand: Opts[IO[ExitCode]] = val compileCommand: Opts[IO[ExitCode]] =
(filesOpt, logOpt, outputOpt).mapN { (files, log, outDir) => (filesOpt, logOpt, outputOpt).mapN { (files, log, outDir) =>
files files
.traverse { file => .traverse { file =>
compile(file.toAbsolutePath.toString, outDir, log) compile(file.toAbsolutePath, outDir, log)
} }
.map { exitCodes => .map { exitCodes =>
if (exitCodes.exists(_ != 0)) if (exitCodes.exists(_ != 0))

View File

@@ -36,7 +36,7 @@ object writer {
/** Main function to write assembly to a file */ /** Main function to write assembly to a file */
def writeTo(asmList: Chain[AsmLine], outputPath: Path)(using logger: Logger[IO]): IO[Unit] = def writeTo(asmList: Chain[AsmLine], outputPath: Path)(using logger: Logger[IO]): IO[Unit] =
bufferedWriter(outputPath).use { writer => bufferedWriter(outputPath).use {
writeLines(writer, asmList) *> logger.info(s"Success: ${outputPath.toAbsolutePath}") writeLines(_, asmList)
} }
} }

View File

@@ -6,6 +6,7 @@ import org.scalatest.matchers.should.Matchers._
import org.scalatest.freespec.AsyncFreeSpec import org.scalatest.freespec.AsyncFreeSpec
import cats.effect.testing.scalatest.AsyncIOSpec import cats.effect.testing.scalatest.AsyncIOSpec
import java.io.File import java.io.File
import java.nio.file.Path
import sys.process._ import sys.process._
import scala.io.Source import scala.io.Source
import cats.effect.IO import cats.effect.IO
@@ -32,7 +33,7 @@ class ParallelExamplesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAnd
s"$filename" - { s"$filename" - {
"should be compiled with correct result" in { "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) expectedResult should contain(result)
} }
} }