Skip to content

Commit

Permalink
Merge more logic into MainModule (#1331)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnynek authored Dec 22, 2024
1 parent 263f1cd commit 43aa0e8
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 165 deletions.
12 changes: 11 additions & 1 deletion cli/src/main/scala/org/bykn/bosatsu/IOPlatformIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.bykn.bosatsu

import _root_.bosatsu.{TypedAst => proto}
import cats.MonadError
import cats.effect.IO
import cats.effect.{IO, Resource}
import com.monovore.decline.Argument
import java.nio.file.{Path => JPath}
import java.io.{
Expand All @@ -29,6 +29,13 @@ object IOPlatformIO extends PlatformIO[IO, JPath] {
override def moduleIOMonad: MonadError[IO, Throwable] =
cats.effect.IO.asyncForIO

private val parResource: Resource[IO, Par.EC] =
Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es)))
.map(Par.ecFromService(_))

def withEC[A](fn: Par.EC => IO[A]): IO[A] =
parResource.use(fn)

def readUtf8(path: Path): IO[String] =
IO.blocking(new String(java.nio.file.Files.readAllBytes(path), "utf-8"))

Expand Down Expand Up @@ -137,6 +144,9 @@ object IOPlatformIO extends PlatformIO[IO, JPath] {
def println(str: String): IO[Unit] =
IO.println(str)

def errorln(str: String): IO[Unit] =
IO.consoleForIO.errorln(str)

def writeStdout(doc: Doc): IO[Unit] =
IO.blocking {
doc
Expand Down
11 changes: 8 additions & 3 deletions cli/src/main/scala/org/bykn/bosatsu/Main.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package org.bykn.bosatsu

import org.bykn.bosatsu.tool
import cats.effect.{ExitCode, IO, IOApp}

object Main extends IOApp {
def fromToolExit(ec: tool.ExitCode): ExitCode =
ec match {
case tool.ExitCode.Success => ExitCode.Success
case tool.ExitCode.Error => ExitCode.Error
}
def run(args: List[String]): IO[ExitCode] =
PathModule.run(args) match {
case Right(getOutput) =>
PathModule.report(getOutput)
PathModule.runAndReport(args) match {
case Right(io) => io.map(fromToolExit)
case Left(help) =>
IO.blocking {
System.err.println(help.toString)
Expand Down
39 changes: 3 additions & 36 deletions cli/src/main/scala/org/bykn/bosatsu/PathModule.scala
Original file line number Diff line number Diff line change
@@ -1,39 +1,6 @@
package org.bykn.bosatsu

import cats.effect.ExitCode
import cats.effect.{IO, Resource}
import java.nio.file.{Path => JPath}
import org.bykn.bosatsu.tool.Output
import cats.effect.IO
import java.nio.file.Path

object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self =>
type Path = JPath

val parResource: Resource[IO, Par.EC] =
Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es)))
.map(Par.ecFromService(_))

def withEC[A](fn: Par.EC => IO[A]): IO[A] =
parResource.use(fn)

def fromToolExit(ec: tool.ExitCode): ExitCode =
ec match {
case tool.ExitCode.Success => ExitCode.Success
case tool.ExitCode.Error => ExitCode.Error
}

def report(io: IO[Output[JPath]]): IO[ExitCode] =
io.attempt.flatMap {
case Right(out) => reportOutput(out).map(fromToolExit)
case Left(err) => reportException(err).as(ExitCode.Error)
}

def reportException(ex: Throwable): IO[Unit] =
mainExceptionToString(ex) match {
case Some(msg) =>
IO.consoleForIO.errorln(msg)
case None =>
IO.consoleForIO.errorln("unknown error:\n") *>
IO.blocking(ex.printStackTrace(System.err))
}

}
object PathModule extends MainModule[IO, Path](IOPlatformIO)
30 changes: 11 additions & 19 deletions cliJS/src/main/scala/org/bykn/bosatsu/Fs2PlatformIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.bykn.bosatsu
import _root_.bosatsu.{TypedAst => proto}
import cats.MonadError
import cats.data.Validated
import cats.effect.IO
import cats.effect.{IO, Resource}
import fs2.io.file.{Files, Path}
import com.monovore.decline.Argument
import org.typelevel.paiges.Doc
Expand Down Expand Up @@ -32,6 +32,13 @@ object Fs2PlatformIO extends PlatformIO[IO, Path] {

private val FilesIO = Files.forIO

private val parResource: Resource[IO, Par.EC] =
Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es)))
.map(Par.ecFromService(_))

def withEC[A](fn: Par.EC => IO[A]): IO[A] =
parResource.use(fn)

def readUtf8(p: Path): IO[String] =
FilesIO.readUtf8(p).compile.string

Expand Down Expand Up @@ -80,24 +87,6 @@ object Fs2PlatformIO extends PlatformIO[IO, Path] {

def resolve(p: Path, c: String): Path = p.resolve(c)

/** Modules optionally have the capability to combine paths into a tree
*/
val resolvePath: Option[(Path, PackageName) => IO[Option[Path]]] = Some {
(root: Path, pack: PackageName) => {
val dir = pack.parts.init.foldLeft(root)(_.resolve(_))
val filePath = dir.resolve(pack.parts.last + ".bosatsu")
FilesIO.exists(filePath, true)
.map {
case true => Some(filePath)
case false => None
}
}
}

/** some modules have paths that form directory trees
*
* if the given path is a directory, return Some and all the first children.
*/
def unfoldDir(path: Path): IO[Option[IO[List[Path]]]] =
FilesIO.isDirectory(path, followLinks = true)
.map {
Expand Down Expand Up @@ -128,6 +117,9 @@ object Fs2PlatformIO extends PlatformIO[IO, Path] {
def println(str: String): IO[Unit] =
IO.println(str)

def errorln(str: String): IO[Unit] =
IO.consoleForIO.errorln(str)

def writeInterfaces(
interfaces: List[Package.Interface],
path: Path
Expand Down
16 changes: 10 additions & 6 deletions cliJS/src/main/scala/org/bykn/bosatsu/tool/Fs2Main.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package org.bykn.bosatsu.tool

import cats.effect.{ExitCode, IO, IOApp}
import cats.effect.{ExitCode => ceExitCode, IO, IOApp}

object Fs2Main extends IOApp {
def run(args: List[String]): IO[ExitCode] =
Fs2Module.run(args) match {
case Right(getOutput) =>
Fs2Module.report(getOutput)
def fromToolExit(ec: ExitCode): ceExitCode =
ec match {
case ExitCode.Success => ceExitCode.Success
case ExitCode.Error => ceExitCode.Error
}
def run(args: List[String]): IO[ceExitCode] =
Fs2Module.runAndReport(args) match {
case Right(io) => io.map(fromToolExit)
case Left(help) =>
IO.blocking {
System.err.println(help.toString)
ExitCode.Error
ceExitCode.Error
}
}
}
36 changes: 3 additions & 33 deletions cliJS/src/main/scala/org/bykn/bosatsu/tool/Fs2Module.scala
Original file line number Diff line number Diff line change
@@ -1,37 +1,7 @@
package org.bykn.bosatsu.tool

import cats.{effect => ce}
import cats.effect.{IO, Resource}
import cats.effect.IO
import fs2.io.file.Path
import org.bykn.bosatsu.{Par, MainModule, Fs2PlatformIO}
import org.bykn.bosatsu.{MainModule, Fs2PlatformIO}

object Fs2Module extends MainModule[IO, Path](Fs2PlatformIO) { self =>
val parResource: Resource[IO, Par.EC] =
Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es)))
.map(Par.ecFromService(_))

def withEC[A](fn: Par.EC => IO[A]): IO[A] =
parResource.use(fn)

def fromToolExit(ec: ExitCode): ce.ExitCode =
ec match {
case ExitCode.Success => ce.ExitCode.Success
case ExitCode.Error => ce.ExitCode.Error
}

def report(io: IO[Output[Path]]): IO[ce.ExitCode] =
io.attempt.flatMap {
case Right(out) => reportOutput(out).map(fromToolExit)
case Left(err) => reportException(err).as(ce.ExitCode.Error)
}

def reportException(ex: Throwable): IO[Unit] =
mainExceptionToString(ex) match {
case Some(msg) =>
IO.consoleForIO.errorln(msg)
case None =>
IO.consoleForIO.errorln("unknown error:\n") *>
IO.blocking(ex.printStackTrace(System.err))
}

}
object Fs2Module extends MainModule[IO, Path](Fs2PlatformIO)
80 changes: 30 additions & 50 deletions core/src/main/scala/org/bykn/bosatsu/MainModule.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.bykn.bosatsu

import cats.data.{Chain, Validated, ValidatedNel, NonEmptyList}
import cats.data.{Validated, ValidatedNel, NonEmptyList}
import cats.implicits.catsKernelOrderingForOrder
import cats.Traverse
import com.monovore.decline.{Argument, Command, Help, Opts}
Expand All @@ -23,13 +23,11 @@ import cats.syntax.all._
* is to allow it to be testable and usable in scalajs where we don't have
* file-IO
*/
abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
type F[A] = IO[A]

import platformIO._

def withEC[A](fn: Par.EC => IO[A]): IO[A]

//////////////////////////////
// Below here are concrete and should not use override
//////////////////////////////
Expand All @@ -39,6 +37,8 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
.parse(args.toList)
.map(_.run.widen)

final def runAndReport(args: List[String]): Either[Help, IO[ExitCode]] =
run(args).map(report)

sealed abstract class MainException extends Exception {
def command: MainCommand
Expand Down Expand Up @@ -110,11 +110,6 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
}

object MainCommand {
type ParseTransResult = ValidatedNel[
PathParseError[Path],
(Chain[((Path, LocationMap), Package.Parsed)], Set[PackageName])
]

/** like typecheck, but a no-op for empty lists
*/
def typeCheck0(
Expand Down Expand Up @@ -427,52 +422,15 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
} yield pn
}

private def pathGen(
arg: String,
help: String,
ext: String
): Opts[PathGen] = {
val direct = Opts
.options[Path](arg, help = help)
.orEmpty
.map(paths => paths.foldMap(PathGen.Direct[IO, Path](_): PathGen))

val select = hasExtension(ext)
val child1 = Opts
.options[Path](arg + "_dir", help = s"all $help in directory")
.orEmpty
.map { paths =>
paths.foldMap(
PathGen
.ChildrenOfDir[IO, Path](_, select, false, unfoldDir(_)): PathGen
)
}
val childMany = Opts
.options[Path](
arg + "_all_subdir",
help = s"all $help recursively in all directories"
)
.orEmpty
.map { paths =>
paths.foldMap(
PathGen
.ChildrenOfDir[IO, Path](_, select, true, unfoldDir(_)): PathGen
)
}

(direct, child1, childMany).mapN { (a, b, c) =>
(a :: b :: c :: Nil).combineAll
}
}
private val srcs =
pathGen("input", help = "input source files", ".bosatsu")
PathGen.opts("input", help = "input source files", ".bosatsu")(platformIO)
private val ifaces =
pathGen("interface", help = "interface files", ".bosatsig")
private val includes = pathGen(
PathGen.opts("interface", help = "interface files", ".bosatsig")(platformIO)
private val includes = PathGen.opts(
"include",
help = "compiled packages to include files",
".bosatsu_package"
)
)(platformIO)

private val packRes = PackageResolver.opts(platformIO)
private val noSearchRes = PackageResolver.noSearchOpts(platformIO)
Expand Down Expand Up @@ -1237,4 +1195,26 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) {
writeOut(fullDoc, output).as(ExitCode.Success)
}
}

private def stackTraceToString(t: Throwable): String = {
val stringWriter = new java.io.StringWriter()
val printWriter = new java.io.PrintWriter(stringWriter)
t.printStackTrace(printWriter)
stringWriter.toString
}

def reportException(ex: Throwable): IO[Unit] =
mainExceptionToString(ex) match {
case Some(msg) =>
platformIO.errorln(msg)
case None =>
platformIO.errorln("unknown error:\n") *>
platformIO.errorln(stackTraceToString(ex))
}

def report(io: IO[Output[Path]]): IO[ExitCode] =
io.attempt.flatMap {
case Right(out) => reportOutput(out)
case Left(err) => reportException(err).as(ExitCode.Error)
}
}
Loading

0 comments on commit 43aa0e8

Please sign in to comment.