|
|
|
|
@@ -1,10 +1,11 @@
|
|
|
|
|
package wacc
|
|
|
|
|
|
|
|
|
|
import scala.collection.mutable
|
|
|
|
|
import cats.data.Chain
|
|
|
|
|
import cats.data.{Chain, NonEmptyList}
|
|
|
|
|
import parsley.{Failure, Success}
|
|
|
|
|
import java.io.File
|
|
|
|
|
import cats.implicits.*
|
|
|
|
|
|
|
|
|
|
import java.nio.file.{Files, Path, Paths}
|
|
|
|
|
import cats.syntax.all._
|
|
|
|
|
|
|
|
|
|
import cats.effect.IO
|
|
|
|
|
import cats.effect.ExitCode
|
|
|
|
|
@@ -18,24 +19,30 @@ import org.typelevel.log4cats.Logger
|
|
|
|
|
|
|
|
|
|
import assemblyIR as asm
|
|
|
|
|
|
|
|
|
|
given Argument[File] = Argument.from("file") { str =>
|
|
|
|
|
val file = File(str)
|
|
|
|
|
(
|
|
|
|
|
Option.when(file.exists())(file).toValidNel(s"File '${file.getAbsolutePath}' does not exist"),
|
|
|
|
|
Option
|
|
|
|
|
.when(file.isFile())(file)
|
|
|
|
|
.toValidNel(s"File '${file.getAbsolutePath}' must be a regular file"),
|
|
|
|
|
Option.when(file.getName.endsWith(".wacc"))(file).toValidNel("File must have .wacc extension")
|
|
|
|
|
).mapN((_, _, _) => file)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val cliCommand: Command[File] =
|
|
|
|
|
Command("wacc-compiler", "Compile WACC programs") {
|
|
|
|
|
Opts.argument[File]("file")
|
|
|
|
|
// TODO: IO correctness, --greedy, parallelisable, and probably splitting this file up
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
given Argument[Path] = Argument.from("path") { str =>
|
|
|
|
|
val path = Path.of(str)
|
|
|
|
|
(
|
|
|
|
|
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(path.toString.endsWith(".wacc"), path, "File must have .wacc extension")
|
|
|
|
|
).mapN((_, _, _) => path).toValidatedNel
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
given logger: Logger[IO] = Slf4jLogger.getLogger[IO]
|
|
|
|
|
|
|
|
|
|
val logOpt: Opts[Boolean] =
|
|
|
|
|
Opts.flag("log", "Enable logging for additional compilation details", short = "l").orFalse
|
|
|
|
|
|
|
|
|
|
val outputOpt: Opts[Option[Path]] =
|
|
|
|
|
Opts.option[Path]("output", "Specify path for output assembly file(s)").orNone
|
|
|
|
|
|
|
|
|
|
val filesOpt: Opts[NonEmptyList[Path]] = Opts.arguments[Path]("files")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def frontend(
|
|
|
|
|
contents: String
|
|
|
|
|
): IO[Either[Int, microWacc.Program]] = {
|
|
|
|
|
@@ -51,55 +58,73 @@ def frontend(
|
|
|
|
|
|
|
|
|
|
val typedProg = typeChecker.check(prog)
|
|
|
|
|
|
|
|
|
|
if (errors.result.isEmpty) IO.pure(Right(typedProg))
|
|
|
|
|
val errResult = errors.result
|
|
|
|
|
|
|
|
|
|
if (errResult.isEmpty) IO.pure(Right(typedProg))
|
|
|
|
|
else {
|
|
|
|
|
// TODO: multiple traversal of error content, should be a foldleft or co
|
|
|
|
|
given errorContent: String = contents
|
|
|
|
|
val exitCode = errors.result.view.map {
|
|
|
|
|
val exitCode = errResult.collectFirst {
|
|
|
|
|
case _: Error.InternalError => 201
|
|
|
|
|
case _ => 200
|
|
|
|
|
}.max
|
|
|
|
|
}.getOrElse(200)
|
|
|
|
|
|
|
|
|
|
val formattedErrors = errors.result.map(formatError).mkString("\n")
|
|
|
|
|
val formattedErrors = errResult.map(formatError).mkString("\n")
|
|
|
|
|
|
|
|
|
|
logger.error(s"Semantic errors:\n$formattedErrors") *> IO.pure(Left(exitCode))
|
|
|
|
|
logger.error(s"Semantic errors:\n$formattedErrors").as(Left(exitCode))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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): IO[Int] =
|
|
|
|
|
// 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] =
|
|
|
|
|
val logAction: String => IO[Unit] =
|
|
|
|
|
if (log) logger.info(_)
|
|
|
|
|
else (_ => IO.unit)
|
|
|
|
|
for {
|
|
|
|
|
contents <- IO(os.read(os.Path(filename)))
|
|
|
|
|
_ <- logger.info(s"Compiling file: $filename")
|
|
|
|
|
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 outputFile = outFile.getOrElse(File(filename.stripSuffix(".wacc") + ".s"))
|
|
|
|
|
writer.writeTo(backend(typedProg), outputFile) *> logger
|
|
|
|
|
.info(s"Compilation succeeded: $filename")
|
|
|
|
|
.as(0)
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}.map {
|
|
|
|
|
exitCodes =>
|
|
|
|
|
if (exitCodes.exists(_ != 0)) ExitCode.Error // TODO- it should be the first one to exit when parallelised :)
|
|
|
|
|
else ExitCode.Success
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: add parallelisable option
|
|
|
|
|
object Main
|
|
|
|
|
extends CommandIOApp(
|
|
|
|
|
name = "wacc-compiler",
|
|
|
|
|
header = "the ultimate wacc compiler",
|
|
|
|
|
name = "wacc",
|
|
|
|
|
header = "The ultimate WACC compiler",
|
|
|
|
|
version = "1.0"
|
|
|
|
|
) {
|
|
|
|
|
def main: Opts[IO[ExitCode]] =
|
|
|
|
|
Opts.arguments[File]("files").map { files =>
|
|
|
|
|
files
|
|
|
|
|
.parTraverse_ { file =>
|
|
|
|
|
compile(
|
|
|
|
|
file.getAbsolutePath,
|
|
|
|
|
outFile = Some(File(".", file.getName.stripSuffix(".wacc") + ".s"))
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
.as(ExitCode.Success)
|
|
|
|
|
}
|
|
|
|
|
compileCommand
|
|
|
|
|
|
|
|
|
|
}
|