refactor: introduce decline to integrate command-line parsing with cats-effect

This commit is contained in:
Jonny
2025-02-28 18:00:18 +00:00
parent 1a72decf55
commit d56be9249a
2 changed files with 35 additions and 31 deletions

View File

@@ -9,7 +9,6 @@
//> using dep org.typelevel::cats-effect::3.5.7 //> using dep org.typelevel::cats-effect::3.5.7
//> using dep com.monovore::decline::2.5.0 //> using dep com.monovore::decline::2.5.0
//> using dep com.monovore::decline-effect::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 test.dep org.scalatest::scalatest::3.2.19
//> using dep org.typelevel::cats-effect-testing-scalatest::1.6.0 //> using dep org.typelevel::cats-effect-testing-scalatest::1.6.0

View File

@@ -3,38 +3,38 @@ package wacc
import scala.collection.mutable import scala.collection.mutable
import cats.data.Chain import cats.data.Chain
import parsley.{Failure, Success} import parsley.{Failure, Success}
import scopt.OParser
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import cats.implicits.* import cats.implicits.*
import assemblyIR as asm
import cats.effect.IO 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( case class CliConfig(
file: File = new File(".") file: File = new File(".")
) )
val cliBuilder = OParser.builder[CliConfig] given Argument[File] = Argument.from("file") { str =>
val cliParser = { val file = File(str)
import cliBuilder._ (
OParser.sequence( Option.when(file.exists())(file).toValidNel(s"File '${file.getAbsolutePath}' does not exist"),
programName("wacc-compiler"), Option
help('h', "help") .when(file.isFile())(file)
.text("Prints this help message"), .toValidNel(s"File '${file.getAbsolutePath}' must be a regular file"),
arg[File]("<file>") Option.when(file.getName.endsWith(".wacc"))(file).toValidNel("File must have .wacc extension")
.text("Input WACC source file") ).mapN((_, _, _) => file)
.required() }
.action((f, c) => c.copy(file = f))
.validate(f => val cliCommand: Command[File] =
if (!f.exists) failure("File does not exist") Command("wacc-compiler", "Compile WACC programs") {
else if (!f.isFile) failure("File must be a regular file") Opts.argument[File]("file")
else if (!f.getName.endsWith(".wacc"))
failure("File must have .wacc extension")
else success
)
)
} }
def frontend( def frontend(
@@ -87,13 +87,18 @@ def compile(filename: String, outFile: Option[File] = None)(using
) )
} yield exitCode } yield exitCode
object Main extends IOApp.Simple { object Main
override def run: IO[Unit] = extends CommandIOApp(
OParser.parse(cliParser, sys.env.getOrElse("WACC_ARGS", "").split(" "), CliConfig()).traverse_ { name = "wacc-compiler",
config => header = "the ultimate wacc compiler",
version = "1.0"
) {
def main: Opts[IO[ExitCode]] =
Opts.argument[File]("file").map { file =>
compile( compile(
config.file.getAbsolutePath, file.getAbsolutePath,
outFile = Some(File(".", config.file.getName.stripSuffix(".wacc") + ".s")) 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
} }
} }