mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Crossprojects for JVM and JS platforms (#237)
This commit is contained in:
parent
50aa4db4e8
commit
e2da2e90d7
6
.jvmopts
Normal file
6
.jvmopts
Normal file
@ -0,0 +1,6 @@
|
||||
-Dfile.encoding=UTF8
|
||||
-Xms1G
|
||||
-Xmx5G
|
||||
-XX:ReservedCodeCacheSize=500M
|
||||
-XX:+TieredCompilation
|
||||
-XX:+UseParallelGC
|
7
backend/.js/src/main/scala/aqua/backend/Version.scala
Normal file
7
backend/.js/src/main/scala/aqua/backend/Version.scala
Normal file
@ -0,0 +1,7 @@
|
||||
package aqua.backend
|
||||
|
||||
object Version {
|
||||
|
||||
// TODO: get version for JS compiler
|
||||
lazy val version = "Unknown (JS)"
|
||||
}
|
8
backend/.jvm/src/main/scala/aqua/backend/Version.scala
Normal file
8
backend/.jvm/src/main/scala/aqua/backend/Version.scala
Normal file
@ -0,0 +1,8 @@
|
||||
package aqua.backend
|
||||
|
||||
object Version {
|
||||
|
||||
lazy val version = Option(getClass.getPackage.getImplementationVersion)
|
||||
.filter(_.nonEmpty)
|
||||
.getOrElse("Unknown")
|
||||
}
|
@ -7,14 +7,14 @@ import aqua.types.StreamType
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
sealed trait AirGen {
|
||||
def generate: Air
|
||||
|
||||
}
|
||||
|
||||
object AirGen extends LogSupport {
|
||||
object AirGen extends Logging {
|
||||
|
||||
def lambdaToString(ls: List[LambdaModel]): String = ls match {
|
||||
case Nil => ""
|
||||
@ -54,7 +54,7 @@ object AirGen extends LogSupport {
|
||||
case o :: Nil => ParGen(o, NullGen)
|
||||
case _ =>
|
||||
ops.toList.reduceLeftOption(ParGen(_, _)).getOrElse {
|
||||
warn("ParRes with no children converted to Null")
|
||||
logger.warn("ParRes with no children converted to Null")
|
||||
NullGen
|
||||
}
|
||||
})
|
||||
@ -63,7 +63,7 @@ object AirGen extends LogSupport {
|
||||
case o :: Nil => XorGen(o, NullGen)
|
||||
case _ =>
|
||||
ops.toList.reduceLeftOption(XorGen(_, _)).getOrElse {
|
||||
warn("XorRes with no children converted to Null")
|
||||
logger.warn("XorRes with no children converted to Null")
|
||||
NullGen
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
package aqua.backend.js
|
||||
|
||||
import aqua.backend.Version
|
||||
import aqua.model.AquaContext
|
||||
import aqua.model.transform.GenerationConfig
|
||||
import cats.data.Chain
|
||||
@ -21,9 +22,7 @@ object JavaScriptFile {
|
||||
| * This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
| * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
| * Aqua version: ${Option(getClass.getPackage.getImplementationVersion)
|
||||
.filter(_.nonEmpty)
|
||||
.getOrElse("Unknown")}
|
||||
| * Aqua version: ${Version.version}
|
||||
| *
|
||||
| */
|
||||
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|
||||
|
@ -1,5 +1,6 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.Version
|
||||
import aqua.model.AquaContext
|
||||
import aqua.model.transform.GenerationConfig
|
||||
import cats.data.Chain
|
||||
@ -21,9 +22,7 @@ object TypeScriptFile {
|
||||
| * This file is auto-generated. Do not edit manually: changes may be erased.
|
||||
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
|
||||
| * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
|
||||
| * Aqua version: ${Option(getClass.getPackage.getImplementationVersion)
|
||||
.filter(_.nonEmpty)
|
||||
.getOrElse("Unknown")}
|
||||
| * Aqua version: ${Version.version}
|
||||
| *
|
||||
| */
|
||||
|import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|
||||
|
103
build.sbt
103
build.sbt
@ -8,19 +8,12 @@ val catsV = "2.6.1"
|
||||
val catsParseV = "0.3.4"
|
||||
val monocleV = "3.0.0-M6"
|
||||
val scalaTestV = "3.2.9"
|
||||
val fs2V = "3.0.6"
|
||||
val fs2V = "3.1.0"
|
||||
val catsEffectV = "3.2.1"
|
||||
val airframeLogV = "21.5.4"
|
||||
val log4catsV = "2.1.1"
|
||||
val slf4jV = "1.7.30"
|
||||
val declineV = "2.1.0"
|
||||
|
||||
val airframeLog = "org.wvlet.airframe" %% "airframe-log" % airframeLogV
|
||||
val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectV
|
||||
val fs2Io = "co.fs2" %% "fs2-io" % fs2V
|
||||
val catsFree = "org.typelevel" %% "cats-free" % catsV
|
||||
val cats = "org.typelevel" %% "cats-core" % catsV
|
||||
|
||||
name := "aqua-hll"
|
||||
|
||||
val commons = Seq(
|
||||
@ -28,9 +21,8 @@ val commons = Seq(
|
||||
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
|
||||
scalaVersion := dottyVersion,
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "log4cats-core" % log4catsV,
|
||||
airframeLog,
|
||||
"org.scalatest" %% "scalatest" % scalaTestV % Test
|
||||
"com.outr" %%% "scribe" % "3.5.5",
|
||||
"org.scalatest" %%% "scalatest" % scalaTestV % Test
|
||||
),
|
||||
scalacOptions ++= {
|
||||
Seq(
|
||||
@ -47,95 +39,124 @@ val commons = Seq(
|
||||
|
||||
commons
|
||||
|
||||
lazy val cli = project
|
||||
lazy val cli = crossProject(JSPlatform, JVMPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("cli"))
|
||||
.settings(commons: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %%% "cats-effect" % catsEffectV,
|
||||
"com.monovore" %%% "decline" % declineV,
|
||||
"com.monovore" %%% "decline-effect" % declineV,
|
||||
"co.fs2" %%% "fs2-io" % fs2V
|
||||
)
|
||||
)
|
||||
.dependsOn(compiler, `backend-air`, `backend-ts`, `backend-js`)
|
||||
|
||||
lazy val cliJS = cli.js
|
||||
.settings(
|
||||
scalaJSUseMainModuleInitializer := true
|
||||
)
|
||||
|
||||
lazy val cliJVM = cli.jvm
|
||||
.settings(
|
||||
Compile / run / mainClass := Some("aqua.AquaCli"),
|
||||
assembly / mainClass := Some("aqua.AquaCli"),
|
||||
assembly / assemblyJarName := "aqua-cli-" + version.value + ".jar",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.monovore" %% "decline" % declineV,
|
||||
"com.monovore" %% "decline-effect" % declineV,
|
||||
catsEffect,
|
||||
fs2Io,
|
||||
"org.typelevel" %% "log4cats-slf4j" % log4catsV,
|
||||
"org.slf4j" % "slf4j-jdk14" % slf4jV
|
||||
)
|
||||
)
|
||||
.dependsOn(compiler, `backend-air`, `backend-ts`, `backend-js`)
|
||||
|
||||
lazy val types = project
|
||||
lazy val types = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.settings(commons)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
cats
|
||||
"org.typelevel" %%% "cats-core" % catsV
|
||||
)
|
||||
)
|
||||
|
||||
lazy val parser = project
|
||||
lazy val parser = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.settings(commons: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "cats-parse" % catsParseV,
|
||||
catsFree
|
||||
"org.typelevel" %%% "cats-parse" % catsParseV,
|
||||
"org.typelevel" %%% "cats-free" % catsV
|
||||
)
|
||||
)
|
||||
.dependsOn(types)
|
||||
|
||||
lazy val linker = project
|
||||
lazy val linker = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.settings(commons: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
airframeLog
|
||||
)
|
||||
)
|
||||
.dependsOn(parser)
|
||||
|
||||
lazy val model = project
|
||||
lazy val model = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.settings(commons: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
catsFree
|
||||
"org.typelevel" %%% "cats-free" % catsV
|
||||
)
|
||||
)
|
||||
.dependsOn(types)
|
||||
|
||||
lazy val `test-kit` = project
|
||||
lazy val `test-kit` = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("model/test-kit"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(model)
|
||||
|
||||
lazy val semantics = project
|
||||
lazy val semantics = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.settings(commons: _*)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"com.github.julien-truffaut" %% "monocle-core" % monocleV,
|
||||
"com.github.julien-truffaut" %% "monocle-macro" % monocleV
|
||||
"com.github.julien-truffaut" %%% "monocle-core" % monocleV,
|
||||
"com.github.julien-truffaut" %%% "monocle-macro" % monocleV
|
||||
)
|
||||
)
|
||||
.dependsOn(model, `test-kit` % Test, parser)
|
||||
|
||||
lazy val compiler = project
|
||||
lazy val compiler = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("compiler"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(semantics, linker, backend)
|
||||
|
||||
lazy val backend = project
|
||||
lazy val backend = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("backend"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(model)
|
||||
|
||||
lazy val `backend-air` = project
|
||||
lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("backend/air"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(backend)
|
||||
|
||||
lazy val `backend-ts` = project
|
||||
lazy val `backend-ts` = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("backend/ts"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(`backend-air`)
|
||||
|
||||
lazy val `backend-js` = project
|
||||
lazy val `backend-js` = crossProject(JVMPlatform, JSPlatform)
|
||||
.withoutSuffixFor(JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("backend/js"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(`backend-air`)
|
||||
|
48
cli/.js/src/main/scala/aqua/JsApp.scala
Normal file
48
cli/.js/src/main/scala/aqua/JsApp.scala
Normal file
@ -0,0 +1,48 @@
|
||||
package aqua
|
||||
|
||||
import aqua.backend.Backend
|
||||
import aqua.backend.ts.TypeScriptBackend
|
||||
import aqua.compiler.{AquaCompiler, AquaError, AquaSources}
|
||||
import aqua.model.transform.GenerationConfig
|
||||
import cats.effect.IO
|
||||
import cats.effect.unsafe.implicits.global
|
||||
import cats.data.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.{Applicative, Monad, Show}
|
||||
import aqua.parser.lift.FileSpan
|
||||
|
||||
object JsApp {
|
||||
|
||||
trait AquaJsError {}
|
||||
|
||||
case class FileId(name: String)
|
||||
|
||||
class Sources[F[_]: Applicative] extends AquaSources[F, AquaJsError, FileId] {
|
||||
|
||||
def sources: F[ValidatedNec[AquaJsError, Chain[(FileId, String)]]] =
|
||||
Validated.Valid(Chain.empty).pure[F]
|
||||
|
||||
// Resolve id of the imported imp string from I file
|
||||
def resolveImport(from: FileId, imp: String): F[ValidatedNec[AquaJsError, FileId]] =
|
||||
Validated.Valid(FileId("")).pure[F]
|
||||
|
||||
// Load file by its resolved I
|
||||
def load(file: FileId): F[ValidatedNec[AquaJsError, String]] = Validated.Valid("").pure[F]
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
// test code
|
||||
// val sources = new Sources[IO]()
|
||||
// val bodyConfig = GenerationConfig()
|
||||
// val b = AquaCompiler
|
||||
// .compileTo[IO, AquaJsError, FileId, FileSpan.F, String](
|
||||
// sources,
|
||||
// (fmid, src) => FileSpan.fileSpanLiftParser(fmid.name, src),
|
||||
// TypeScriptBackend,
|
||||
// bodyConfig,
|
||||
// (a) => ???
|
||||
// )
|
||||
println("Hello world!")
|
||||
}
|
||||
}
|
@ -6,16 +6,15 @@ import aqua.parser.expr.ConstantExpr
|
||||
import aqua.parser.lift.LiftParser
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{NonEmptyList, Validated, ValidatedNel}
|
||||
import cats.effect.ExitCode
|
||||
import cats.effect.{unsafe, ExitCode, IO}
|
||||
import cats.effect.std.Console
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.{Comonad, Functor}
|
||||
import com.monovore.decline.Opts.help
|
||||
import com.monovore.decline.{Opts, Visibility}
|
||||
import wvlet.log.{LogLevel => WLogLevel}
|
||||
|
||||
import java.nio.file.Path
|
||||
import scribe.Level
|
||||
import fs2.io.file.{Files, Path}
|
||||
|
||||
object AppOps {
|
||||
|
||||
@ -25,13 +24,13 @@ object AppOps {
|
||||
val versionOpt: Opts[Unit] =
|
||||
Opts.flag("version", help = "Show version", "v", Visibility.Partial)
|
||||
|
||||
val logLevelOpt: Opts[WLogLevel] =
|
||||
val logLevelOpt: Opts[Level] =
|
||||
Opts.option[String]("log-level", help = "Set log level").withDefault("info").mapValidated {
|
||||
str =>
|
||||
Validated.fromEither(toLogLevel(str))
|
||||
}
|
||||
|
||||
def toLogLevel(logLevel: String): Either[NonEmptyList[String], WLogLevel] = {
|
||||
def toLogLevel(logLevel: String): Either[NonEmptyList[String], Level] = {
|
||||
LogLevel.stringToLogLevel
|
||||
.get(logLevel.toLowerCase)
|
||||
.toRight(
|
||||
@ -42,56 +41,68 @@ object AppOps {
|
||||
)
|
||||
}
|
||||
|
||||
def checkPath: Path => ValidatedNel[String, Path] = { p =>
|
||||
def checkPath(implicit runtime: unsafe.IORuntime): String => ValidatedNel[String, Path] = {
|
||||
pathStr =>
|
||||
Validated
|
||||
.fromEither(Validated.catchNonFatal {
|
||||
val f = p.toFile
|
||||
if (f.exists()) {
|
||||
if (f.isFile) {
|
||||
val filename = f.getName
|
||||
val p = Path(pathStr)
|
||||
Files[IO]
|
||||
.exists(p)
|
||||
.flatMap { exists =>
|
||||
if (exists)
|
||||
Files[IO].isRegularFile(p).map { isFile =>
|
||||
if (isFile) {
|
||||
val filename = p.fileName.toString
|
||||
val ext = Option(filename)
|
||||
.filter(_.contains("."))
|
||||
.map(f => f.substring(f.lastIndexOf(".") + 1))
|
||||
.getOrElse("")
|
||||
if (ext != "aqua") Left("File must be with 'aqua' extension")
|
||||
else Right(p)
|
||||
} else Right(p)
|
||||
} else {
|
||||
Left(s"There is no path '${p.toString}'")
|
||||
} else
|
||||
Right(p)
|
||||
}
|
||||
else
|
||||
IO(Left(s"There is no path '${p.toString}'"))
|
||||
}
|
||||
// TODO: make it with correct effects
|
||||
.unsafeRunSync()
|
||||
}.toEither.left.map(t => s"An error occurred on imports reading: ${t.getMessage}").flatten)
|
||||
.toValidatedNel
|
||||
}
|
||||
|
||||
val inputOpts: Opts[Path] =
|
||||
def inputOpts(implicit runtime: unsafe.IORuntime): Opts[Path] =
|
||||
Opts
|
||||
.option[Path](
|
||||
.option[String](
|
||||
"input",
|
||||
"Path to an aqua file or an input directory that contains your .aqua files",
|
||||
"i"
|
||||
)
|
||||
.mapValidated(checkPath)
|
||||
|
||||
val outputOpts: Opts[Path] =
|
||||
Opts.option[Path]("output", "Path to the output directory", "o").mapValidated(checkPath)
|
||||
def outputOpts(implicit runtime: unsafe.IORuntime): Opts[Path] =
|
||||
Opts.option[String]("output", "Path to the output directory", "o").mapValidated(checkPath)
|
||||
|
||||
val importOpts: Opts[List[Path]] =
|
||||
def importOpts(implicit runtime: unsafe.IORuntime): Opts[List[Path]] =
|
||||
Opts
|
||||
.options[Path]("import", "Path to the directory to import from", "m")
|
||||
.options[String]("import", "Path to the directory to import from", "m")
|
||||
.mapValidated { ps =>
|
||||
val checked = ps
|
||||
.map(p => {
|
||||
.map(pStr => {
|
||||
Validated.catchNonFatal {
|
||||
val f = p.toFile
|
||||
if (f.exists() && f.isDirectory) {
|
||||
Right(p)
|
||||
} else {
|
||||
Left(s"There is no path ${p.toString} or it is not a directory")
|
||||
}
|
||||
val p = Path(pStr)
|
||||
(for {
|
||||
exists <- Files[IO].exists(p)
|
||||
isDir <- Files[IO].isDirectory(p)
|
||||
} yield {
|
||||
if (exists && isDir) Right(p)
|
||||
else Left(s"There is no path ${p.toString} or it is not a directory")
|
||||
})
|
||||
// TODO: make it with correct effects
|
||||
.unsafeRunSync()
|
||||
}
|
||||
})
|
||||
.toList
|
||||
|
||||
checked.map {
|
||||
case Validated.Valid(pE) =>
|
||||
pE match {
|
@ -9,18 +9,16 @@ import aqua.model.transform.GenerationConfig
|
||||
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||
import cats.Id
|
||||
import cats.data.Validated
|
||||
import cats.effect._
|
||||
import cats.effect.std.{Console => ConsoleEff}
|
||||
import cats.syntax.apply._
|
||||
import cats.syntax.functor._
|
||||
import cats.effect.*
|
||||
import cats.effect.std.Console as ConsoleEff
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.functor.*
|
||||
import com.monovore.decline.Opts
|
||||
import com.monovore.decline.effect.CommandIOApp
|
||||
import fs2.io.file.Files
|
||||
import org.typelevel.log4cats.slf4j.Slf4jLogger
|
||||
import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger}
|
||||
import wvlet.log.{LogSupport, Logger => WLogger}
|
||||
import scribe.Logging
|
||||
|
||||
object AquaCli extends IOApp with LogSupport {
|
||||
object AquaCli extends IOApp with Logging {
|
||||
import AppOps._
|
||||
|
||||
sealed trait CompileTarget
|
||||
@ -39,7 +37,8 @@ object AquaCli extends IOApp with LogSupport {
|
||||
}
|
||||
}
|
||||
|
||||
def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
|
||||
def main[F[_]: Concurrent: Files: ConsoleEff](runtime: unsafe.IORuntime): Opts[F[ExitCode]] = {
|
||||
implicit val r = runtime
|
||||
versionOpt
|
||||
.as(
|
||||
versionAndExit
|
||||
@ -59,8 +58,11 @@ object AquaCli extends IOApp with LogSupport {
|
||||
constantOpts[Id]
|
||||
).mapN {
|
||||
case (input, imports, output, toAir, toJs, noRelay, noXor, h, v, logLevel, constants) =>
|
||||
WLogger.setDefaultLogLevel(logLevel)
|
||||
WLogger.setDefaultFormatter(CustomLogFormatter)
|
||||
scribe.Logger.root
|
||||
.clearHandlers()
|
||||
.clearModifiers()
|
||||
.withHandler(formatter = LogFormatter.formatter, minimumLevel = Some(logLevel))
|
||||
.replace()
|
||||
|
||||
implicit val aio: AquaIO[F] = new AquaFilesIO[F]
|
||||
|
||||
@ -75,7 +77,7 @@ object AquaCli extends IOApp with LogSupport {
|
||||
val bc = GenerationConfig(wrapWithXor = !noXor, constants = constants)
|
||||
bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay))
|
||||
}
|
||||
info(s"Aqua Compiler ${versionStr}")
|
||||
logger.info(s"Aqua Compiler ${versionStr}")
|
||||
AquaPathCompiler
|
||||
.compileFilesTo[F](
|
||||
input,
|
||||
@ -89,7 +91,7 @@ object AquaCli extends IOApp with LogSupport {
|
||||
errs.map(System.out.println)
|
||||
ExitCode.Error
|
||||
case Validated.Valid(results) =>
|
||||
results.map(info(_))
|
||||
results.map(logger.info(_))
|
||||
ExitCode.Success
|
||||
}
|
||||
}
|
||||
@ -97,17 +99,13 @@ object AquaCli extends IOApp with LogSupport {
|
||||
}
|
||||
|
||||
override def run(args: List[String]): IO[ExitCode] = {
|
||||
|
||||
implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
|
||||
Slf4jLogger.getLogger[F]
|
||||
|
||||
CommandIOApp.run[IO](
|
||||
"aqua-c",
|
||||
"Aquamarine compiler",
|
||||
helpFlag = false,
|
||||
None
|
||||
)(
|
||||
main[IO],
|
||||
main[IO](runtime),
|
||||
// Weird ugly hack: in case version flag or help flag is present, ignore other options,
|
||||
// be it correct or not
|
||||
args match {
|
@ -3,20 +3,19 @@ package aqua
|
||||
import aqua.backend.Backend
|
||||
import aqua.compiler.{AquaCompiler, AquaError}
|
||||
import aqua.files.{AquaFileSources, FileModuleId}
|
||||
import aqua.io._
|
||||
import aqua.io.*
|
||||
import aqua.model.transform.GenerationConfig
|
||||
import aqua.parser.lift.FileSpan
|
||||
import cats.data._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.show._
|
||||
import cats.data.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.show.*
|
||||
import cats.{Monad, Show}
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
import fs2.io.file.{Files, Path}
|
||||
|
||||
import java.nio.file.Path
|
||||
object AquaPathCompiler extends Logging {
|
||||
|
||||
object AquaPathCompiler extends LogSupport {
|
||||
|
||||
def compileFilesTo[F[_]: AquaIO: Monad](
|
||||
def compileFilesTo[F[_]: AquaIO: Monad: Files](
|
||||
srcPath: Path,
|
||||
imports: List[Path],
|
||||
targetPath: Path,
|
@ -53,7 +53,7 @@ object ErrorRendering {
|
||||
val span = token.unit._1
|
||||
showForConsole(span, s"Cannot resolve import")
|
||||
case CycleError(modules) =>
|
||||
s"Cycle loops detected in imports: ${modules.map(_.file.getFileName)}"
|
||||
s"Cycle loops detected in imports: ${modules.map(_.file.fileName)}"
|
||||
case CompileError(err) =>
|
||||
err match {
|
||||
case RulesViolated(token, message) =>
|
@ -5,24 +5,19 @@ import aqua.files.AquaFilesIO
|
||||
import aqua.model.transform.GenerationConfig
|
||||
import cats.data.Validated
|
||||
import cats.effect.{IO, IOApp, Sync}
|
||||
import org.typelevel.log4cats.SelfAwareStructuredLogger
|
||||
import org.typelevel.log4cats.slf4j.Slf4jLogger
|
||||
|
||||
import java.nio.file.Paths
|
||||
import fs2.io.file.Path
|
||||
|
||||
object Test extends IOApp.Simple {
|
||||
|
||||
implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
|
||||
Slf4jLogger.getLogger[F]
|
||||
|
||||
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
|
||||
|
||||
override def run: IO[Unit] =
|
||||
AquaPathCompiler
|
||||
.compileFilesTo[IO](
|
||||
Paths.get("./aqua-src"),
|
||||
List(Paths.get("./aqua")),
|
||||
Paths.get("./target"),
|
||||
Path("./aqua-src"),
|
||||
List(Path("./aqua")),
|
||||
Path("./target"),
|
||||
TypeScriptBackend,
|
||||
GenerationConfig()
|
||||
)
|
@ -3,19 +3,22 @@ package aqua.files
|
||||
import aqua.AquaIO
|
||||
import aqua.compiler.{AquaCompiled, AquaSources}
|
||||
import aqua.io.{AquaFileError, FileSystemError, ListAquaErrors}
|
||||
import cats.Monad
|
||||
import cats.{Functor, Monad}
|
||||
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
|
||||
import cats.implicits.catsSyntaxApplicativeId
|
||||
import cats.syntax.either._
|
||||
import cats.syntax.flatMap._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.traverse._
|
||||
import cats.syntax.either.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.monad.*
|
||||
import cats.syntax.traverse.*
|
||||
import fs2.io.file.{Files, Path}
|
||||
|
||||
import java.nio.file.{Path, Paths}
|
||||
import scala.util.Try
|
||||
|
||||
class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[Path])
|
||||
extends AquaSources[F, AquaFileError, FileModuleId] {
|
||||
class AquaFileSources[F[_]: AquaIO: Monad: Files: Functor](
|
||||
sourcesPath: Path,
|
||||
importFrom: List[Path]
|
||||
) extends AquaSources[F, AquaFileError, FileModuleId] {
|
||||
private val filesIO = implicitly[AquaIO[F]]
|
||||
|
||||
override def sources: F[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] =
|
||||
@ -49,10 +52,11 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
|
||||
from: FileModuleId,
|
||||
imp: String
|
||||
): F[ValidatedNec[AquaFileError, FileModuleId]] = {
|
||||
Validated.fromEither(Try(Paths.get(imp)).toEither.leftMap(FileSystemError.apply)) match {
|
||||
val validatedPath = Validated.fromEither(Try(Path(imp)).toEither.leftMap(FileSystemError.apply))
|
||||
validatedPath match {
|
||||
case Validated.Valid(importP) =>
|
||||
filesIO
|
||||
.resolve(importP, from.file.getParent +: importFrom)
|
||||
.resolve(importP, importFrom.prependedAll(from.file.parent))
|
||||
.bimap(NonEmptyChain.one, FileModuleId(_))
|
||||
.value
|
||||
.map(Validated.fromEither)
|
||||
@ -64,6 +68,15 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
|
||||
override def load(file: FileModuleId): F[ValidatedNec[AquaFileError, String]] =
|
||||
filesIO.readFile(file.file).leftMap(NonEmptyChain.one).value.map(Validated.fromEither)
|
||||
|
||||
// Get a directory of a file, or this file if it is a directory itself
|
||||
private def getDir(path: Path): F[Path] = {
|
||||
Files[F]
|
||||
.isDirectory(path)
|
||||
.map { res =>
|
||||
if (res) path else path.parent.getOrElse(path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param srcFile aqua source
|
||||
* @param targetPath a main path where all output files will be written
|
||||
@ -74,21 +87,34 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
|
||||
srcFile: Path,
|
||||
targetPath: Path,
|
||||
suffix: String
|
||||
): Validated[Throwable, Path] =
|
||||
): F[Validated[Throwable, Path]] =
|
||||
getDir(sourcesPath).map { srcDir =>
|
||||
Validated.catchNonFatal {
|
||||
val srcDir = if (sourcesPath.toFile.isDirectory) sourcesPath else sourcesPath.getParent
|
||||
val srcFilePath = srcDir.toAbsolutePath
|
||||
.normalize()
|
||||
.relativize(srcFile.toAbsolutePath.normalize())
|
||||
val srcFilePath = srcDir.absolute.normalize
|
||||
.relativize(srcFile.absolute.normalize)
|
||||
|
||||
val targetDir =
|
||||
targetPath.toAbsolutePath
|
||||
.normalize()
|
||||
targetPath.absolute.normalize
|
||||
.resolve(
|
||||
srcFilePath
|
||||
)
|
||||
|
||||
targetDir.getParent.resolve(srcFile.getFileName.toString.stripSuffix(".aqua") + suffix)
|
||||
targetDir.parent
|
||||
.getOrElse(targetDir)
|
||||
.resolve(srcFile.fileName.toString.stripSuffix(".aqua") + suffix)
|
||||
}
|
||||
}
|
||||
|
||||
// Write content to a file and return a success message
|
||||
private def writeWithResult(target: Path, content: String, size: Int) = {
|
||||
filesIO
|
||||
.writeFile(
|
||||
target,
|
||||
content
|
||||
)
|
||||
.as(s"Result $target: compilation OK ($size functions)")
|
||||
.value
|
||||
.map(Validated.fromEither)
|
||||
}
|
||||
|
||||
def write(
|
||||
@ -106,19 +132,14 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
|
||||
ac.sourceId.file,
|
||||
targetPath,
|
||||
compiled.suffix
|
||||
).leftMap(FileSystemError.apply)
|
||||
).flatMap { result =>
|
||||
result
|
||||
.leftMap(FileSystemError.apply)
|
||||
.map { target =>
|
||||
filesIO
|
||||
.writeFile(
|
||||
target,
|
||||
compiled.content
|
||||
)
|
||||
.as(s"Result $target: compilation OK (${ac.compiled.size} functions)")
|
||||
.value
|
||||
.map(Validated.fromEither)
|
||||
writeWithResult(target, compiled.content, ac.compiled.size)
|
||||
}
|
||||
// TODO: we use both EitherT and F[Validated] to handle errors, that's why so weird
|
||||
.traverse(identity)
|
||||
}
|
||||
}.traverse(identity)
|
||||
.map(_.map(_.andThen(identity)))
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package aqua.files
|
||||
|
||||
import java.nio.file.Path
|
||||
import fs2.io.file.Path
|
||||
|
||||
case class FileModuleId private (file: Path)
|
||||
|
||||
object FileModuleId {
|
||||
|
||||
def apply(file: Path): FileModuleId =
|
||||
new FileModuleId(file.toAbsolutePath.normalize())
|
||||
new FileModuleId(file.absolute.normalize)
|
||||
}
|
@ -5,18 +5,16 @@ import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
|
||||
import cats.data.Chain
|
||||
import cats.effect.IO
|
||||
import cats.effect.unsafe.implicits.global
|
||||
import fs2.io.file.Files
|
||||
import fs2.text
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import java.nio.file.Paths
|
||||
import fs2.io.file.{Files, Path}
|
||||
|
||||
class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
implicit val aquaIO: AquaIO[IO] = AquaFilesIO.summon[IO]
|
||||
|
||||
"AquaFileSources" should "generate correct fileId with imports" in {
|
||||
val path = Paths.get("cli/src/test/test-dir/path-test")
|
||||
val path = Path("cli/.jvm/src/test/test-dir/path-test")
|
||||
val importPath = path.resolve("imports")
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
|
||||
@ -42,7 +40,7 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
"AquaFileSources" should "throw an error if a source file doesn't exist" in {
|
||||
val path = Paths.get("some/random/path")
|
||||
val path = Path("some/random/path")
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](path, Nil)
|
||||
|
||||
@ -51,7 +49,7 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
"AquaFileSources" should "throw an error if there is no import that is indicated in a source" in {
|
||||
val path = Paths.get("cli/src/test/test-dir")
|
||||
val path = Path("cli/.jvm/src/test/test-dir")
|
||||
val importPath = path.resolve("random/import/path")
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
|
||||
@ -61,7 +59,7 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
"AquaFileSources" should "find correct imports" in {
|
||||
val srcPath = Paths.get("cli/src/test/test-dir/index.aqua")
|
||||
val srcPath = Path("cli/.jvm/src/test/test-dir/index.aqua")
|
||||
val importPath = srcPath.resolve("imports")
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](srcPath, importPath :: Nil)
|
||||
@ -73,7 +71,8 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
.unsafeRunSync()
|
||||
|
||||
result.isValid shouldBe true
|
||||
result.getOrElse(FileModuleId(Paths.get("/some/random"))).file.toFile.exists() shouldBe true
|
||||
val file = result.getOrElse(FileModuleId(Path("/some/random"))).file
|
||||
Files[IO].exists(file).unsafeRunSync() shouldBe true
|
||||
|
||||
// should be found near src file
|
||||
val result2 =
|
||||
@ -82,7 +81,8 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
.unsafeRunSync()
|
||||
|
||||
result2.isValid shouldBe true
|
||||
result2.getOrElse(FileModuleId(Paths.get("/some/random"))).file.toFile.exists() shouldBe true
|
||||
val file2 = result2.getOrElse(FileModuleId(Path("/some/random"))).file
|
||||
Files[IO].exists(file2).unsafeRunSync() shouldBe true
|
||||
|
||||
// near src file but in another directory
|
||||
val sourceGen2 = new AquaFileSources[IO](srcPath, Nil)
|
||||
@ -92,20 +92,21 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
.unsafeRunSync()
|
||||
|
||||
result3.isValid shouldBe true
|
||||
result3.getOrElse(FileModuleId(Paths.get("/some/random"))).file.toFile.exists() shouldBe true
|
||||
val file3 = result3.getOrElse(FileModuleId(Path("/some/random"))).file
|
||||
Files[IO].exists(file3).unsafeRunSync() shouldBe true
|
||||
}
|
||||
|
||||
"AquaFileSources" should "resolve correct path for target" in {
|
||||
val path = Paths.get("cli/src/test/test-dir")
|
||||
val path = Path("cli/.jvm/src/test/test-dir")
|
||||
val filePath = path.resolve("some-dir/file.aqua")
|
||||
|
||||
val targetPath = Paths.get("/target/dir/")
|
||||
val targetPath = Path("/target/dir/")
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](path, Nil)
|
||||
|
||||
val suffix = "_custom.super"
|
||||
|
||||
val resolved = sourceGen.resolveTargetPath(filePath, targetPath, suffix)
|
||||
val resolved = sourceGen.resolveTargetPath(filePath, targetPath, suffix).unsafeRunSync()
|
||||
resolved.isValid shouldBe true
|
||||
|
||||
val targetFilePath = resolved.toOption.get
|
||||
@ -113,13 +114,13 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
|
||||
"AquaFileSources" should "write correct file with correct path" in {
|
||||
val path = Paths.get("cli/src/test/test-dir")
|
||||
val path = Path("cli/.jvm/src/test/test-dir")
|
||||
val filePath = path.resolve("imports/import.aqua")
|
||||
|
||||
val targetPath = path.resolve("target/")
|
||||
|
||||
// clean up
|
||||
val resultPath = Paths.get("cli/src/test/test-dir/target/imports/import_hey.custom")
|
||||
val resultPath = Path("cli/.jvm/src/test/test-dir/target/imports/import_hey.custom")
|
||||
Files[IO].deleteIfExists(resultPath).unsafeRunSync()
|
||||
|
||||
val sourceGen = new AquaFileSources[IO](path, Nil)
|
||||
@ -136,13 +137,13 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
|
||||
Files[IO].exists(resultPath).unsafeRunSync() shouldBe true
|
||||
|
||||
val resultText = Files[IO]
|
||||
.readAll(resultPath, 1000)
|
||||
.readAll(resultPath)
|
||||
.fold(
|
||||
Vector
|
||||
.empty[Byte]
|
||||
)((acc, b) => acc :+ b)
|
||||
.flatMap(fs2.Stream.emits)
|
||||
.through(text.utf8Decode)
|
||||
.through(text.utf8.decode)
|
||||
.attempt
|
||||
.compile
|
||||
.last
|
@ -8,14 +8,14 @@ import cats.effect.unsafe.implicits.global
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import java.nio.file.{Files, Paths}
|
||||
import fs2.io.file.{Files, Path}
|
||||
|
||||
class WriteFileSpec extends AnyFlatSpec with Matchers {
|
||||
"cli" should "compile aqua code in js" in {
|
||||
val src = Paths.get("./cli/src/test/aqua")
|
||||
val targetTs = Files.createTempDirectory("ts")
|
||||
val targetJs = Files.createTempDirectory("js")
|
||||
val targetAir = Files.createTempDirectory("air")
|
||||
val src = Path("./cli/.jvm/src/test/aqua")
|
||||
val targetTs = Files[IO].createTempDirectory.unsafeRunSync()
|
||||
val targetJs = Files[IO].createTempDirectory.unsafeRunSync()
|
||||
val targetAir = Files[IO].createTempDirectory.unsafeRunSync()
|
||||
|
||||
import aqua.files.AquaFilesIO.summon
|
||||
|
||||
@ -29,8 +29,8 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
.isValid should be(true)
|
||||
val targetTsFile = targetTs.resolve("test.ts")
|
||||
targetTsFile.toFile.exists() should be(true)
|
||||
Files.deleteIfExists(targetTsFile)
|
||||
Files[IO].exists(targetTsFile).unsafeRunSync() should be(true)
|
||||
Files[IO].deleteIfExists(targetTsFile).unsafeRunSync()
|
||||
|
||||
AquaPathCompiler
|
||||
.compileFilesTo[IO](src, List.empty, targetJs, JavaScriptBackend, bc)
|
||||
@ -41,8 +41,8 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
||||
}
|
||||
.isValid should be(true)
|
||||
val targetJsFile = targetJs.resolve("test.js")
|
||||
targetJsFile.toFile.exists() should be(true)
|
||||
Files.deleteIfExists(targetJsFile)
|
||||
Files[IO].exists(targetJsFile).unsafeRunSync() should be(true)
|
||||
Files[IO].deleteIfExists(targetJsFile).unsafeRunSync()
|
||||
|
||||
AquaPathCompiler
|
||||
.compileFilesTo[IO](src, List.empty, targetAir, AirBackend, bc)
|
||||
@ -55,11 +55,13 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
||||
val targetAirFileFirst = targetAir.resolve("test.first.air")
|
||||
val targetAirFileSecond = targetAir.resolve("test.second.air")
|
||||
val targetAirFileThird = targetAir.resolve("test.third.air")
|
||||
targetAirFileFirst.toFile.exists() should be(true)
|
||||
targetAirFileSecond.toFile.exists() should be(true)
|
||||
targetAirFileThird.toFile.exists() should be(true)
|
||||
Files[IO].exists(targetAirFileFirst).unsafeRunSync() should be(true)
|
||||
Files[IO].exists(targetAirFileSecond).unsafeRunSync() should be(true)
|
||||
Files[IO].exists(targetAirFileThird).unsafeRunSync() should be(true)
|
||||
|
||||
Seq(targetAirFileFirst, targetAirFileSecond, targetAirFileThird).map(Files.deleteIfExists)
|
||||
Seq(targetAirFileFirst, targetAirFileSecond, targetAirFileThird).map(f =>
|
||||
Files[IO].deleteIfExists(f).unsafeRunSync()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -3,7 +3,7 @@ package aqua
|
||||
import aqua.io.AquaFileError
|
||||
import cats.data.{Chain, EitherT, ValidatedNec}
|
||||
|
||||
import java.nio.file.Path
|
||||
import fs2.io.file.Path
|
||||
|
||||
trait AquaIO[F[_]] {
|
||||
def readFile(file: Path): EitherT[F, AquaFileError, String]
|
||||
|
@ -1,13 +0,0 @@
|
||||
package aqua
|
||||
|
||||
import wvlet.log.LogFormatter.{appendStackTrace, highlightLog}
|
||||
import wvlet.log.{LogFormatter, LogRecord}
|
||||
|
||||
object CustomLogFormatter extends LogFormatter {
|
||||
|
||||
override def formatLog(r: LogRecord): String = {
|
||||
val log =
|
||||
s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}"
|
||||
appendStackTrace(log, r)
|
||||
}
|
||||
}
|
7
cli/src/main/scala/aqua/LogFormatter.scala
Normal file
7
cli/src/main/scala/aqua/LogFormatter.scala
Normal file
@ -0,0 +1,7 @@
|
||||
package aqua
|
||||
|
||||
import scribe.format._
|
||||
|
||||
object LogFormatter {
|
||||
val formatter: Formatter = formatter"$date ${string("[")}$levelColored${string("]")} $message$mdc"
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
package aqua
|
||||
|
||||
import wvlet.log.{LogLevel => WLogLevel}
|
||||
import scribe.Level
|
||||
|
||||
object LogLevel {
|
||||
|
||||
val stringToLogLevel: Map[String, WLogLevel] = Map(
|
||||
("debug" -> WLogLevel.DEBUG),
|
||||
("trace" -> WLogLevel.TRACE),
|
||||
("info" -> WLogLevel.INFO),
|
||||
("off" -> WLogLevel.OFF),
|
||||
("warn" -> WLogLevel.WARN),
|
||||
("error" -> WLogLevel.ERROR),
|
||||
("all" -> WLogLevel.ALL)
|
||||
val stringToLogLevel: Map[String, Level] = Map(
|
||||
("debug" -> Level.Debug),
|
||||
("trace" -> Level.Trace),
|
||||
("info" -> Level.Info),
|
||||
("off" -> Level.Fatal),
|
||||
("warn" -> Level.Warn),
|
||||
("error" -> Level.Error),
|
||||
("all" -> Level.Trace)
|
||||
)
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
package aqua.files
|
||||
|
||||
import aqua.AquaIO
|
||||
import aqua.io._
|
||||
import aqua.io.*
|
||||
import cats.data.*
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data._
|
||||
import cats.effect.kernel.Concurrent
|
||||
import cats.syntax.applicative._
|
||||
import cats.syntax.apply._
|
||||
import cats.syntax.either._
|
||||
import cats.syntax.flatMap._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.applicativeError._
|
||||
import fs2.io.file.Files
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.applicativeError.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.either.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.foldable.*
|
||||
import fs2.io.file.{Files, Path}
|
||||
import fs2.text
|
||||
|
||||
import java.nio.file.Path
|
||||
import scala.util.Try
|
||||
|
||||
class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
|
||||
@ -22,13 +23,13 @@ class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
|
||||
override def readFile(file: Path): EitherT[F, AquaFileError, String] =
|
||||
EitherT(
|
||||
Files[F]
|
||||
.readAll(file, 4096)
|
||||
.readAll(file)
|
||||
.fold(Vector.empty[Byte])((acc, b) => acc :+ b)
|
||||
// TODO fix for comment on last line in air
|
||||
// TODO should be fixed by parser
|
||||
.map(_.appendedAll("\n\r".getBytes))
|
||||
.flatMap(fs2.Stream.emits)
|
||||
.through(text.utf8Decode)
|
||||
.through(text.utf8.decode)
|
||||
.attempt
|
||||
.compile
|
||||
.last
|
||||
@ -49,14 +50,14 @@ class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
|
||||
): EitherT[F, AquaFileError, Path] =
|
||||
in.headOption.fold(notFound)(p =>
|
||||
EitherT(
|
||||
Concurrent[F].attempt(p.toFile.isFile.pure[F])
|
||||
Concurrent[F].attempt(Files[F].isRegularFile(p))
|
||||
)
|
||||
.leftMap[AquaFileError](FileSystemError.apply)
|
||||
.recover({ case _ => false })
|
||||
.flatMap {
|
||||
case true =>
|
||||
EitherT(
|
||||
Concurrent[F].attempt(p.toAbsolutePath.normalize().pure[F])
|
||||
Concurrent[F].attempt(p.absolute.normalize.pure[F])
|
||||
).leftMap[AquaFileError](FileSystemError.apply)
|
||||
case false =>
|
||||
findFirstF(in.tail, notFound)
|
||||
@ -76,56 +77,72 @@ class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
|
||||
EitherT.leftT(FileNotFound(src, imports))
|
||||
)
|
||||
|
||||
override def listAqua(folder: Path): F[ValidatedNec[AquaFileError, Chain[Path]]] =
|
||||
Validated
|
||||
.fromEither(
|
||||
Try {
|
||||
val f = folder.toFile
|
||||
if (!f.exists()) {
|
||||
Left(FileNotFound(folder, Nil))
|
||||
} else if (f.isDirectory) {
|
||||
Right(f.listFiles().toList)
|
||||
} else {
|
||||
Right(f :: Nil)
|
||||
// Get all files for every path if the path in the list is a directory or this path otherwise
|
||||
private def gatherFiles(files: List[Path], listFunction: (f: Path) => F[ValidatedNec[AquaFileError, Chain[Path]]]): List[F[ValidatedNec[AquaFileError, Chain[Path]]]] = {
|
||||
files.map(f => gatherFile(f, listFunction))
|
||||
}
|
||||
}.toEither.leftMap[AquaFileError](FileSystemError.apply).flatMap(identity)
|
||||
)
|
||||
.leftMap(NonEmptyChain.one)
|
||||
.pure[F]
|
||||
|
||||
// Get all files if the path is a directory or this path otherwise
|
||||
private def gatherFile(f: Path, listFunction: (f: Path) => F[ValidatedNec[AquaFileError, Chain[Path]]]): F[ValidatedNec[AquaFileError, Chain[Path]]] = {
|
||||
Files[F].isDirectory(f).flatMap { isDir =>
|
||||
if (isDir)
|
||||
listFunction(f)
|
||||
else
|
||||
Files[F].isRegularFile(f).map { isFile =>
|
||||
if (isFile)
|
||||
Validated.validNec(Chain.one(f.absolute.normalize))
|
||||
else
|
||||
Validated.invalidNec(FileNotFound(f, Nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all files if the path is a directory or this path otherwise
|
||||
override def listAqua(folder: Path): F[ValidatedNec[AquaFileError, Chain[Path]]] = {
|
||||
Files[F]
|
||||
.exists(folder)
|
||||
.flatMap { exists =>
|
||||
if (!exists) {
|
||||
Left(FileNotFound(folder, Nil): AquaFileError).pure[F]
|
||||
} else {
|
||||
Files[F].isDirectory(folder).flatMap { isDir =>
|
||||
if (isDir) {
|
||||
Files[F].list(folder).compile.toList.map(Right(_))
|
||||
} else {
|
||||
Right(folder :: Nil).pure[F]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.map(Validated.fromEither)
|
||||
.map(_.leftMap(NonEmptyChain.one))
|
||||
.flatMap {
|
||||
case Valid(files) =>
|
||||
files.collect {
|
||||
case f if f.isFile && f.getName.endsWith(".aqua") =>
|
||||
Validated
|
||||
.fromTry(
|
||||
Try(Chain.one(f.toPath.toAbsolutePath.normalize()))
|
||||
)
|
||||
.leftMap(FileSystemError.apply)
|
||||
.leftMap(NonEmptyChain.one)
|
||||
.pure[F]
|
||||
case f if f.isDirectory =>
|
||||
listAqua(f.toPath)
|
||||
}.foldLeft(Validated.validNec[AquaFileError, Chain[Path]](Chain.nil).pure[F]) {
|
||||
case (acc, v) =>
|
||||
gatherFiles(files, listAqua).foldLeft(
|
||||
Validated.validNec[AquaFileError, Chain[Path]](Chain.nil).pure[F]
|
||||
) { case (acc, v) =>
|
||||
(acc, v).mapN(_ combine _)
|
||||
}
|
||||
case Invalid(errs) =>
|
||||
Validated.invalid[NonEmptyChain[AquaFileError], Chain[Path]](errs).pure[F]
|
||||
}
|
||||
}
|
||||
|
||||
private def deleteIfExists(file: Path): EitherT[F, AquaFileError, Boolean] =
|
||||
Files[F].deleteIfExists(file).attemptT.leftMap(FileSystemError.apply)
|
||||
|
||||
private def createDirectories(path: Path): EitherT[F, AquaFileError, Path] =
|
||||
Files[F].createDirectories(path).attemptT.leftMap(FileSystemError.apply)
|
||||
private def createDirectories(path: Path): EitherT[F, AquaFileError, Unit] =
|
||||
Files[F].createDirectories(path).attemptT.leftMap(FileSystemError.apply(_): AquaFileError)
|
||||
|
||||
// Writes to a file, creates directories if they do not exist
|
||||
override def writeFile(file: Path, content: String): EitherT[F, AquaFileError, Unit] =
|
||||
deleteIfExists(file) >> createDirectories(file.getParent) >>
|
||||
deleteIfExists(file) >> file.parent
|
||||
.map(createDirectories)
|
||||
.getOrElse(EitherT.liftF(().pure[F])) >>
|
||||
EitherT(
|
||||
fs2.Stream
|
||||
.emit(content)
|
||||
.through(text.utf8Encode)
|
||||
.through(text.utf8.encode)
|
||||
.through(Files[F].writeAll(file))
|
||||
.attempt
|
||||
.compile
|
||||
|
@ -2,7 +2,7 @@ package aqua.io
|
||||
|
||||
import cats.data.NonEmptyChain
|
||||
|
||||
import java.nio.file.Path
|
||||
import fs2.io.file.Path
|
||||
|
||||
sealed trait AquaFileError {
|
||||
def showForConsole: String
|
||||
|
@ -3,11 +3,11 @@ package aqua.linker
|
||||
import cats.data.{NonEmptyChain, Validated, ValidatedNec}
|
||||
import cats.kernel.{Monoid, Semigroup}
|
||||
import cats.syntax.monoid._
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object Linker extends LogSupport {
|
||||
object Linker extends Logging {
|
||||
|
||||
@tailrec
|
||||
def iter[I, E, T: Semigroup](
|
||||
@ -20,22 +20,22 @@ object Linker extends LogSupport {
|
||||
Right(proc)
|
||||
case _ =>
|
||||
val (canHandle, postpone) = mods.partition(_.dependsOn.keySet.forall(proc.contains))
|
||||
debug("ITERATE, can handle: " + canHandle.map(_.id))
|
||||
debug(s"dependsOn = ${mods.map(_.dependsOn.keySet)}")
|
||||
debug(s"postpone = ${postpone.map(_.id)}")
|
||||
debug(s"proc = ${proc.keySet}")
|
||||
logger.debug("ITERATE, can handle: " + canHandle.map(_.id))
|
||||
logger.debug(s"dependsOn = ${mods.map(_.dependsOn.keySet)}")
|
||||
logger.debug(s"postpone = ${postpone.map(_.id)}")
|
||||
logger.debug(s"proc = ${proc.keySet}")
|
||||
|
||||
if (canHandle.isEmpty && postpone.nonEmpty)
|
||||
Left(cycleError(postpone))
|
||||
else {
|
||||
val folded = canHandle.foldLeft(proc) { case (acc, m) =>
|
||||
val importKeys = m.dependsOn.keySet
|
||||
debug(s"${m.id} dependsOn $importKeys")
|
||||
logger.debug(s"${m.id} dependsOn $importKeys")
|
||||
val deps: T => T =
|
||||
importKeys.map(acc).foldLeft[T => T](identity) { case (fAcc, f) =>
|
||||
debug("COMBINING ONE TIME ")
|
||||
logger.debug("COMBINING ONE TIME ")
|
||||
t => {
|
||||
debug(s"call combine $t")
|
||||
logger.debug(s"call combine $t")
|
||||
fAcc(t) |+| f(t)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import cats.Monoid
|
||||
import cats.data.NonEmptyMap
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.monoid.*
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
import scala.collection.immutable.SortedMap
|
||||
|
||||
@ -67,7 +67,7 @@ case class AquaContext(
|
||||
.map(StructType(name, _))
|
||||
}
|
||||
|
||||
object AquaContext extends LogSupport {
|
||||
object AquaContext extends Logging {
|
||||
|
||||
trait Implicits {
|
||||
implicit val aquaContextMonoid: Monoid[AquaContext]
|
||||
|
@ -3,7 +3,7 @@ package aqua.model
|
||||
import aqua.types._
|
||||
import cats.Eq
|
||||
import cats.data.{Chain, NonEmptyMap}
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
sealed trait ValueModel {
|
||||
def `type`: Type
|
||||
@ -46,7 +46,8 @@ case class IntoFieldModel(field: String, `type`: Type) extends LambdaModel
|
||||
case class IntoIndexModel(idx: Int, `type`: Type) extends LambdaModel
|
||||
|
||||
case class VarModel(name: String, `type`: Type, lambda: Chain[LambdaModel] = Chain.empty)
|
||||
extends ValueModel with LogSupport {
|
||||
extends ValueModel with Logging {
|
||||
|
||||
def deriveFrom(vm: VarModel): VarModel = vm.copy(lambda = vm.lambda ++ lambda)
|
||||
|
||||
override val lastType: Type = lambda.lastOption.map(_.`type`).getOrElse(`type`)
|
||||
@ -81,7 +82,7 @@ case class VarModel(name: String, `type`: Type, lambda: Chain[LambdaModel] = Cha
|
||||
deriveFrom(nvm)
|
||||
case valueModel =>
|
||||
if (lambda.nonEmpty)
|
||||
error(
|
||||
logger.error(
|
||||
s"Var $name derived from scalar $valueModel, but lambda is lost: $lambda"
|
||||
)
|
||||
valueModel
|
||||
|
@ -7,7 +7,7 @@ import aqua.types.{ArrowType, ProductType, StreamType, Type}
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
import wvlet.log.Logger
|
||||
import scribe.Logging
|
||||
|
||||
case class FuncCallable(
|
||||
funcName: String,
|
||||
@ -16,10 +16,7 @@ case class FuncCallable(
|
||||
ret: List[ValueModel],
|
||||
capturedArrows: Map[String, FuncCallable],
|
||||
capturedValues: Map[String, ValueModel]
|
||||
) extends Model {
|
||||
|
||||
private val logger = Logger.of[FuncCallable]
|
||||
import logger._
|
||||
) extends Model with Logging {
|
||||
|
||||
lazy val args: List[(String, Type)] = arrowType.domain.toLabelledList()
|
||||
lazy val argNames: List[String] = args.map(_._1)
|
||||
@ -48,7 +45,7 @@ case class FuncCallable(
|
||||
forbiddenNames: Set[String]
|
||||
): Eval[(FuncOp, List[ValueModel])] = {
|
||||
|
||||
debug("Call: " + call)
|
||||
logger.debug("Call: " + call)
|
||||
|
||||
// Collect all arguments: what names are used inside the function, what values are received
|
||||
val argsFull = ArgsCall(arrowType.domain, call.args)
|
||||
@ -140,7 +137,7 @@ case class FuncCallable(
|
||||
case (acc @ (_, resolvedExports), tag) =>
|
||||
tag match {
|
||||
case CallArrowTag(fn, _) if !allArrows.contains(fn) =>
|
||||
error(s"UNRESOLVED $fn in $funcName, skipping, will become (null) in AIR!")
|
||||
logger.error(s"UNRESOLVED $fn in $funcName, skipping, will become (null) in AIR!")
|
||||
case _ =>
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,11 @@ package aqua.model.func.raw
|
||||
import aqua.model.ValueModel
|
||||
import cats.data.Chain
|
||||
import cats.data.Chain.{:==, ==:, nil}
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object PathFinder extends LogSupport {
|
||||
object PathFinder extends Logging {
|
||||
|
||||
def find(from: RawCursor, to: RawCursor, isExit: Boolean = false): Chain[ValueModel] = {
|
||||
|
||||
@ -21,14 +21,14 @@ object PathFinder extends LogSupport {
|
||||
!to.parentTag.exists(_.isInstanceOf[ParGroupTag])
|
||||
|
||||
if (wasHandled) {
|
||||
debug("Was handled")
|
||||
debug(" :: " + from)
|
||||
debug(" -> " + to)
|
||||
logger.debug("Was handled")
|
||||
logger.debug(" :: " + from)
|
||||
logger.debug(" -> " + to)
|
||||
Chain.empty
|
||||
} else {
|
||||
debug("Find path")
|
||||
debug(" :: " + from)
|
||||
debug(" -> " + to)
|
||||
logger.debug("Find path")
|
||||
logger.debug(" :: " + from)
|
||||
logger.debug(" -> " + to)
|
||||
findPath(
|
||||
fromOn,
|
||||
toOn,
|
||||
@ -49,10 +49,10 @@ object PathFinder extends LogSupport {
|
||||
case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p
|
||||
case (acc, p) => acc :+ p
|
||||
}
|
||||
trace(s"PEER IDS: $optimized")
|
||||
trace(s"PREFIX: $prefix")
|
||||
trace(s"SUFFIX: $suffix")
|
||||
trace(s"OPTIMIZED WITH PREFIX AND SUFFIX: $optimized")
|
||||
logger.trace(s"PEER IDS: $optimized")
|
||||
logger.trace(s"PREFIX: $prefix")
|
||||
logger.trace(s"SUFFIX: $suffix")
|
||||
logger.trace(s"OPTIMIZED WITH PREFIX AND SUFFIX: $optimized")
|
||||
val noPrefix = skipPrefix(optimized, prefix, optimized)
|
||||
skipSuffix(noPrefix, suffix, noPrefix)
|
||||
}
|
||||
@ -63,28 +63,28 @@ object PathFinder extends LogSupport {
|
||||
fromPeer: Option[ValueModel],
|
||||
toPeer: Option[ValueModel]
|
||||
): Chain[ValueModel] = {
|
||||
trace(s"FROM ON: $fromOn")
|
||||
trace(s"TO ON: $toOn")
|
||||
logger.trace(s"FROM ON: $fromOn")
|
||||
logger.trace(s"TO ON: $toOn")
|
||||
|
||||
val (from, to) = skipCommonPrefix(fromOn, toOn)
|
||||
val fromFix =
|
||||
if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from
|
||||
val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to
|
||||
|
||||
trace("FIND PATH FROM | " + fromFix)
|
||||
trace(" TO | " + toFix)
|
||||
logger.trace("FIND PATH FROM | " + fromFix)
|
||||
logger.trace(" TO | " + toFix)
|
||||
|
||||
val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via)
|
||||
trace(s"FROM TO: $fromTo")
|
||||
logger.trace(s"FROM TO: $fromTo")
|
||||
|
||||
val fromPeerCh = Chain.fromOption(fromPeer)
|
||||
val toPeerCh = Chain.fromOption(toPeer)
|
||||
val optimized = optimizePath(fromPeerCh ++ fromTo ++ toPeerCh, fromPeerCh, toPeerCh)
|
||||
|
||||
trace(
|
||||
logger.trace(
|
||||
s"FROM PEER '${fromPeer.map(_.toString).getOrElse("None")}' TO PEER '${toPeer.map(_.toString).getOrElse("None")}'"
|
||||
)
|
||||
trace(" Optimized: " + optimized)
|
||||
logger.trace(" Optimized: " + optimized)
|
||||
optimized
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,12 @@ import cats.Eval
|
||||
import cats.data.{Chain, NonEmptyList, OptionT}
|
||||
import cats.free.Cofree
|
||||
import cats.syntax.traverse._
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
// Can be heavily optimized by caching parent cursors, not just list of zippers
|
||||
case class RawCursor(tree: NonEmptyList[ChainZipper[FuncOp.Tree]])
|
||||
extends ChainCursor[RawCursor, FuncOp.Tree](RawCursor.apply) with LogSupport {
|
||||
extends ChainCursor[RawCursor, FuncOp.Tree](RawCursor.apply) with Logging {
|
||||
|
||||
def tag: RawTag = current.head
|
||||
def parentTag: Option[RawTag] = parent.map(_.head)
|
||||
|
||||
|
@ -10,9 +10,9 @@ import cats.data.Chain.nil
|
||||
import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
|
||||
import cats.free.Cofree
|
||||
import cats.syntax.traverse._
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
object Topology extends LogSupport {
|
||||
object Topology extends Logging {
|
||||
type Tree = Cofree[Chain, RawTag]
|
||||
type Res = Cofree[Chain, ResolvedOp]
|
||||
|
||||
@ -50,7 +50,7 @@ object Topology extends LogSupport {
|
||||
val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
|
||||
val resolvedCofree = cursor
|
||||
.cata(wrap) { rc =>
|
||||
debug(s"<:> $rc")
|
||||
logger.debug(s"<:> $rc")
|
||||
val resolved = MakeRes
|
||||
.resolve(rc.currentPeerId)
|
||||
.lift
|
||||
@ -64,12 +64,12 @@ object Topology extends LogSupport {
|
||||
through(rc.pathToNext)
|
||||
)
|
||||
if (cz.next.nonEmpty || cz.prev.nonEmpty) {
|
||||
debug(s"Resolved $rc -> $cofree")
|
||||
logger.debug(s"Resolved $rc -> $cofree")
|
||||
if (cz.prev.nonEmpty)
|
||||
trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> "))
|
||||
logger.trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> "))
|
||||
if (cz.next.nonEmpty)
|
||||
trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
|
||||
} else debug(s"EMPTY $rc -> $cofree")
|
||||
logger.trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
|
||||
} else logger.debug(s"EMPTY $rc -> $cofree")
|
||||
cz
|
||||
}
|
||||
)
|
||||
@ -78,11 +78,11 @@ object Topology extends LogSupport {
|
||||
|
||||
resolvedCofree.map(NonEmptyChain.fromChain(_).map(_.uncons)).map {
|
||||
case None =>
|
||||
error("Topology emitted nothing")
|
||||
logger.error("Topology emitted nothing")
|
||||
Cofree(SeqRes, MakeRes.nilTail)
|
||||
case Some((el, `nil`)) => el
|
||||
case Some((el, tail)) =>
|
||||
warn("Topology emitted many nodes, that's unusual")
|
||||
logger.warn("Topology emitted many nodes, that's unusual")
|
||||
Cofree(SeqRes, Eval.now(el +: tail))
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import aqua.model.topology.Topology
|
||||
import aqua.types.ScalarType
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
object Transform extends LogSupport {
|
||||
object Transform extends Logging {
|
||||
|
||||
def defaultFilter(t: ResolvedOp): Boolean = t match {
|
||||
case _: NoAir => false
|
||||
|
@ -113,7 +113,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
| <- v
|
||||
|""".stripMargin
|
||||
|
||||
parser[Id]().parseAll(script).value.toEither shouldBe Symbol("left")
|
||||
parser[Id]().parseAll(script).value.toEither.isLeft shouldBe true
|
||||
}
|
||||
|
||||
"function with multiline definitions" should "parse without error" in {
|
||||
@ -121,7 +121,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
"""func tryGen(a: string,
|
||||
| b: string)""".stripMargin
|
||||
|
||||
FuncExpr.p[Id].parseAll(script) shouldBe Symbol("right")
|
||||
FuncExpr.p[Id].parseAll(script).isRight shouldBe true
|
||||
}
|
||||
|
||||
"function with root expression without children" should "parse with error" in {
|
||||
@ -131,7 +131,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
| <- v
|
||||
|""".stripMargin
|
||||
|
||||
parser[Id]().parseAll(script).value.toEither shouldBe Symbol("left")
|
||||
parser[Id]().parseAll(script).value.toEither.isLeft shouldBe true
|
||||
}
|
||||
|
||||
"multi function expression" should "parse" in {
|
||||
|
@ -1 +1,3 @@
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
|
||||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0")
|
||||
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0")
|
||||
|
@ -23,9 +23,9 @@ import cats.syntax.apply._
|
||||
import cats.syntax.semigroup._
|
||||
import monocle.Lens
|
||||
import monocle.macros.GenLens
|
||||
import wvlet.log.LogSupport
|
||||
import scribe.Logging
|
||||
|
||||
object Semantics extends LogSupport {
|
||||
object Semantics extends Logging {
|
||||
|
||||
def folder[F[_], G[_]](implicit
|
||||
A: AbilitiesAlgebra[F, G],
|
||||
|
Loading…
Reference in New Issue
Block a user