diff --git a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala index c6d667285..eb0a5eafc 100644 --- a/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala +++ b/cli/src/main/scala/org/bykn/bosatsu/PathModule.scala @@ -1,7 +1,6 @@ package org.bykn.bosatsu -import cats.effect.IO -import com.monovore.decline.Argument +import cats.effect.{IO, Resource} import org.typelevel.paiges.{Doc, Document} import java.nio.file.{Path => JPath} @@ -12,66 +11,18 @@ import cats.effect.ExitCode import cats.implicits.catsKernelOrderingForOrder import cats.syntax.all._ -object PathModule extends MainModule[IO] { +object PathModule extends MainModule[IO, JPath](IOPlatformIO) { self => type Path = JPath - override def pathArg: Argument[Path] = - Argument.readPath - - def readPath(path: Path): IO[String] = - IO.blocking(new String(java.nio.file.Files.readAllBytes(path), "utf-8")) - - val resolvePath: Some[(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") - IO.blocking { - // this is a side-effect since file is mutable - // and talks to the file system - val file = filePath.toFile - if (file.exists()) Some(filePath) - else None - } - } - ) - - def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] = - ProtoConverter.readPackages(paths) - - def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] = - ProtoConverter.readInterfaces(paths) - - def writeInterfaces( - interfaces: List[Package.Interface], - path: Path - ): IO[Unit] = - ProtoConverter.writeInterfaces(interfaces, path) - - def writePackages[A](packages: List[Package.Typed[A]], path: Path): IO[Unit] = - ProtoConverter.writePackages(packages, path) - - def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] = Some { - (path: Path) => - IO.blocking { - val f = path.toFile - - if (f.isDirectory()) { - Some(IO.blocking { - f.listFiles.iterator.map(_.toPath).toList - }) - } else None - } - } - - def hasExtension(str: String): Path => Boolean = { (path: Path) => - path.toString.endsWith(str) - } - - def print(str: => String): IO[Unit] = + def print(str: String): IO[Unit] = IO.println(str) - def delay[A](a: => A): IO[A] = IO(a) + 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 report(io: IO[Output]): IO[ExitCode] = io.attempt.flatMap { @@ -79,7 +30,7 @@ object PathModule extends MainModule[IO] { case Left(err) => reportException(err).as(ExitCode.Error) } - private def writeOut(doc: Doc, out: Option[Path]): IO[Unit] = + private def writeOut(doc: Doc, out: Option[JPath]): IO[Unit] = out match { case None => IO.blocking { @@ -94,6 +45,15 @@ object PathModule extends MainModule[IO] { CodeGenWrite.writeDoc(p, doc) } + def writeInterfaces( + interfaces: List[Package.Interface], + path: JPath + ): IO[Unit] = + ProtoConverter.writeInterfaces(interfaces, path) + + def writePackages[A](packages: List[Package.Typed[A]], path: JPath): IO[Unit] = + ProtoConverter.writePackages(packages, path) + def reportOutput(out: Output): IO[ExitCode] = out match { case Output.TestOutput(resMap, color) => @@ -120,7 +80,7 @@ object PathModule extends MainModule[IO] { .as(ExitCode.Success) case Output.TranspileOut(outs, base) => - def path(p: List[String]): Path = + def path(p: List[String]): JPath = p.foldLeft(base)(_.resolve(_)) outs.toList @@ -295,35 +255,4 @@ object PathModule extends MainModule[IO] { IO.blocking(ex.printStackTrace(System.err)) } - def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] = { - import scala.jdk.CollectionConverters._ - - def getP(p: Path): Option[PackageName] = { - val subPath = p - .relativize(packFile) - .asScala - .map { part => - part.toString.toLowerCase.capitalize - } - .mkString("/") - - val dropExtension = """(.*)\.[^.]*$""".r - val toParse = subPath match { - case dropExtension(prefix) => prefix - case _ => subPath - } - PackageName.parse(toParse) - } - - @annotation.tailrec - def loop(roots: List[Path]): Option[PackageName] = - roots match { - case Nil => None - case h :: _ if packFile.startsWith(h) => getP(h) - case _ :: t => loop(t) - } - - if (packFile.toString.isEmpty) None - else loop(roots) - } } diff --git a/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala b/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala new file mode 100644 index 000000000..f6edab994 --- /dev/null +++ b/cli/src/main/scala/org/bykn/bosatsu/PlatformModule.scala @@ -0,0 +1,90 @@ +package org.bykn.bosatsu + +import cats.MonadError +import cats.effect.IO +import com.monovore.decline.Argument +import java.nio.file.{Path => JPath} + +object IOPlatformIO extends PlatformIO[IO, JPath] { + type F[A] = IO[A] + type Path = JPath + + override def pathArg: Argument[Path] = + Argument.readPath + + override def moduleIOMonad: MonadError[IO, Throwable] = + cats.effect.IO.asyncForIO + + def readPath(path: Path): IO[String] = + IO.blocking(new String(java.nio.file.Files.readAllBytes(path), "utf-8")) + + val resolvePath: Some[(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") + IO.blocking { + // this is a side-effect since file is mutable + // and talks to the file system + val file = filePath.toFile + if (file.exists()) Some(filePath) + else None + } + } + ) + + def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] = + ProtoConverter.readPackages(paths) + + def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] = + ProtoConverter.readInterfaces(paths) + + def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] = Some { + (path: Path) => + IO.blocking { + val f = path.toFile + + if (f.isDirectory()) { + Some(IO.blocking { + f.listFiles.iterator.map(_.toPath).toList + }) + } else None + } + } + + def hasExtension(str: String): Path => Boolean = { (path: Path) => + path.toString.endsWith(str) + } + + def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] = { + import scala.jdk.CollectionConverters._ + + def getP(p: Path): Option[PackageName] = { + val subPath = p + .relativize(packFile) + .asScala + .map { part => + part.toString.toLowerCase.capitalize + } + .mkString("/") + + val dropExtension = """(.*)\.[^.]*$""".r + val toParse = subPath match { + case dropExtension(prefix) => prefix + case _ => subPath + } + PackageName.parse(toParse) + } + + @annotation.tailrec + def loop(roots: List[Path]): Option[PackageName] = + roots match { + case Nil => None + case h :: _ if packFile.startsWith(h) => getP(h) + case _ :: t => loop(t) + } + + if (packFile.toString.isEmpty) None + else loop(roots) + } +} \ No newline at end of file diff --git a/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala b/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala index 8cbd4a1b8..42ea770cb 100644 --- a/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala +++ b/cli/src/test/scala/org/bykn/bosatsu/PathModuleTest.scala @@ -14,6 +14,7 @@ import org.scalatest.funsuite.AnyFunSuite import cats.effect.unsafe.implicits.global class PathModuleTest extends AnyFunSuite { + import PathModule.platformIO.pathPackage implicit val arbPath: Arbitrary[Path] = Arbitrary { @@ -24,7 +25,7 @@ class PathModuleTest extends AnyFunSuite { test("test some hand written examples") { def pn(roots: List[String], file: String): Option[PackageName] = - PathModule.pathPackage(roots.map(Paths.get(_)), Paths.get(file)) + pathPackage(roots.map(Paths.get(_)), Paths.get(file)) assert( pn(List("/root0", "/root1"), "/root0/Bar.bosatsu") == Some( @@ -56,13 +57,13 @@ class PathModuleTest extends AnyFunSuite { test("no roots means no Package") { forAll { (p: Path) => - assert(PathModule.pathPackage(Nil, p) == None) + assert(pathPackage(Nil, p) == None) } } test("empty path is not okay for a package") { forAll { (roots: List[Path]) => - assert(PathModule.pathPackage(roots, Paths.get("")) == None) + assert(pathPackage(roots, Paths.get("")) == None) } } @@ -74,7 +75,7 @@ class PathModuleTest extends AnyFunSuite { PackageName.parse( rest.asScala.map(_.toString.toLowerCase.capitalize).mkString("/") ) - assert(PathModule.pathPackage(root :: otherRoots, path) == pack) + assert(pathPackage(root :: otherRoots, path) == pack) } forAll(law(_, _, _)) @@ -91,7 +92,7 @@ class PathModuleTest extends AnyFunSuite { test("if none of the roots are prefixes we have none") { forAll { (r0: Path, roots0: List[Path], file: Path) => val roots = (r0 :: roots0).filterNot(_.toString == "") - val pack = PathModule.pathPackage(roots, file) + val pack = pathPackage(roots, file) val noPrefix = !roots.exists { r => file.asScala.toList.startsWith(r.asScala.toList) diff --git a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala index 2125115de..ea97b20cb 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MainModule.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MainModule.scala @@ -1,7 +1,7 @@ package org.bykn.bosatsu import cats.data.{Chain, Validated, ValidatedNel, NonEmptyList} -import cats.{Eval, MonadError, Traverse} +import cats.{Eval, Traverse} import com.monovore.decline.{Argument, Command, Help, Opts} import cats.parse.{Parser0 => P0, Parser => P} import org.typelevel.paiges.Doc @@ -20,60 +20,17 @@ import cats.implicits._ * is to allow it to be testable and usable in scalajs where we don't have * file-IO */ -abstract class MainModule[IO[_]](implicit - val moduleIOMonad: MonadError[IO, Throwable] -) { - type Path +abstract class MainModule[IO[_], Path](val platformIO: PlatformIO[IO, Path]) { + type F[A] = IO[A] - implicit def pathArg: Argument[Path] + import platformIO._ - def readPath(p: Path): IO[String] - - def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] - - def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] - - /** given an ordered list of prefered roots, if a packFile starts with one of - * these roots, return a PackageName based on the rest - */ - def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] - - /** Modules optionally have the capability to combine paths into a tree - */ - def resolvePath: Option[(Path, PackageName) => IO[Option[Path]]] - - /** some modules have paths that form directory trees - * - * if the given path is a directory, return Some and all the first children. - */ - def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] - - def hasExtension(str: String): Path => Boolean - - // we can do side effects in here - def delay[A](a: => A): IO[A] + def withEC[A](fn: Par.EC => IO[A]): IO[A] ////////////////////////////// // Below here are concrete and should not use override ////////////////////////////// - final def withEC[A](fn: Par.EC => IO[A]): IO[A] = - delay(Par.newService()) - .flatMap { es => - fn(Par.ecFromService(es)) - .flatMap { a => - delay { - Par.shutdownService(es) - a - } - } - .recoverWith { case e => - delay { - Par.shutdownService(es) - }.flatMap(_ => moduleIOMonad.raiseError[A](e)) - } - } - final def run(args: List[String]): Either[Help, IO[Output]] = MainCommand.command .parse(args.toList) @@ -206,10 +163,10 @@ abstract class MainModule[IO[_]](implicit } object MainCommand { - def parseInputs[F[_]: Traverse]( - paths: F[Path], + def parseInputs[G[_]: Traverse]( + paths: G[Path], packRes: PackageResolver - ): IO[ValidatedNel[ParseError, F[((Path, LocationMap), Package.Parsed)]]] = + ): IO[ValidatedNel[ParseError, G[((Path, LocationMap), Package.Parsed)]]] = // we use IO(traverse) so we can accumulate all the errors in parallel easily // if do this with parseFile returning an IO, we need to do IO.Par[Validated[...]] // and use the composed applicative... too much work for the same result @@ -223,10 +180,10 @@ abstract class MainModule[IO[_]](implicit } .map(_.sequence) - def parseHeaders[F[_]: Traverse]( - paths: F[Path], + def parseHeaders[G[_]: Traverse]( + paths: G[Path], packRes: PackageResolver - ): IO[ValidatedNel[ParseError, F[(Path, Package.Header)]]] = + ): IO[ValidatedNel[ParseError, G[(Path, Package.Header)]]] = // we use IO(traverse) so we can accumulate all the errors in parallel easily // if do this with parseFile returning an IO, we need to do IO.Par[Validated[...]] // and use the composed applicative... too much work for the same result @@ -1000,10 +957,10 @@ abstract class MainModule[IO[_]](implicit ) } - def process[F[_]: Traverse]( + def process[G[_]: Traverse]( io: IO[String], - extract: Json => IO[F[Json]], - inject: F[Json] => Json + extract: Json => IO[G[Json]], + inject: G[Json] => Json ): IO[Output.JsonOutput] = v2j.valueFnToJsonFn(res.tpe) match { case Left(unsup) => unsupported(unsup) diff --git a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala index 353ecc516..1deb84537 100644 --- a/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala +++ b/core/src/main/scala/org/bykn/bosatsu/MemoryMain.scala @@ -5,83 +5,38 @@ import cats.data.Kleisli import com.monovore.decline.Argument import scala.collection.immutable.SortedMap -import cats.implicits._ - -class MemoryMain[F[_], K: Ordering](split: K => List[String])(implicit - val pathArg: Argument[K], - val innerMonad: MonadError[F, Throwable] -) extends MainModule[Kleisli[F, MemoryMain.State[K], *]] { - - type IO[A] = Kleisli[F, MemoryMain.State[K], A] - - type Path = K - - def readPath(p: Path): IO[String] = - Kleisli - .ask[F, MemoryMain.State[K]] - .flatMap { files => - files.get(p) match { - case Some(MemoryMain.FileContent.Str(res)) => moduleIOMonad.pure(res) - case other => - moduleIOMonad.raiseError( - new Exception(s"expect String content, found: $other") - ) - } - } - - def resolvePath: Option[(Path, PackageName) => IO[Option[Path]]] = None - - def readPackages(paths: List[Path]): IO[List[Package.Typed[Unit]]] = - Kleisli - .ask[F, MemoryMain.State[K]] - .flatMap { files => - paths - .traverse { path => - files.get(path) match { - case Some(MemoryMain.FileContent.Packages(res)) => - moduleIOMonad.pure(res) - case other => - moduleIOMonad.raiseError[List[Package.Typed[Unit]]]( - new Exception(s"expect Packages content, found: $other") - ) - } - } - .map(_.flatten) - } +import cats.syntax.all._ - def readInterfaces(paths: List[Path]): IO[List[Package.Interface]] = - Kleisli - .ask[F, MemoryMain.State[K]] - .flatMap { files => - paths - .traverse { path => - files.get(path) match { - case Some(MemoryMain.FileContent.Interfaces(res)) => - moduleIOMonad.pure(res) - case other => - moduleIOMonad.raiseError[List[Package.Interface]]( - new Exception(s"expect Packages content, found: $other") - ) - } - } - .map(_.flatten) - } +class MemoryMain[G[_], K: Ordering]( + platform: PlatformIO[Kleisli[G, MemoryMain.State[K], *], K]) extends + MainModule[Kleisli[G, MemoryMain.State[K], *], K](platform) { - def unfoldDir: Option[Path => IO[Option[IO[List[Path]]]]] = None + import platformIO._ - def hasExtension(str: String): (Path => Boolean) = - Function.const(false)(_) + def withEC[A](fn: Par.EC => F[A]): F[A] = + moduleIOMonad.flatMap(moduleIOMonad.unit) { _ => + val es = Par.newService() + moduleIOMonad.map(fn(Par.ecFromService(es))) { a => + Par.shutdownService(es) + a + } + .recoverWith { case e => + Par.shutdownService(es) + moduleIOMonad.raiseError[A](e) + } + } def runWith( files: Iterable[(K, String)], packages: Iterable[(K, List[Package.Typed[Unit]])] = Nil, interfaces: Iterable[(K, List[Package.Interface])] = Nil - )(cmd: List[String]): F[Output] = + )(cmd: List[String]): G[Output] = run(cmd) match { case Left(msg) => - innerMonad.raiseError[Output]( + moduleIOMonad.raiseError[Output]( new Exception(s"got the help message for: $cmd: $msg") ) + .run(SortedMap.empty[K, MemoryMain.FileContent]) case Right(io) => val state0 = files.foldLeft(SortedMap.empty[K, MemoryMain.FileContent]) { @@ -97,22 +52,6 @@ class MemoryMain[F[_], K: Ordering](split: K => List[String])(implicit io.run(state2) } - def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] = { - val fparts = split(packFile) - - def getP(p: Path): Option[PackageName] = { - val splitP = split(p) - if (fparts.startsWith(splitP)) { - val parts = fparts.drop(splitP.length) - PackageName.parse(parts.mkString("/")) - } else None - } - - roots.collectFirstSome(getP) - } - - def delay[A](a: => A): IO[A] = - Kleisli(_ => innerMonad.pure(a)) } object MemoryMain { @@ -124,4 +63,91 @@ object MemoryMain { } type State[K] = SortedMap[K, FileContent] + + def apply[G[_], K: Argument: Ordering](split: K => List[String])(implicit innerMonad: MonadError[G, Throwable]): MemoryMain[G, K] = + new MemoryMain(memoryPlatformIO[G, K](split)) + + def memoryPlatformIO[G[_], K](split: K => List[String])(implicit + pathArg0: Argument[K], + innerMonad: MonadError[G, Throwable]): PlatformIO[Kleisli[G, State[K], *], K] = { + + val catsDefaultME = implicitly[MonadError[Kleisli[G, State[K], *], Throwable]] + + new PlatformIO[Kleisli[G, State[K], *], K] { + type F[A] = Kleisli[G, State[K], A] + type Path = K + def moduleIOMonad: MonadError[F, Throwable] = catsDefaultME + def pathArg: Argument[K] = pathArg0 + + def readPath(p: Path): F[String] = + Kleisli + .ask[G, State[K]] + .flatMap { files => + files.get(p) match { + case Some(MemoryMain.FileContent.Str(res)) => moduleIOMonad.pure(res) + case other => + moduleIOMonad.raiseError( + new Exception(s"expect String content, found: $other") + ) + } + } + + def resolvePath: Option[(Path, PackageName) => F[Option[Path]]] = None + + def readPackages(paths: List[Path]): F[List[Package.Typed[Unit]]] = + Kleisli + .ask[G, MemoryMain.State[K]] + .flatMap { files => + paths + .traverse { path => + files.get(path) match { + case Some(MemoryMain.FileContent.Packages(res)) => + moduleIOMonad.pure(res) + case other => + moduleIOMonad.raiseError[List[Package.Typed[Unit]]]( + new Exception(s"expect Packages content, found: $other") + ) + } + } + .map(_.flatten) + } + + def readInterfaces(paths: List[Path]): F[List[Package.Interface]] = + Kleisli + .ask[G, MemoryMain.State[K]] + .flatMap { files => + paths + .traverse { path => + files.get(path) match { + case Some(MemoryMain.FileContent.Interfaces(res)) => + moduleIOMonad.pure(res) + case other => + moduleIOMonad.raiseError[List[Package.Interface]]( + new Exception(s"expect Packages content, found: $other") + ) + } + } + .map(_.flatten) + } + + def unfoldDir: Option[Path => F[Option[F[List[Path]]]]] = None + + def hasExtension(str: String): (Path => Boolean) = + Function.const(false)(_) + + def pathPackage(roots: List[K], packFile: K): Option[PackageName] = { + val fparts = split(packFile) + + def getP(p: Path): Option[PackageName] = { + val splitP = split(p) + if (fparts.startsWith(splitP)) { + val parts = fparts.drop(splitP.length) + PackageName.parse(parts.mkString("/")) + } else None + } + + roots.collectFirstSome(getP) + } + } + } } diff --git a/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala b/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala new file mode 100644 index 000000000..96cede7aa --- /dev/null +++ b/core/src/main/scala/org/bykn/bosatsu/PlatformIO.scala @@ -0,0 +1,31 @@ +package org.bykn.bosatsu + +import cats.MonadError +import com.monovore.decline.Argument + +trait PlatformIO[F[_], Path] { + implicit def moduleIOMonad: MonadError[F, Throwable] + implicit def pathArg: Argument[Path] + + def readPath(p: Path): F[String] + def readPackages(paths: List[Path]): F[List[Package.Typed[Unit]]] + + def readInterfaces(paths: List[Path]): F[List[Package.Interface]] + + /** given an ordered list of prefered roots, if a packFile starts with one of + * these roots, return a PackageName based on the rest + */ + def pathPackage(roots: List[Path], packFile: Path): Option[PackageName] + + /** Modules optionally have the capability to combine paths into a tree + */ + def resolvePath: Option[(Path, PackageName) => F[Option[Path]]] + + /** some modules have paths that form directory trees + * + * if the given path is a directory, return Some and all the first children. + */ + def unfoldDir: Option[Path => F[Option[F[List[Path]]]]] + + def hasExtension(str: String): Path => Boolean +} \ No newline at end of file diff --git a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala index a608b093f..1db953856 100644 --- a/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala +++ b/core/src/test/scala/org/bykn/bosatsu/TestUtils.scala @@ -157,7 +157,7 @@ object TestUtils { case (idx, _) => "--input" :: idx.toString :: Nil } - private val module = new MemoryMain[Either[Throwable, *], Int]({ idx => + private val module = MemoryMain[Either[Throwable, *], Int]({ idx => if (idx == Int.MaxValue) Nil else List(s"Package$idx") }) diff --git a/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala b/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala index 8a74bc4ba..d8833bb60 100644 --- a/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala +++ b/jsapi/src/main/scala/org/bykn/bosatsu/jsapi/JsApi.scala @@ -16,7 +16,7 @@ object JsApi { private def splitPath(p: String): List[String] = p.split("/", -1).toList.map(_.toLowerCase.capitalize) - private val module = new MemoryMain[Either[Throwable, *], String](splitPath) + private val module = MemoryMain[Either[Throwable, *], String](splitPath) private def makeInputArgs(keys: Iterable[String]): List[String] = keys.iterator.flatMap(key => "--input" :: key :: Nil).toList diff --git a/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala b/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala index 94d600ee3..54a2d1eab 100644 --- a/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala +++ b/jsui/src/main/scala/org/bykn/bosatsu/jsui/Store.scala @@ -8,8 +8,7 @@ import org.scalajs.dom.window.localStorage import Action.Cmd object Store { - val memoryMain = - new MemoryMain[Either[Throwable, *], String](_.split("/", -1).toList) + val memoryMain = MemoryMain[Either[Throwable, *], String](_.split("/", -1).toList) type HandlerFn = memoryMain.Output => String def cmdHandler(cmd: Cmd): (List[String], HandlerFn) =