feat: add option flag, greedy compilation of multiple files, and refactor to... #41
@@ -19,15 +19,17 @@ import org.typelevel.log4cats.Logger
|
|||||||
|
|
||||||
import assemblyIR as asm
|
import assemblyIR as asm
|
||||||
|
|
||||||
|
|
||||||
// TODO: IO correctness, --greedy, parallelisable, and probably splitting this file up
|
// TODO: IO correctness, --greedy, parallelisable, and probably splitting this file up
|
||||||
|
|
||||||
|
|
||||||
given Argument[Path] = Argument.from("path") { str =>
|
given Argument[Path] = Argument.from("path") { str =>
|
||||||
val path = Path.of(str)
|
val path = Path.of(str)
|
||||||
(
|
(
|
||||||
Either.cond(Files.exists(path), path, s"File '${path.toAbsolutePath}' does not exist"),
|
Either.cond(Files.exists(path), path, s"File '${path.toAbsolutePath}' does not exist"),
|
||||||
Either.cond(Files.isRegularFile(path), path, s"File '${path.toAbsolutePath}' must be a regular file"),
|
Either.cond(
|
||||||
|
Files.isRegularFile(path),
|
||||||
|
path,
|
||||||
|
s"File '${path.toAbsolutePath}' must be a regular file"
|
||||||
|
),
|
||||||
Either.cond(path.toString.endsWith(".wacc"), path, "File must have .wacc extension")
|
Either.cond(path.toString.endsWith(".wacc"), path, "File must have .wacc extension")
|
||||||
).mapN((_, _, _) => path).toValidatedNel
|
).mapN((_, _, _) => path).toValidatedNel
|
||||||
}
|
}
|
||||||
@@ -42,7 +44,6 @@ val outputOpt: Opts[Option[Path]] =
|
|||||||
|
|
||||||
val filesOpt: Opts[NonEmptyList[Path]] = Opts.arguments[Path]("files")
|
val filesOpt: Opts[NonEmptyList[Path]] = Opts.arguments[Path]("files")
|
||||||
|
|
||||||
|
|
||||||
def frontend(
|
def frontend(
|
||||||
contents: String
|
contents: String
|
||||||
): IO[Either[Int, microWacc.Program]] = {
|
): IO[Either[Int, microWacc.Program]] = {
|
||||||
@@ -64,9 +65,11 @@ def frontend(
|
|||||||
else {
|
else {
|
||||||
// TODO: multiple traversal of error content, should be a foldleft or co
|
// TODO: multiple traversal of error content, should be a foldleft or co
|
||||||
given errorContent: String = contents
|
given errorContent: String = contents
|
||||||
val exitCode = errResult.collectFirst {
|
val exitCode = errResult
|
||||||
case _: Error.InternalError => 201
|
.collectFirst { case _: Error.InternalError =>
|
||||||
}.getOrElse(200)
|
201
|
||||||
|
}
|
||||||
|
.getOrElse(200)
|
||||||
|
|
||||||
val formattedErrors = errResult.map(formatError).mkString("\n")
|
val formattedErrors = errResult.map(formatError).mkString("\n")
|
||||||
|
|
||||||
@@ -85,35 +88,39 @@ def compile(filename: String, outputDir: Option[Path], log: Boolean): IO[Int] =
|
|||||||
if (log) logger.info(_)
|
if (log) logger.info(_)
|
||||||
else (_ => IO.unit)
|
else (_ => IO.unit)
|
||||||
for {
|
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?
|
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")
|
_ <- logAction(s"Compiling file: $filename")
|
||||||
result <- frontend(contents)
|
result <- frontend(contents)
|
||||||
exitCode <- result.fold(
|
exitCode <- result.fold(
|
||||||
code => logger.error(s"Compilation failed for $filename\nExit code: $code").as(code),
|
code => logger.error(s"Compilation failed for $filename\nExit code: $code").as(code),
|
||||||
typedProg =>
|
typedProg =>
|
||||||
val outDir = outputDir.getOrElse(Paths.get(filename).getParent)
|
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?
|
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")
|
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
|
writer.writeTo(
|
||||||
logAction(s"Compilation succeeded: $filename").as(0)
|
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{
|
(filesOpt, logOpt, outputOpt).mapN { (files, log, outDir) =>
|
||||||
(files, log, outDir) =>
|
files
|
||||||
files
|
.traverse { file =>
|
||||||
.traverse{ file =>
|
compile(file.toAbsolutePath.toString, outDir, log)
|
||||||
compile(
|
}
|
||||||
file.toAbsolutePath.toString,
|
.map { exitCodes =>
|
||||||
outDir,
|
if (exitCodes.exists(_ != 0))
|
||||||
log)
|
ExitCode.Error // TODO- it should be the first one to exit when parallelised :)
|
||||||
}.map {
|
else ExitCode.Success
|
||||||
exitCodes =>
|
}
|
||||||
if (exitCodes.exists(_ != 0)) ExitCode.Error // TODO- it should be the first one to exit when parallelised :)
|
|
||||||
else ExitCode.Success
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,4 +134,4 @@ object Main
|
|||||||
def main: Opts[IO[ExitCode]] =
|
def main: Opts[IO[ExitCode]] =
|
||||||
compileCommand
|
compileCommand
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user