refactor: compile function split up into smaller functions
This commit is contained in:
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user