From d56be9249a07893769f288484637fd3d65c75ae3 Mon Sep 17 00:00:00 2001 From: Jonny Date: Fri, 28 Feb 2025 18:00:18 +0000 Subject: [PATCH] refactor: introduce decline to integrate command-line parsing with cats-effect --- project.scala | 1 - src/main/wacc/Main.scala | 65 +++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/project.scala b/project.scala index 86e8a44..b6fb20a 100644 --- a/project.scala +++ b/project.scala @@ -9,7 +9,6 @@ //> using dep org.typelevel::cats-effect::3.5.7 //> using dep com.monovore::decline::2.5.0 //> using dep com.monovore::decline-effect::2.5.0 -//> using dep com.github.scopt::scopt::4.1.0 //> using test.dep org.scalatest::scalatest::3.2.19 //> using dep org.typelevel::cats-effect-testing-scalatest::1.6.0 diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index 4d3c101..ef4575c 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -3,40 +3,40 @@ package wacc import scala.collection.mutable import cats.data.Chain import parsley.{Failure, Success} -import scopt.OParser import java.io.File import java.io.PrintStream import cats.implicits.* -import assemblyIR as asm import cats.effect.IO -import cats.effect.IOApp +import cats.effect.ExitCode + +import com.monovore.decline._ +import com.monovore.decline.effect._ +import com.monovore.decline.Argument + +import assemblyIR as asm + case class CliConfig( file: File = new File(".") ) -val cliBuilder = OParser.builder[CliConfig] -val cliParser = { - import cliBuilder._ - OParser.sequence( - programName("wacc-compiler"), - help('h', "help") - .text("Prints this help message"), - arg[File]("") - .text("Input WACC source file") - .required() - .action((f, c) => c.copy(file = f)) - .validate(f => - if (!f.exists) failure("File does not exist") - else if (!f.isFile) failure("File must be a regular file") - else if (!f.getName.endsWith(".wacc")) - failure("File must have .wacc extension") - else success - ) - ) +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") + } + def frontend( contents: String )(using stdout: PrintStream): IO[Either[Int, microWacc.Program]] = { @@ -87,13 +87,18 @@ def compile(filename: String, outFile: Option[File] = None)(using ) } yield exitCode -object Main extends IOApp.Simple { - override def run: IO[Unit] = - OParser.parse(cliParser, sys.env.getOrElse("WACC_ARGS", "").split(" "), CliConfig()).traverse_ { - config => - compile( - config.file.getAbsolutePath, - outFile = Some(File(".", config.file.getName.stripSuffix(".wacc") + ".s")) - ) +object Main + extends CommandIOApp( + name = "wacc-compiler", + header = "the ultimate wacc compiler", + version = "1.0" + ) { + def main: Opts[IO[ExitCode]] = + Opts.argument[File]("file").map { file => + compile( + file.getAbsolutePath, + outFile = Some(File(".", file.getName.stripSuffix(".wacc") + ".s")) + ).map(ExitCode(_)) // turn the int into exit code for compatibility with commandioapp + // https://ben.kirw.in/decline/effect.html } }