diff --git a/cli/src/main/scala/org/bykn/bosatsu/CodeGenWrite.scala b/cli/src/main/scala/org/bykn/bosatsu/CodeGenWrite.scala deleted file mode 100644 index 48e4e6127..000000000 --- a/cli/src/main/scala/org/bykn/bosatsu/CodeGenWrite.scala +++ /dev/null @@ -1,27 +0,0 @@ -package org.bykn.bosatsu - -import cats.data.NonEmptyList -import cats.effect.IO -import java.nio.file.Path -import java.io.PrintWriter -import org.typelevel.paiges.Doc - -object CodeGenWrite { - @annotation.tailrec - final def toPath(root: Path, pn: PackageName): Path = - pn.parts match { - case NonEmptyList(h, Nil) => root.resolve(h).resolve("Values.java") - case NonEmptyList(h0, h1 :: tail) => - toPath(root.resolve(h0), PackageName(NonEmptyList(h1, tail))) - } - - def writeDoc(p: Path, d: Doc): IO[Unit] = - IO.blocking { - Option(p.getParent).foreach(_.toFile.mkdirs) - val pw = new PrintWriter(p.toFile, "UTF-8") - try d.renderStream(100).foreach(pw.print(_)) - finally { - pw.close - } - } -} diff --git a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala index 7692b57a0..6d3516957 100644 --- a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala +++ b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala @@ -1,21 +1,13 @@ package org.bykn.bosatsu -import cats.data.NonEmptyList import cats.effect.ExitCode import cats.effect.{IO, Resource} import java.nio.file.{Path => JPath} -import org.typelevel.paiges.{Doc, Document} -import org.bykn.bosatsu.tool.{FileKind, GraphOutput, Output} - -import cats.implicits.catsKernelOrderingForOrder -import cats.syntax.all._ +import org.bykn.bosatsu.tool.Output object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self => type Path = JPath - def print(str: String): IO[Unit] = - IO.println(str) - val parResource: Resource[IO, Par.EC] = Resource.make(IO(Par.newService()))(es => IO(Par.shutdownService(es))) .map(Par.ecFromService(_)) @@ -23,228 +15,18 @@ object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self => 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) + case Right(out) => reportOutput(out).map(fromToolExit) case Left(err) => reportException(err).as(ExitCode.Error) } - private def writeOut(doc: Doc, out: Option[JPath]): IO[Unit] = - out match { - case None => - IO.blocking { - doc - .renderStreamTrim(80) - .iterator - .foreach(System.out.print) - - System.out.println("") - } - case Some(p) => - CodeGenWrite.writeDoc(p, doc) - } - - def writeInterfaces( - interfaces: List[Package.Interface], - path: JPath - ): IO[Unit] = - IOPlatformIO.writeInterfaces(interfaces, path) - - def writePackages[A](packages: List[Package.Typed[A]], path: JPath): IO[Unit] = - IOPlatformIO.writePackages(packages, path) - - def reportOutput(out: Output[JPath]): IO[ExitCode] = - out match { - case Output.TestOutput(resMap, color) => - val hasMissing = resMap.exists(_._2.isEmpty) - // it would be nice to run in parallel, but - // MatchlessToValue is not currently threadsafe - val evalTest = resMap.map { - case (p, Some(evalTest)) => - (p, Some(evalTest.value)) - case (p, None) => (p, None) - } - - val testReport = Test.outputFor(evalTest, color) - val success = !hasMissing && (testReport.fails == 0) - val code = if (success) ExitCode.Success else ExitCode.Error - print(testReport.doc.render(80)).as(code) - case Output.EvaluationResult(_, tpe, resDoc) => - val tDoc = rankn.Type.fullyResolvedDocument.document(tpe) - val doc = - resDoc.value + (Doc.lineOrEmpty + Doc.text(": ") + tDoc).nested(4) - print(doc.render(100)).as(ExitCode.Success) - case Output.JsonOutput(json, pathOpt) => - writeOut(json.toDoc, pathOpt) - .as(ExitCode.Success) - - case Output.TranspileOut(outs, base) => - def path(p: List[String]): JPath = - p.foldLeft(base)(_.resolve(_)) - - outs.toList - .map { case (p, d) => - (p, CodeGenWrite.writeDoc(path(p.toList), d)) - } - .sortBy(_._1) - .traverse_ { case (_, w) => w } - .as(ExitCode.Success) - - case Output.CompileOut(packList, ifout, output) => - val ifres = ifout match { - case None => - IO.unit - case Some(ifacePath) => - val ifs = packList.map(Package.interfaceOf(_)) - writeInterfaces(ifs, ifacePath) - } - val out = output.fold(IO.unit)(writePackages(packList, _)) - - (ifres *> out).as(ExitCode.Success) - - case Output.ShowOutput(packs, ifaces, output) => - val pdocs = packs.map { pack => - Document[Package.Typed[Any]].document(pack) - } - val idocs = ifaces.map { iface => - Document[Package.Interface].document(iface) - } - - val doc = Doc.intercalate(Doc.hardLine, idocs ::: pdocs) - writeOut(doc, output).as(ExitCode.Success) - - case Output.DepsOutput(depinfo, output, style) => - style match { - case GraphOutput.Json => - def toJson( - dep: (Path, PackageName, FileKind, List[PackageName]) - ): Json = - Json.JObject( - ("path", Json.JString(dep._1.toString())) :: - ("package", Json.JString(dep._2.asString)) :: - ("kind", Json.JString(dep._3.name)) :: - ( - "dependsOn", - Json.JArray( - dep._4.map(pn => Json.JString(pn.asString)).toVector - ) - ) :: - Nil - ) - - val asJson = Json.JArray( - depinfo - .sortBy { case (path, pn, fk, _) => (path, pn, fk.name) } - .map(toJson) - .toVector - ) - - writeOut(asJson.toDoc, output).as(ExitCode.Success) - case GraphOutput.Dot => - def shapeOf(fk: FileKind): String = - fk match { - case FileKind.Iface => "diamond" - case FileKind.Pack => "box" - case FileKind.Source => "circle" - } - - type Ident = String - def makeNode( - idx: Int, - dep: (Path, PackageName, FileKind, List[PackageName]) - ): (Ident, String) = { - // C [shape=box, fillcolor=lightgrey, label="Node C"]; - val ident = s"N$idx" - val decl = - s"$ident [shape=${shapeOf(dep._3)}, label=\"${dep._2.asString}\"];" - (ident, decl) - } - def makeMissing(idx: Int, pack: PackageName): (Ident, String) = { - // C [shape=box, fillcolor=lightgrey, label="Node C"]; - val ident = s"N$idx" - val decl = s"$ident [shape=octogon, label=\"${pack.asString}\"];" - (ident, decl) - } - - val knownPacks = depinfo.map(_._2).toSet - val allPacks = - depinfo.flatMap(dep => dep._2 :: dep._4).distinct.sorted - val unknownPacks = allPacks.filterNot(knownPacks) - type NodeMap = Map[PackageName, NonEmptyList[ - (Int, Option[FileKind], String, String) - ]] - val depinfoSize = depinfo.size - val nodes: NodeMap = - (depinfo.zipWithIndex.map { case (dep, idx) => - val (ident, nstr) = makeNode(idx, dep) - (dep._2, (idx, Some(dep._3), ident, nstr)) - } ::: unknownPacks.mapWithIndex { (pn, idx0) => - val idx = depinfoSize + idx0 - val (ident, nstr) = makeMissing(idx, pn) - (pn, (idx, None, ident, nstr)) - }) - .groupByNel(_._1) - .map { case (k, v) => - (k, v.map(_._2)) - } - - // now NodeMap has everything - def makeEdge( - src: PackageName, - k: FileKind, - dst: PackageName, - nm: NodeMap - ): String = { - implicit val orderKind: cats.Order[Option[FileKind]] = - new cats.Order[Option[FileKind]] { - def compare(a: Option[FileKind], b: Option[FileKind]) = - (a, b) match { - case (None, None) => 0 - case (Some(_), None) => -1 - case (None, Some(_)) => 1 - case (Some(FileKind.Iface), Some(FileKind.Iface)) => 0 - case (Some(FileKind.Iface), Some(_)) => -1 - case (Some(FileKind.Pack), Some(FileKind.Iface)) => 1 - case (Some(FileKind.Pack), Some(FileKind.Pack)) => 0 - case (Some(FileKind.Pack), Some(FileKind.Source)) => -1 - case (Some(FileKind.Source), Some(FileKind.Source)) => 0 - case (Some(FileKind.Source), Some(_)) => 1 - } - } - - val srcNode = nm(src).find { case (_, sk, _, _) => - sk == Some(k) - }.get - val dstNode = nm(dst).sortBy(rec => (rec._2, rec._1)).head - s"${srcNode._3} -> ${dstNode._3};" - } - - val header = Doc.text("digraph G {") - val allNodes: List[Doc] = nodes.iterator - .flatMap { case (_, ns) => - ns.map(rec => (rec._1, Doc.text(rec._4))).toList - } - .toList - .sortBy(_._1) - .map(_._2) - val nodesDoc = Doc.intercalate(Doc.hardLine, allNodes) - val edges: List[Doc] = - depinfo.flatMap { case (_, pn, k, deps) => - deps.map { dep => - Doc.text(makeEdge(pn, k, dep, nodes)) - } - } - val edgesDoc = Doc.intercalate(Doc.hardLine, edges) - - val fullDoc = - header + (Doc.hardLine + nodesDoc + Doc.hardLine + edgesDoc) - .nested(2) + - Doc.hardLine + Doc.char('}') - - writeOut(fullDoc, output).as(ExitCode.Success) - } - } - def reportException(ex: Throwable): IO[Unit] = mainExceptionToString(ex) match { case Some(msg) => diff --git a/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala b/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala index d05bd81cd..2d8c45168 100644 --- a/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala +++ b/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala @@ -9,9 +9,11 @@ import java.io.{ FileInputStream, FileOutputStream, BufferedInputStream, - BufferedOutputStream + BufferedOutputStream, + PrintWriter } import scalapb.{GeneratedMessage, GeneratedMessageCompanion} +import org.typelevel.paiges.Doc import cats.syntax.all._ @@ -19,9 +21,14 @@ object IOPlatformIO extends PlatformIO[IO, JPath] { type F[A] = IO[A] type Path = JPath + def pathOrdering = Ordering.ordered[JPath] + override def pathArg: Argument[Path] = Argument.readPath + def resolve(base: JPath, p: List[String]): JPath = + p.foldLeft(base)(_.resolve(_)) + override def moduleIOMonad: MonadError[IO, Throwable] = cats.effect.IO.asyncForIO @@ -150,4 +157,28 @@ object IOPlatformIO extends PlatformIO[IO, JPath] { if (packFile.toString.isEmpty) None else loop(roots) } + + def writeDoc(p: Path, d: Doc): IO[Unit] = + IO.blocking { + Option(p.getParent).foreach(_.toFile.mkdirs) + val pw = new PrintWriter(p.toFile, "UTF-8") + try d.renderStream(100).foreach(pw.print(_)) + finally { + pw.close + } + } + + def print(str: String): IO[Unit] = + IO.println(str) + + def writeStdout(doc: Doc): IO[Unit] = + IO.blocking { + doc + .renderStreamTrim(80) + .iterator + .foreach(System.out.print) + + System.out.println("") + } + } \ No newline at end of file diff --git a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala index 4612affd5..1818db316 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala @@ -1,13 +1,15 @@ package org.bykn.bosatsu import cats.data.{Chain, Validated, ValidatedNel, NonEmptyList} +import cats.implicits.catsKernelOrderingForOrder import cats.Traverse import com.monovore.decline.{Argument, Command, Help, Opts} import cats.parse.{Parser0 => P0, Parser => P} import org.typelevel.paiges.Doc import scala.util.{Failure, Success, Try} import org.bykn.bosatsu.Parser.argFromParser -import org.bykn.bosatsu.tool.{FileKind, GraphOutput, Output} +import org.bykn.bosatsu.tool.{ExitCode, FileKind, GraphOutput, Output} +import org.typelevel.paiges.Document import codegen.Transpiler @@ -15,7 +17,7 @@ import Identifier.Bindable import IorMethods.IorExtension import LocationMap.Colorize -import cats.implicits._ +import cats.syntax.all._ /** This is an implementation of the CLI tool where Path is abstracted. The idea * is to allow it to be testable and usable in scalajs where we don't have @@ -1316,4 +1318,192 @@ abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { )(opts) } } + + final def reportOutput(out: Output[Path]): IO[ExitCode] = + out match { + case Output.TestOutput(resMap, color) => + val hasMissing = resMap.exists(_._2.isEmpty) + // it would be nice to run in parallel, but + // MatchlessToValue is not currently threadsafe + val evalTest = resMap.map { + case (p, Some(evalTest)) => + (p, Some(evalTest.value)) + case (p, None) => (p, None) + } + + val testReport = Test.outputFor(evalTest, color) + val success = !hasMissing && (testReport.fails == 0) + val code = if (success) ExitCode.Success else ExitCode.Error + print(testReport.doc.render(80)).as(code) + case Output.EvaluationResult(_, tpe, resDoc) => + val tDoc = rankn.Type.fullyResolvedDocument.document(tpe) + val doc = + resDoc.value + (Doc.lineOrEmpty + Doc.text(": ") + tDoc).nested(4) + print(doc.render(100)).as(ExitCode.Success) + case Output.JsonOutput(json, pathOpt) => + writeOut(json.toDoc, pathOpt) + .as(ExitCode.Success) + + case Output.TranspileOut(outs, base) => + outs.toList + .map { case (p, d) => + (p, platformIO.writeDoc(platformIO.resolve(base, p.toList), d)) + } + .sortBy(_._1) + .traverse_ { case (_, w) => w } + .as(ExitCode.Success) + + case Output.CompileOut(packList, ifout, output) => + val ifres = ifout match { + case None => moduleIOMonad.unit + case Some(ifacePath) => + val ifs = packList.map(Package.interfaceOf(_)) + writeInterfaces(ifs, ifacePath) + } + val out = output.fold(moduleIOMonad.unit)(writePackages(packList, _)) + + (ifres *> out).as(ExitCode.Success) + + case Output.ShowOutput(packs, ifaces, output) => + val pdocs = packs.map { pack => + Document[Package.Typed[Any]].document(pack) + } + val idocs = ifaces.map { iface => + Document[Package.Interface].document(iface) + } + + val doc = Doc.intercalate(Doc.hardLine, idocs ::: pdocs) + writeOut(doc, output).as(ExitCode.Success) + + case Output.DepsOutput(depinfo, output, style) => + style match { + case GraphOutput.Json => + def toJson( + dep: (Path, PackageName, FileKind, List[PackageName]) + ): Json = + Json.JObject( + ("path", Json.JString(dep._1.toString())) :: + ("package", Json.JString(dep._2.asString)) :: + ("kind", Json.JString(dep._3.name)) :: + ( + "dependsOn", + Json.JArray( + dep._4.map(pn => Json.JString(pn.asString)).toVector + ) + ) :: + Nil + ) + + val asJson = Json.JArray( + depinfo + .sortBy { case (path, pn, fk, _) => (path, pn, fk.name) } + .map(toJson) + .toVector + ) + + writeOut(asJson.toDoc, output).as(ExitCode.Success) + case GraphOutput.Dot => + def shapeOf(fk: FileKind): String = + fk match { + case FileKind.Iface => "diamond" + case FileKind.Pack => "box" + case FileKind.Source => "circle" + } + + type Ident = String + def makeNode( + idx: Int, + dep: (Path, PackageName, FileKind, List[PackageName]) + ): (Ident, String) = { + // C [shape=box, fillcolor=lightgrey, label="Node C"]; + val ident = s"N$idx" + val decl = + s"$ident [shape=${shapeOf(dep._3)}, label=\"${dep._2.asString}\"];" + (ident, decl) + } + def makeMissing(idx: Int, pack: PackageName): (Ident, String) = { + // C [shape=box, fillcolor=lightgrey, label="Node C"]; + val ident = s"N$idx" + val decl = s"$ident [shape=octogon, label=\"${pack.asString}\"];" + (ident, decl) + } + + val knownPacks = depinfo.map(_._2).toSet + val allPacks = + depinfo.flatMap(dep => dep._2 :: dep._4).distinct.sorted + val unknownPacks = allPacks.filterNot(knownPacks) + type NodeMap = Map[PackageName, NonEmptyList[ + (Int, Option[FileKind], String, String) + ]] + val depinfoSize = depinfo.size + val nodes: NodeMap = + (depinfo.zipWithIndex.map { case (dep, idx) => + val (ident, nstr) = makeNode(idx, dep) + (dep._2, (idx, Some(dep._3), ident, nstr)) + } ::: unknownPacks.mapWithIndex { (pn, idx0) => + val idx = depinfoSize + idx0 + val (ident, nstr) = makeMissing(idx, pn) + (pn, (idx, None, ident, nstr)) + }) + .groupByNel(_._1) + .map { case (k, v) => + (k, v.map(_._2)) + } + + // now NodeMap has everything + def makeEdge( + src: PackageName, + k: FileKind, + dst: PackageName, + nm: NodeMap + ): String = { + implicit val orderKind: cats.Order[Option[FileKind]] = + new cats.Order[Option[FileKind]] { + def compare(a: Option[FileKind], b: Option[FileKind]) = + (a, b) match { + case (None, None) => 0 + case (Some(_), None) => -1 + case (None, Some(_)) => 1 + case (Some(FileKind.Iface), Some(FileKind.Iface)) => 0 + case (Some(FileKind.Iface), Some(_)) => -1 + case (Some(FileKind.Pack), Some(FileKind.Iface)) => 1 + case (Some(FileKind.Pack), Some(FileKind.Pack)) => 0 + case (Some(FileKind.Pack), Some(FileKind.Source)) => -1 + case (Some(FileKind.Source), Some(FileKind.Source)) => 0 + case (Some(FileKind.Source), Some(_)) => 1 + } + } + + val srcNode = nm(src).find { case (_, sk, _, _) => + sk == Some(k) + }.get + val dstNode = nm(dst).sortBy(rec => (rec._2, rec._1)).head + s"${srcNode._3} -> ${dstNode._3};" + } + + val header = Doc.text("digraph G {") + val allNodes: List[Doc] = nodes.iterator + .flatMap { case (_, ns) => + ns.map(rec => (rec._1, Doc.text(rec._4))).toList + } + .toList + .sortBy(_._1) + .map(_._2) + val nodesDoc = Doc.intercalate(Doc.hardLine, allNodes) + val edges: List[Doc] = + depinfo.flatMap { case (_, pn, k, deps) => + deps.map { dep => + Doc.text(makeEdge(pn, k, dep, nodes)) + } + } + val edgesDoc = Doc.intercalate(Doc.hardLine, edges) + + val fullDoc = + header + (Doc.hardLine + nodesDoc + Doc.hardLine + edgesDoc) + .nested(2) + + Doc.hardLine + Doc.char('}') + + writeOut(fullDoc, output).as(ExitCode.Success) + } + } } diff --git a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala index 0c0d262a7..a66069e13 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala @@ -5,10 +5,11 @@ import cats.data.Kleisli import com.monovore.decline.Argument import scala.collection.immutable.SortedMap import org.bykn.bosatsu.tool.Output +import org.typelevel.paiges.Doc import cats.syntax.all._ -class MemoryMain[G[_], K: Ordering]( +class MemoryMain[G[_], K]( platform: PlatformIO[Kleisli[G, MemoryMain.State[K], *], K]) extends MainModule[Kleisli[G, MemoryMain.State[K], *], K](platform) { @@ -70,6 +71,7 @@ object MemoryMain { def memoryPlatformIO[G[_], K](split: K => List[String])(implicit pathArg0: Argument[K], + pathOrd: Ordering[K], innerMonad: MonadError[G, Throwable]): PlatformIO[Kleisli[G, State[K], *], K] = { val catsDefaultME = implicitly[MonadError[Kleisli[G, State[K], *], Throwable]] @@ -78,6 +80,7 @@ object MemoryMain { type F[A] = Kleisli[G, State[K], A] type Path = K def moduleIOMonad: MonadError[F, Throwable] = catsDefaultME + def pathOrdering = pathOrd def pathArg: Argument[K] = pathArg0 def readPath(p: Path): F[String] = @@ -149,6 +152,24 @@ object MemoryMain { roots.collectFirstSome(getP) } + + def writeDoc(p: K, d: Doc): F[Unit] = + catsDefaultME.raiseError(new Exception(s"writeDoc($p, $d) is unimplemented on a read only platform")) + + def writeInterfaces(ifaces: List[Package.Interface], path: K): F[Unit] = + catsDefaultME.raiseError(new Exception(s"writeInterfaces($ifaces, $path) is unimplemented on a read only platform")) + + def writePackages[A](packs: List[Package.Typed[A]], path: K): F[Unit] = + catsDefaultME.raiseError(new Exception(s"writePackages($packs, $path) is unimplemented on a read only platform")) + + def writeStdout(doc: Doc): F[Unit] = + catsDefaultME.raiseError(new Exception(s"writeStdout($doc) is unimplemented on a read only platform")) + + def print(str: String): F[Unit] = + catsDefaultME.raiseError(new Exception(s"print($str) is unimplemented on a read only platform")) + + // TODO: this is broken + def resolve(base: K, parts: List[String]): K = base } } } diff --git a/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala b/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala index 96cede7aa..3cde210ae 100644 --- a/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala +++ b/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala @@ -2,10 +2,12 @@ package org.bykn.bosatsu import cats.MonadError import com.monovore.decline.Argument +import org.typelevel.paiges.Doc trait PlatformIO[F[_], Path] { implicit def moduleIOMonad: MonadError[F, Throwable] implicit def pathArg: Argument[Path] + implicit def pathOrdering: Ordering[Path] def readPath(p: Path): F[String] def readPackages(paths: List[Path]): F[List[Package.Typed[Unit]]] @@ -28,4 +30,26 @@ trait PlatformIO[F[_], Path] { def unfoldDir: Option[Path => F[Option[F[List[Path]]]]] def hasExtension(str: String): Path => Boolean -} \ No newline at end of file + + def writeDoc(p: Path, d: Doc): F[Unit] + def writeStdout(doc: Doc): F[Unit] + + final def writeOut(doc: Doc, out: Option[Path]): F[Unit] = + out match { + case None => writeStdout(doc) + case Some(p) => writeDoc(p, doc) + } + + + def resolve(base: Path, p: List[String]): Path + + // this is println actually + def print(str: String): F[Unit] + + def writeInterfaces( + interfaces: List[Package.Interface], + path: Path + ): F[Unit] + + def writePackages[A](packages: List[Package.Typed[A]], path: Path): F[Unit] +} diff --git a/core/src/main/scala/org/bykn/bosatsu/tool/ExitCode.scala b/core/src/main/scala/org/bykn/bosatsu/tool/ExitCode.scala new file mode 100644 index 000000000..0b5ac701a --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/tool/ExitCode.scala @@ -0,0 +1,7 @@ +package org.bykn.bosatsu.tool + +sealed abstract class ExitCode(val toInt: Int) +object ExitCode { + case object Success extends ExitCode(0) + case object Error extends ExitCode(1) +} \ No newline at end of file