Crossprojects for JVM and JS platforms (#237)

This commit is contained in:
Dima 2021-08-11 19:53:36 +03:00 committed by GitHub
parent 50aa4db4e8
commit e2da2e90d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 454 additions and 327 deletions

6
.jvmopts Normal file
View File

@ -0,0 +1,6 @@
-Dfile.encoding=UTF8
-Xms1G
-Xmx5G
-XX:ReservedCodeCacheSize=500M
-XX:+TieredCompilation
-XX:+UseParallelGC

View File

@ -0,0 +1,7 @@
package aqua.backend
object Version {
// TODO: get version for JS compiler
lazy val version = "Unknown (JS)"
}

View File

@ -0,0 +1,8 @@
package aqua.backend
object Version {
lazy val version = Option(getClass.getPackage.getImplementationVersion)
.filter(_.nonEmpty)
.getOrElse("Unknown")
}

View File

@ -7,14 +7,14 @@ import aqua.types.StreamType
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
import wvlet.log.LogSupport import scribe.Logging
sealed trait AirGen { sealed trait AirGen {
def generate: Air def generate: Air
} }
object AirGen extends LogSupport { object AirGen extends Logging {
def lambdaToString(ls: List[LambdaModel]): String = ls match { def lambdaToString(ls: List[LambdaModel]): String = ls match {
case Nil => "" case Nil => ""
@ -54,7 +54,7 @@ object AirGen extends LogSupport {
case o :: Nil => ParGen(o, NullGen) case o :: Nil => ParGen(o, NullGen)
case _ => case _ =>
ops.toList.reduceLeftOption(ParGen(_, _)).getOrElse { ops.toList.reduceLeftOption(ParGen(_, _)).getOrElse {
warn("ParRes with no children converted to Null") logger.warn("ParRes with no children converted to Null")
NullGen NullGen
} }
}) })
@ -63,7 +63,7 @@ object AirGen extends LogSupport {
case o :: Nil => XorGen(o, NullGen) case o :: Nil => XorGen(o, NullGen)
case _ => case _ =>
ops.toList.reduceLeftOption(XorGen(_, _)).getOrElse { ops.toList.reduceLeftOption(XorGen(_, _)).getOrElse {
warn("XorRes with no children converted to Null") logger.warn("XorRes with no children converted to Null")
NullGen NullGen
} }
}) })

View File

@ -1,5 +1,6 @@
package aqua.backend.js package aqua.backend.js
import aqua.backend.Version
import aqua.model.AquaContext import aqua.model.AquaContext
import aqua.model.transform.GenerationConfig import aqua.model.transform.GenerationConfig
import cats.data.Chain import cats.data.Chain
@ -21,9 +22,7 @@ object JavaScriptFile {
| * This file is auto-generated. Do not edit manually: changes may be erased. | * This file is auto-generated. Do not edit manually: changes may be erased.
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/. | * 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 | * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
| * Aqua version: ${Option(getClass.getPackage.getImplementationVersion) | * Aqua version: ${Version.version}
.filter(_.nonEmpty)
.getOrElse("Unknown")}
| * | *
| */ | */
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable'; |import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';

View File

@ -1,5 +1,6 @@
package aqua.backend.ts package aqua.backend.ts
import aqua.backend.Version
import aqua.model.AquaContext import aqua.model.AquaContext
import aqua.model.transform.GenerationConfig import aqua.model.transform.GenerationConfig
import cats.data.Chain import cats.data.Chain
@ -21,9 +22,7 @@ object TypeScriptFile {
| * This file is auto-generated. Do not edit manually: changes may be erased. | * This file is auto-generated. Do not edit manually: changes may be erased.
| * Generated by Aqua compiler: https://github.com/fluencelabs/aqua/. | * 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 | * If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
| * Aqua version: ${Option(getClass.getPackage.getImplementationVersion) | * Aqua version: ${Version.version}
.filter(_.nonEmpty)
.getOrElse("Unknown")}
| * | *
| */ | */
|import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence'; |import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';

103
build.sbt
View File

@ -8,19 +8,12 @@ val catsV = "2.6.1"
val catsParseV = "0.3.4" val catsParseV = "0.3.4"
val monocleV = "3.0.0-M6" val monocleV = "3.0.0-M6"
val scalaTestV = "3.2.9" val scalaTestV = "3.2.9"
val fs2V = "3.0.6" val fs2V = "3.1.0"
val catsEffectV = "3.2.1" val catsEffectV = "3.2.1"
val airframeLogV = "21.5.4"
val log4catsV = "2.1.1" val log4catsV = "2.1.1"
val slf4jV = "1.7.30" val slf4jV = "1.7.30"
val declineV = "2.1.0" 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" name := "aqua-hll"
val commons = Seq( val commons = Seq(
@ -28,9 +21,8 @@ val commons = Seq(
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion, scalaVersion := dottyVersion,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.typelevel" %% "log4cats-core" % log4catsV, "com.outr" %%% "scribe" % "3.5.5",
airframeLog, "org.scalatest" %%% "scalatest" % scalaTestV % Test
"org.scalatest" %% "scalatest" % scalaTestV % Test
), ),
scalacOptions ++= { scalacOptions ++= {
Seq( Seq(
@ -47,95 +39,124 @@ val commons = Seq(
commons commons
lazy val cli = project lazy val cli = crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("cli"))
.settings(commons: _*) .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( .settings(
Compile / run / mainClass := Some("aqua.AquaCli"), Compile / run / mainClass := Some("aqua.AquaCli"),
assembly / mainClass := Some("aqua.AquaCli"), assembly / mainClass := Some("aqua.AquaCli"),
assembly / assemblyJarName := "aqua-cli-" + version.value + ".jar", assembly / assemblyJarName := "aqua-cli-" + version.value + ".jar",
libraryDependencies ++= Seq( 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(commons)
.settings( .settings(
libraryDependencies ++= Seq( 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(commons: _*)
.settings( .settings(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.typelevel" %% "cats-parse" % catsParseV, "org.typelevel" %%% "cats-parse" % catsParseV,
catsFree "org.typelevel" %%% "cats-free" % catsV
) )
) )
.dependsOn(types) .dependsOn(types)
lazy val linker = project lazy val linker = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.settings(commons: _*) .settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
airframeLog
)
)
.dependsOn(parser) .dependsOn(parser)
lazy val model = project lazy val model = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.settings(commons: _*) .settings(commons: _*)
.settings( .settings(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
catsFree "org.typelevel" %%% "cats-free" % catsV
) )
) )
.dependsOn(types) .dependsOn(types)
lazy val `test-kit` = project lazy val `test-kit` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("model/test-kit")) .in(file("model/test-kit"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(model) .dependsOn(model)
lazy val semantics = project lazy val semantics = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.settings(commons: _*) .settings(commons: _*)
.settings( .settings(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.github.julien-truffaut" %% "monocle-core" % monocleV, "com.github.julien-truffaut" %%% "monocle-core" % monocleV,
"com.github.julien-truffaut" %% "monocle-macro" % monocleV "com.github.julien-truffaut" %%% "monocle-macro" % monocleV
) )
) )
.dependsOn(model, `test-kit` % Test, parser) .dependsOn(model, `test-kit` % Test, parser)
lazy val compiler = project lazy val compiler = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("compiler")) .in(file("compiler"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(semantics, linker, backend) .dependsOn(semantics, linker, backend)
lazy val backend = project lazy val backend = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend")) .in(file("backend"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(model) .dependsOn(model)
lazy val `backend-air` = project lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend/air")) .in(file("backend/air"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(backend) .dependsOn(backend)
lazy val `backend-ts` = project lazy val `backend-ts` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend/ts")) .in(file("backend/ts"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(`backend-air`) .dependsOn(`backend-air`)
lazy val `backend-js` = project lazy val `backend-js` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend/js")) .in(file("backend/js"))
.settings(commons: _*) .settings(commons: _*)
.dependsOn(`backend-air`) .dependsOn(`backend-air`)

View 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!")
}
}

View File

@ -6,16 +6,15 @@ import aqua.parser.expr.ConstantExpr
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
import cats.data.Validated.{Invalid, Valid} import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyList, Validated, ValidatedNel} import cats.data.{NonEmptyList, Validated, ValidatedNel}
import cats.effect.ExitCode import cats.effect.{unsafe, ExitCode, IO}
import cats.effect.std.Console import cats.effect.std.Console
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.{Comonad, Functor} import cats.{Comonad, Functor}
import com.monovore.decline.Opts.help import com.monovore.decline.Opts.help
import com.monovore.decline.{Opts, Visibility} import com.monovore.decline.{Opts, Visibility}
import wvlet.log.{LogLevel => WLogLevel} import scribe.Level
import fs2.io.file.{Files, Path}
import java.nio.file.Path
object AppOps { object AppOps {
@ -25,13 +24,13 @@ object AppOps {
val versionOpt: Opts[Unit] = val versionOpt: Opts[Unit] =
Opts.flag("version", help = "Show version", "v", Visibility.Partial) 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 { Opts.option[String]("log-level", help = "Set log level").withDefault("info").mapValidated {
str => str =>
Validated.fromEither(toLogLevel(str)) Validated.fromEither(toLogLevel(str))
} }
def toLogLevel(logLevel: String): Either[NonEmptyList[String], WLogLevel] = { def toLogLevel(logLevel: String): Either[NonEmptyList[String], Level] = {
LogLevel.stringToLogLevel LogLevel.stringToLogLevel
.get(logLevel.toLowerCase) .get(logLevel.toLowerCase)
.toRight( .toRight(
@ -42,56 +41,68 @@ object AppOps {
) )
} }
def checkPath: Path => ValidatedNel[String, Path] = { p => def checkPath(implicit runtime: unsafe.IORuntime): String => ValidatedNel[String, Path] = {
Validated pathStr =>
.fromEither(Validated.catchNonFatal { Validated
val f = p.toFile .fromEither(Validated.catchNonFatal {
if (f.exists()) { val p = Path(pathStr)
if (f.isFile) { Files[IO]
val filename = f.getName .exists(p)
val ext = Option(filename) .flatMap { exists =>
.filter(_.contains(".")) if (exists)
.map(f => f.substring(f.lastIndexOf(".") + 1)) Files[IO].isRegularFile(p).map { isFile =>
.getOrElse("") if (isFile) {
if (ext != "aqua") Left("File must be with 'aqua' extension") val filename = p.fileName.toString
else Right(p) val ext = Option(filename)
} else Right(p) .filter(_.contains("."))
} else { .map(f => f.substring(f.lastIndexOf(".") + 1))
Left(s"There is no path '${p.toString}'") .getOrElse("")
} if (ext != "aqua") Left("File must be with 'aqua' extension")
}.toEither.left.map(t => s"An error occurred on imports reading: ${t.getMessage}").flatten) else Right(p)
.toValidatedNel } 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 Opts
.option[Path]( .option[String](
"input", "input",
"Path to an aqua file or an input directory that contains your .aqua files", "Path to an aqua file or an input directory that contains your .aqua files",
"i" "i"
) )
.mapValidated(checkPath) .mapValidated(checkPath)
val outputOpts: Opts[Path] = def outputOpts(implicit runtime: unsafe.IORuntime): Opts[Path] =
Opts.option[Path]("output", "Path to the output directory", "o").mapValidated(checkPath) 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 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 => .mapValidated { ps =>
val checked = ps val checked = ps
.map(p => { .map(pStr => {
Validated.catchNonFatal { Validated.catchNonFatal {
val f = p.toFile val p = Path(pStr)
if (f.exists() && f.isDirectory) { (for {
Right(p) exists <- Files[IO].exists(p)
} else { isDir <- Files[IO].isDirectory(p)
Left(s"There is no path ${p.toString} or it is not a directory") } 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 .toList
checked.map { checked.map {
case Validated.Valid(pE) => case Validated.Valid(pE) =>
pE match { pE match {

View File

@ -9,18 +9,16 @@ import aqua.model.transform.GenerationConfig
import aqua.parser.lift.LiftParser.Implicits.idLiftParser import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.Id import cats.Id
import cats.data.Validated import cats.data.Validated
import cats.effect._ import cats.effect.*
import cats.effect.std.{Console => ConsoleEff} import cats.effect.std.Console as ConsoleEff
import cats.syntax.apply._ import cats.syntax.apply.*
import cats.syntax.functor._ import cats.syntax.functor.*
import com.monovore.decline.Opts import com.monovore.decline.Opts
import com.monovore.decline.effect.CommandIOApp import com.monovore.decline.effect.CommandIOApp
import fs2.io.file.Files import fs2.io.file.Files
import org.typelevel.log4cats.slf4j.Slf4jLogger import scribe.Logging
import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger}
import wvlet.log.{LogSupport, Logger => WLogger}
object AquaCli extends IOApp with LogSupport { object AquaCli extends IOApp with Logging {
import AppOps._ import AppOps._
sealed trait CompileTarget 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 versionOpt
.as( .as(
versionAndExit versionAndExit
@ -59,8 +58,11 @@ object AquaCli extends IOApp with LogSupport {
constantOpts[Id] constantOpts[Id]
).mapN { ).mapN {
case (input, imports, output, toAir, toJs, noRelay, noXor, h, v, logLevel, constants) => case (input, imports, output, toAir, toJs, noRelay, noXor, h, v, logLevel, constants) =>
WLogger.setDefaultLogLevel(logLevel) scribe.Logger.root
WLogger.setDefaultFormatter(CustomLogFormatter) .clearHandlers()
.clearModifiers()
.withHandler(formatter = LogFormatter.formatter, minimumLevel = Some(logLevel))
.replace()
implicit val aio: AquaIO[F] = new AquaFilesIO[F] 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) val bc = GenerationConfig(wrapWithXor = !noXor, constants = constants)
bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay)) bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay))
} }
info(s"Aqua Compiler ${versionStr}") logger.info(s"Aqua Compiler ${versionStr}")
AquaPathCompiler AquaPathCompiler
.compileFilesTo[F]( .compileFilesTo[F](
input, input,
@ -89,7 +91,7 @@ object AquaCli extends IOApp with LogSupport {
errs.map(System.out.println) errs.map(System.out.println)
ExitCode.Error ExitCode.Error
case Validated.Valid(results) => case Validated.Valid(results) =>
results.map(info(_)) results.map(logger.info(_))
ExitCode.Success ExitCode.Success
} }
} }
@ -97,17 +99,13 @@ object AquaCli extends IOApp with LogSupport {
} }
override def run(args: List[String]): IO[ExitCode] = { override def run(args: List[String]): IO[ExitCode] = {
implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
Slf4jLogger.getLogger[F]
CommandIOApp.run[IO]( CommandIOApp.run[IO](
"aqua-c", "aqua-c",
"Aquamarine compiler", "Aquamarine compiler",
helpFlag = false, helpFlag = false,
None None
)( )(
main[IO], main[IO](runtime),
// Weird ugly hack: in case version flag or help flag is present, ignore other options, // Weird ugly hack: in case version flag or help flag is present, ignore other options,
// be it correct or not // be it correct or not
args match { args match {

View File

@ -3,20 +3,19 @@ package aqua
import aqua.backend.Backend import aqua.backend.Backend
import aqua.compiler.{AquaCompiler, AquaError} import aqua.compiler.{AquaCompiler, AquaError}
import aqua.files.{AquaFileSources, FileModuleId} import aqua.files.{AquaFileSources, FileModuleId}
import aqua.io._ import aqua.io.*
import aqua.model.transform.GenerationConfig import aqua.model.transform.GenerationConfig
import aqua.parser.lift.FileSpan import aqua.parser.lift.FileSpan
import cats.data._ import cats.data.*
import cats.syntax.functor._ import cats.syntax.functor.*
import cats.syntax.show._ import cats.syntax.show.*
import cats.{Monad, 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: Files](
def compileFilesTo[F[_]: AquaIO: Monad](
srcPath: Path, srcPath: Path,
imports: List[Path], imports: List[Path],
targetPath: Path, targetPath: Path,

View File

@ -53,7 +53,7 @@ object ErrorRendering {
val span = token.unit._1 val span = token.unit._1
showForConsole(span, s"Cannot resolve import") showForConsole(span, s"Cannot resolve import")
case CycleError(modules) => 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) => case CompileError(err) =>
err match { err match {
case RulesViolated(token, message) => case RulesViolated(token, message) =>

View File

@ -5,24 +5,19 @@ import aqua.files.AquaFilesIO
import aqua.model.transform.GenerationConfig import aqua.model.transform.GenerationConfig
import cats.data.Validated import cats.data.Validated
import cats.effect.{IO, IOApp, Sync} 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 { object Test extends IOApp.Simple {
implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
Slf4jLogger.getLogger[F]
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO] implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
override def run: IO[Unit] = override def run: IO[Unit] =
AquaPathCompiler AquaPathCompiler
.compileFilesTo[IO]( .compileFilesTo[IO](
Paths.get("./aqua-src"), Path("./aqua-src"),
List(Paths.get("./aqua")), List(Path("./aqua")),
Paths.get("./target"), Path("./target"),
TypeScriptBackend, TypeScriptBackend,
GenerationConfig() GenerationConfig()
) )

View File

@ -3,19 +3,22 @@ package aqua.files
import aqua.AquaIO import aqua.AquaIO
import aqua.compiler.{AquaCompiled, AquaSources} import aqua.compiler.{AquaCompiled, AquaSources}
import aqua.io.{AquaFileError, FileSystemError, ListAquaErrors} import aqua.io.{AquaFileError, FileSystemError, ListAquaErrors}
import cats.Monad import cats.{Functor, Monad}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.implicits.catsSyntaxApplicativeId import cats.implicits.catsSyntaxApplicativeId
import cats.syntax.either._ import cats.syntax.either.*
import cats.syntax.flatMap._ import cats.syntax.flatMap.*
import cats.syntax.functor._ import cats.syntax.functor.*
import cats.syntax.traverse._ import cats.syntax.monad.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import java.nio.file.{Path, Paths}
import scala.util.Try import scala.util.Try
class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[Path]) class AquaFileSources[F[_]: AquaIO: Monad: Files: Functor](
extends AquaSources[F, AquaFileError, FileModuleId] { sourcesPath: Path,
importFrom: List[Path]
) extends AquaSources[F, AquaFileError, FileModuleId] {
private val filesIO = implicitly[AquaIO[F]] private val filesIO = implicitly[AquaIO[F]]
override def sources: F[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] = 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, from: FileModuleId,
imp: String imp: String
): F[ValidatedNec[AquaFileError, FileModuleId]] = { ): 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) => case Validated.Valid(importP) =>
filesIO filesIO
.resolve(importP, from.file.getParent +: importFrom) .resolve(importP, importFrom.prependedAll(from.file.parent))
.bimap(NonEmptyChain.one, FileModuleId(_)) .bimap(NonEmptyChain.one, FileModuleId(_))
.value .value
.map(Validated.fromEither) .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]] = override def load(file: FileModuleId): F[ValidatedNec[AquaFileError, String]] =
filesIO.readFile(file.file).leftMap(NonEmptyChain.one).value.map(Validated.fromEither) 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 srcFile aqua source
* @param targetPath a main path where all output files will be written * @param targetPath a main path where all output files will be written
@ -74,23 +87,36 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
srcFile: Path, srcFile: Path,
targetPath: Path, targetPath: Path,
suffix: String suffix: String
): Validated[Throwable, Path] = ): F[Validated[Throwable, Path]] =
Validated.catchNonFatal { getDir(sourcesPath).map { srcDir =>
val srcDir = if (sourcesPath.toFile.isDirectory) sourcesPath else sourcesPath.getParent Validated.catchNonFatal {
val srcFilePath = srcDir.toAbsolutePath val srcFilePath = srcDir.absolute.normalize
.normalize() .relativize(srcFile.absolute.normalize)
.relativize(srcFile.toAbsolutePath.normalize())
val targetDir = val targetDir =
targetPath.toAbsolutePath targetPath.absolute.normalize
.normalize() .resolve(
.resolve( srcFilePath
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( def write(
targetPath: Path targetPath: Path
)(ac: AquaCompiled[FileModuleId]): F[Seq[Validated[AquaFileError, String]]] = )(ac: AquaCompiled[FileModuleId]): F[Seq[Validated[AquaFileError, String]]] =
@ -106,19 +132,14 @@ class AquaFileSources[F[_]: AquaIO: Monad](sourcesPath: Path, importFrom: List[P
ac.sourceId.file, ac.sourceId.file,
targetPath, targetPath,
compiled.suffix compiled.suffix
).leftMap(FileSystemError.apply) ).flatMap { result =>
.map { target => result
filesIO .leftMap(FileSystemError.apply)
.writeFile( .map { target =>
target, writeWithResult(target, compiled.content, ac.compiled.size)
compiled.content }
) .traverse(identity)
.as(s"Result $target: compilation OK (${ac.compiled.size} functions)") }
.value
.map(Validated.fromEither)
}
// TODO: we use both EitherT and F[Validated] to handle errors, that's why so weird
.traverse(identity)
}.traverse(identity) }.traverse(identity)
.map(_.map(_.andThen(identity))) .map(_.map(_.andThen(identity)))
} }

View File

@ -1,11 +1,11 @@
package aqua.files package aqua.files
import java.nio.file.Path import fs2.io.file.Path
case class FileModuleId private (file: Path) case class FileModuleId private (file: Path)
object FileModuleId { object FileModuleId {
def apply(file: Path): FileModuleId = def apply(file: Path): FileModuleId =
new FileModuleId(file.toAbsolutePath.normalize()) new FileModuleId(file.absolute.normalize)
} }

View File

@ -5,18 +5,16 @@ import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import cats.data.Chain import cats.data.Chain
import cats.effect.IO import cats.effect.IO
import cats.effect.unsafe.implicits.global import cats.effect.unsafe.implicits.global
import fs2.io.file.Files
import fs2.text import fs2.text
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import fs2.io.file.{Files, Path}
import java.nio.file.Paths
class SourcesSpec extends AnyFlatSpec with Matchers { class SourcesSpec extends AnyFlatSpec with Matchers {
implicit val aquaIO: AquaIO[IO] = AquaFilesIO.summon[IO] implicit val aquaIO: AquaIO[IO] = AquaFilesIO.summon[IO]
"AquaFileSources" should "generate correct fileId with imports" in { "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 importPath = path.resolve("imports")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil) 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 { "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) 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 { "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 importPath = path.resolve("random/import/path")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil) val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
@ -61,7 +59,7 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
} }
"AquaFileSources" should "find correct imports" in { "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 importPath = srcPath.resolve("imports")
val sourceGen = new AquaFileSources[IO](srcPath, importPath :: Nil) val sourceGen = new AquaFileSources[IO](srcPath, importPath :: Nil)
@ -73,7 +71,8 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
.unsafeRunSync() .unsafeRunSync()
result.isValid shouldBe true 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 // should be found near src file
val result2 = val result2 =
@ -82,7 +81,8 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
.unsafeRunSync() .unsafeRunSync()
result2.isValid shouldBe true 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 // near src file but in another directory
val sourceGen2 = new AquaFileSources[IO](srcPath, Nil) val sourceGen2 = new AquaFileSources[IO](srcPath, Nil)
@ -92,20 +92,21 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
.unsafeRunSync() .unsafeRunSync()
result3.isValid shouldBe true 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 { "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 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 sourceGen = new AquaFileSources[IO](path, Nil)
val suffix = "_custom.super" val suffix = "_custom.super"
val resolved = sourceGen.resolveTargetPath(filePath, targetPath, suffix) val resolved = sourceGen.resolveTargetPath(filePath, targetPath, suffix).unsafeRunSync()
resolved.isValid shouldBe true resolved.isValid shouldBe true
val targetFilePath = resolved.toOption.get val targetFilePath = resolved.toOption.get
@ -113,13 +114,13 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
} }
"AquaFileSources" should "write correct file with correct path" in { "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 filePath = path.resolve("imports/import.aqua")
val targetPath = path.resolve("target/") val targetPath = path.resolve("target/")
// clean up // 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() Files[IO].deleteIfExists(resultPath).unsafeRunSync()
val sourceGen = new AquaFileSources[IO](path, Nil) val sourceGen = new AquaFileSources[IO](path, Nil)
@ -136,13 +137,13 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
Files[IO].exists(resultPath).unsafeRunSync() shouldBe true Files[IO].exists(resultPath).unsafeRunSync() shouldBe true
val resultText = Files[IO] val resultText = Files[IO]
.readAll(resultPath, 1000) .readAll(resultPath)
.fold( .fold(
Vector Vector
.empty[Byte] .empty[Byte]
)((acc, b) => acc :+ b) )((acc, b) => acc :+ b)
.flatMap(fs2.Stream.emits) .flatMap(fs2.Stream.emits)
.through(text.utf8Decode) .through(text.utf8.decode)
.attempt .attempt
.compile .compile
.last .last

View File

@ -8,14 +8,14 @@ import cats.effect.unsafe.implicits.global
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import java.nio.file.{Files, Paths} import fs2.io.file.{Files, Path}
class WriteFileSpec extends AnyFlatSpec with Matchers { class WriteFileSpec extends AnyFlatSpec with Matchers {
"cli" should "compile aqua code in js" in { "cli" should "compile aqua code in js" in {
val src = Paths.get("./cli/src/test/aqua") val src = Path("./cli/.jvm/src/test/aqua")
val targetTs = Files.createTempDirectory("ts") val targetTs = Files[IO].createTempDirectory.unsafeRunSync()
val targetJs = Files.createTempDirectory("js") val targetJs = Files[IO].createTempDirectory.unsafeRunSync()
val targetAir = Files.createTempDirectory("air") val targetAir = Files[IO].createTempDirectory.unsafeRunSync()
import aqua.files.AquaFilesIO.summon import aqua.files.AquaFilesIO.summon
@ -29,8 +29,8 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
} }
.isValid should be(true) .isValid should be(true)
val targetTsFile = targetTs.resolve("test.ts") val targetTsFile = targetTs.resolve("test.ts")
targetTsFile.toFile.exists() should be(true) Files[IO].exists(targetTsFile).unsafeRunSync() should be(true)
Files.deleteIfExists(targetTsFile) Files[IO].deleteIfExists(targetTsFile).unsafeRunSync()
AquaPathCompiler AquaPathCompiler
.compileFilesTo[IO](src, List.empty, targetJs, JavaScriptBackend, bc) .compileFilesTo[IO](src, List.empty, targetJs, JavaScriptBackend, bc)
@ -41,8 +41,8 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
} }
.isValid should be(true) .isValid should be(true)
val targetJsFile = targetJs.resolve("test.js") val targetJsFile = targetJs.resolve("test.js")
targetJsFile.toFile.exists() should be(true) Files[IO].exists(targetJsFile).unsafeRunSync() should be(true)
Files.deleteIfExists(targetJsFile) Files[IO].deleteIfExists(targetJsFile).unsafeRunSync()
AquaPathCompiler AquaPathCompiler
.compileFilesTo[IO](src, List.empty, targetAir, AirBackend, bc) .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 targetAirFileFirst = targetAir.resolve("test.first.air")
val targetAirFileSecond = targetAir.resolve("test.second.air") val targetAirFileSecond = targetAir.resolve("test.second.air")
val targetAirFileThird = targetAir.resolve("test.third.air") val targetAirFileThird = targetAir.resolve("test.third.air")
targetAirFileFirst.toFile.exists() should be(true) Files[IO].exists(targetAirFileFirst).unsafeRunSync() should be(true)
targetAirFileSecond.toFile.exists() should be(true) Files[IO].exists(targetAirFileSecond).unsafeRunSync() should be(true)
targetAirFileThird.toFile.exists() 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()
)
} }
} }

View File

@ -3,7 +3,7 @@ package aqua
import aqua.io.AquaFileError import aqua.io.AquaFileError
import cats.data.{Chain, EitherT, ValidatedNec} import cats.data.{Chain, EitherT, ValidatedNec}
import java.nio.file.Path import fs2.io.file.Path
trait AquaIO[F[_]] { trait AquaIO[F[_]] {
def readFile(file: Path): EitherT[F, AquaFileError, String] def readFile(file: Path): EitherT[F, AquaFileError, String]

View File

@ -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)
}
}

View File

@ -0,0 +1,7 @@
package aqua
import scribe.format._
object LogFormatter {
val formatter: Formatter = formatter"$date ${string("[")}$levelColored${string("]")} $message$mdc"
}

View File

@ -1,16 +1,16 @@
package aqua package aqua
import wvlet.log.{LogLevel => WLogLevel} import scribe.Level
object LogLevel { object LogLevel {
val stringToLogLevel: Map[String, WLogLevel] = Map( val stringToLogLevel: Map[String, Level] = Map(
("debug" -> WLogLevel.DEBUG), ("debug" -> Level.Debug),
("trace" -> WLogLevel.TRACE), ("trace" -> Level.Trace),
("info" -> WLogLevel.INFO), ("info" -> Level.Info),
("off" -> WLogLevel.OFF), ("off" -> Level.Fatal),
("warn" -> WLogLevel.WARN), ("warn" -> Level.Warn),
("error" -> WLogLevel.ERROR), ("error" -> Level.Error),
("all" -> WLogLevel.ALL) ("all" -> Level.Trace)
) )
} }

View File

@ -1,20 +1,21 @@
package aqua.files package aqua.files
import aqua.AquaIO import aqua.AquaIO
import aqua.io._ import aqua.io.*
import cats.data.*
import cats.data.Validated.{Invalid, Valid} import cats.data.Validated.{Invalid, Valid}
import cats.data._
import cats.effect.kernel.Concurrent import cats.effect.kernel.Concurrent
import cats.syntax.applicative._ import cats.syntax.applicative.*
import cats.syntax.apply._ import cats.syntax.applicativeError.*
import cats.syntax.either._ import cats.syntax.apply.*
import cats.syntax.flatMap._ import cats.syntax.either.*
import cats.syntax.functor._ import cats.syntax.flatMap.*
import cats.syntax.applicativeError._ import cats.syntax.functor.*
import fs2.io.file.Files import cats.syntax.traverse.*
import cats.syntax.foldable.*
import fs2.io.file.{Files, Path}
import fs2.text import fs2.text
import java.nio.file.Path
import scala.util.Try import scala.util.Try
class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] { 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] = override def readFile(file: Path): EitherT[F, AquaFileError, String] =
EitherT( EitherT(
Files[F] Files[F]
.readAll(file, 4096) .readAll(file)
.fold(Vector.empty[Byte])((acc, b) => acc :+ b) .fold(Vector.empty[Byte])((acc, b) => acc :+ b)
// TODO fix for comment on last line in air // TODO fix for comment on last line in air
// TODO should be fixed by parser // TODO should be fixed by parser
.map(_.appendedAll("\n\r".getBytes)) .map(_.appendedAll("\n\r".getBytes))
.flatMap(fs2.Stream.emits) .flatMap(fs2.Stream.emits)
.through(text.utf8Decode) .through(text.utf8.decode)
.attempt .attempt
.compile .compile
.last .last
@ -49,14 +50,14 @@ class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
): EitherT[F, AquaFileError, Path] = ): EitherT[F, AquaFileError, Path] =
in.headOption.fold(notFound)(p => in.headOption.fold(notFound)(p =>
EitherT( EitherT(
Concurrent[F].attempt(p.toFile.isFile.pure[F]) Concurrent[F].attempt(Files[F].isRegularFile(p))
) )
.leftMap[AquaFileError](FileSystemError.apply) .leftMap[AquaFileError](FileSystemError.apply)
.recover({ case _ => false }) .recover({ case _ => false })
.flatMap { .flatMap {
case true => case true =>
EitherT( EitherT(
Concurrent[F].attempt(p.toAbsolutePath.normalize().pure[F]) Concurrent[F].attempt(p.absolute.normalize.pure[F])
).leftMap[AquaFileError](FileSystemError.apply) ).leftMap[AquaFileError](FileSystemError.apply)
case false => case false =>
findFirstF(in.tail, notFound) findFirstF(in.tail, notFound)
@ -76,56 +77,72 @@ class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
EitherT.leftT(FileNotFound(src, imports)) EitherT.leftT(FileNotFound(src, imports))
) )
override def listAqua(folder: Path): F[ValidatedNec[AquaFileError, Chain[Path]]] = // Get all files for every path if the path in the list is a directory or this path otherwise
Validated private def gatherFiles(files: List[Path], listFunction: (f: Path) => F[ValidatedNec[AquaFileError, Chain[Path]]]): List[F[ValidatedNec[AquaFileError, Chain[Path]]]] = {
.fromEither( files.map(f => gatherFile(f, listFunction))
Try { }
val f = folder.toFile
if (!f.exists()) { // Get all files if the path is a directory or this path otherwise
Left(FileNotFound(folder, Nil)) private def gatherFile(f: Path, listFunction: (f: Path) => F[ValidatedNec[AquaFileError, Chain[Path]]]): F[ValidatedNec[AquaFileError, Chain[Path]]] = {
} else if (f.isDirectory) { Files[F].isDirectory(f).flatMap { isDir =>
Right(f.listFiles().toList) if (isDir)
} else { listFunction(f)
Right(f :: Nil) 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]
}
} }
}.toEither.leftMap[AquaFileError](FileSystemError.apply).flatMap(identity) }
) }
.leftMap(NonEmptyChain.one) .map(Validated.fromEither)
.pure[F] .map(_.leftMap(NonEmptyChain.one))
.flatMap { .flatMap {
case Valid(files) => case Valid(files) =>
files.collect { gatherFiles(files, listAqua).foldLeft(
case f if f.isFile && f.getName.endsWith(".aqua") => Validated.validNec[AquaFileError, Chain[Path]](Chain.nil).pure[F]
Validated ) { case (acc, v) =>
.fromTry( (acc, v).mapN(_ combine _)
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) =>
(acc, v).mapN(_ combine _)
} }
case Invalid(errs) => case Invalid(errs) =>
Validated.invalid[NonEmptyChain[AquaFileError], Chain[Path]](errs).pure[F] Validated.invalid[NonEmptyChain[AquaFileError], Chain[Path]](errs).pure[F]
} }
}
private def deleteIfExists(file: Path): EitherT[F, AquaFileError, Boolean] = private def deleteIfExists(file: Path): EitherT[F, AquaFileError, Boolean] =
Files[F].deleteIfExists(file).attemptT.leftMap(FileSystemError.apply) Files[F].deleteIfExists(file).attemptT.leftMap(FileSystemError.apply)
private def createDirectories(path: Path): EitherT[F, AquaFileError, Path] = private def createDirectories(path: Path): EitherT[F, AquaFileError, Unit] =
Files[F].createDirectories(path).attemptT.leftMap(FileSystemError.apply) Files[F].createDirectories(path).attemptT.leftMap(FileSystemError.apply(_): AquaFileError)
// Writes to a file, creates directories if they do not exist // Writes to a file, creates directories if they do not exist
override def writeFile(file: Path, content: String): EitherT[F, AquaFileError, Unit] = 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( EitherT(
fs2.Stream fs2.Stream
.emit(content) .emit(content)
.through(text.utf8Encode) .through(text.utf8.encode)
.through(Files[F].writeAll(file)) .through(Files[F].writeAll(file))
.attempt .attempt
.compile .compile

View File

@ -2,7 +2,7 @@ package aqua.io
import cats.data.NonEmptyChain import cats.data.NonEmptyChain
import java.nio.file.Path import fs2.io.file.Path
sealed trait AquaFileError { sealed trait AquaFileError {
def showForConsole: String def showForConsole: String

View File

@ -3,11 +3,11 @@ package aqua.linker
import cats.data.{NonEmptyChain, Validated, ValidatedNec} import cats.data.{NonEmptyChain, Validated, ValidatedNec}
import cats.kernel.{Monoid, Semigroup} import cats.kernel.{Monoid, Semigroup}
import cats.syntax.monoid._ import cats.syntax.monoid._
import wvlet.log.LogSupport import scribe.Logging
import scala.annotation.tailrec import scala.annotation.tailrec
object Linker extends LogSupport { object Linker extends Logging {
@tailrec @tailrec
def iter[I, E, T: Semigroup]( def iter[I, E, T: Semigroup](
@ -20,22 +20,22 @@ object Linker extends LogSupport {
Right(proc) Right(proc)
case _ => case _ =>
val (canHandle, postpone) = mods.partition(_.dependsOn.keySet.forall(proc.contains)) val (canHandle, postpone) = mods.partition(_.dependsOn.keySet.forall(proc.contains))
debug("ITERATE, can handle: " + canHandle.map(_.id)) logger.debug("ITERATE, can handle: " + canHandle.map(_.id))
debug(s"dependsOn = ${mods.map(_.dependsOn.keySet)}") logger.debug(s"dependsOn = ${mods.map(_.dependsOn.keySet)}")
debug(s"postpone = ${postpone.map(_.id)}") logger.debug(s"postpone = ${postpone.map(_.id)}")
debug(s"proc = ${proc.keySet}") logger.debug(s"proc = ${proc.keySet}")
if (canHandle.isEmpty && postpone.nonEmpty) if (canHandle.isEmpty && postpone.nonEmpty)
Left(cycleError(postpone)) Left(cycleError(postpone))
else { else {
val folded = canHandle.foldLeft(proc) { case (acc, m) => val folded = canHandle.foldLeft(proc) { case (acc, m) =>
val importKeys = m.dependsOn.keySet val importKeys = m.dependsOn.keySet
debug(s"${m.id} dependsOn $importKeys") logger.debug(s"${m.id} dependsOn $importKeys")
val deps: T => T = val deps: T => T =
importKeys.map(acc).foldLeft[T => T](identity) { case (fAcc, f) => importKeys.map(acc).foldLeft[T => T](identity) { case (fAcc, f) =>
debug("COMBINING ONE TIME ") logger.debug("COMBINING ONE TIME ")
t => { t => {
debug(s"call combine $t") logger.debug(s"call combine $t")
fAcc(t) |+| f(t) fAcc(t) |+| f(t)
} }
} }

View File

@ -7,7 +7,7 @@ import cats.Monoid
import cats.data.NonEmptyMap import cats.data.NonEmptyMap
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.monoid.* import cats.syntax.monoid.*
import wvlet.log.LogSupport import scribe.Logging
import scala.collection.immutable.SortedMap import scala.collection.immutable.SortedMap
@ -19,7 +19,7 @@ case class AquaContext(
// TODO: merge this with abilities, when have ability resolution variance // TODO: merge this with abilities, when have ability resolution variance
services: Map[String, ServiceModel] services: Map[String, ServiceModel]
) { ) {
private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) = private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) =
(prefix + pair._1, pair._2) (prefix + pair._1, pair._2)
@ -28,7 +28,7 @@ case class AquaContext(
.foldLeft(types) { case (ts, (k, v)) => .foldLeft(types) { case (ts, (k, v)) =>
ts ++ v.allTypes(k + ".") ts ++ v.allTypes(k + ".")
} }
.map(prefixFirst(prefix, _)) .map(prefixFirst(prefix, _))
def allFuncs(prefix: String = ""): Map[String, FuncCallable] = def allFuncs(prefix: String = ""): Map[String, FuncCallable] =
abilities abilities
@ -67,7 +67,7 @@ case class AquaContext(
.map(StructType(name, _)) .map(StructType(name, _))
} }
object AquaContext extends LogSupport { object AquaContext extends Logging {
trait Implicits { trait Implicits {
implicit val aquaContextMonoid: Monoid[AquaContext] implicit val aquaContextMonoid: Monoid[AquaContext]

View File

@ -3,7 +3,7 @@ package aqua.model
import aqua.types._ import aqua.types._
import cats.Eq import cats.Eq
import cats.data.{Chain, NonEmptyMap} import cats.data.{Chain, NonEmptyMap}
import wvlet.log.LogSupport import scribe.Logging
sealed trait ValueModel { sealed trait ValueModel {
def `type`: Type 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 IntoIndexModel(idx: Int, `type`: Type) extends LambdaModel
case class VarModel(name: String, `type`: Type, lambda: Chain[LambdaModel] = Chain.empty) 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) def deriveFrom(vm: VarModel): VarModel = vm.copy(lambda = vm.lambda ++ lambda)
override val lastType: Type = lambda.lastOption.map(_.`type`).getOrElse(`type`) 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) deriveFrom(nvm)
case valueModel => case valueModel =>
if (lambda.nonEmpty) if (lambda.nonEmpty)
error( logger.error(
s"Var $name derived from scalar $valueModel, but lambda is lost: $lambda" s"Var $name derived from scalar $valueModel, but lambda is lost: $lambda"
) )
valueModel valueModel

View File

@ -7,7 +7,7 @@ import aqua.types.{ArrowType, ProductType, StreamType, Type}
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
import wvlet.log.Logger import scribe.Logging
case class FuncCallable( case class FuncCallable(
funcName: String, funcName: String,
@ -16,10 +16,7 @@ case class FuncCallable(
ret: List[ValueModel], ret: List[ValueModel],
capturedArrows: Map[String, FuncCallable], capturedArrows: Map[String, FuncCallable],
capturedValues: Map[String, ValueModel] capturedValues: Map[String, ValueModel]
) extends Model { ) extends Model with Logging {
private val logger = Logger.of[FuncCallable]
import logger._
lazy val args: List[(String, Type)] = arrowType.domain.toLabelledList() lazy val args: List[(String, Type)] = arrowType.domain.toLabelledList()
lazy val argNames: List[String] = args.map(_._1) lazy val argNames: List[String] = args.map(_._1)
@ -48,7 +45,7 @@ case class FuncCallable(
forbiddenNames: Set[String] forbiddenNames: Set[String]
): Eval[(FuncOp, List[ValueModel])] = { ): 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 // Collect all arguments: what names are used inside the function, what values are received
val argsFull = ArgsCall(arrowType.domain, call.args) val argsFull = ArgsCall(arrowType.domain, call.args)
@ -140,7 +137,7 @@ case class FuncCallable(
case (acc @ (_, resolvedExports), tag) => case (acc @ (_, resolvedExports), tag) =>
tag match { tag match {
case CallArrowTag(fn, _) if !allArrows.contains(fn) => 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 _ => case _ =>
} }

View File

@ -3,11 +3,11 @@ package aqua.model.func.raw
import aqua.model.ValueModel import aqua.model.ValueModel
import cats.data.Chain import cats.data.Chain
import cats.data.Chain.{:==, ==:, nil} import cats.data.Chain.{:==, ==:, nil}
import wvlet.log.LogSupport import scribe.Logging
import scala.annotation.tailrec import scala.annotation.tailrec
object PathFinder extends LogSupport { object PathFinder extends Logging {
def find(from: RawCursor, to: RawCursor, isExit: Boolean = false): Chain[ValueModel] = { def find(from: RawCursor, to: RawCursor, isExit: Boolean = false): Chain[ValueModel] = {
@ -21,14 +21,14 @@ object PathFinder extends LogSupport {
!to.parentTag.exists(_.isInstanceOf[ParGroupTag]) !to.parentTag.exists(_.isInstanceOf[ParGroupTag])
if (wasHandled) { if (wasHandled) {
debug("Was handled") logger.debug("Was handled")
debug(" :: " + from) logger.debug(" :: " + from)
debug(" -> " + to) logger.debug(" -> " + to)
Chain.empty Chain.empty
} else { } else {
debug("Find path") logger.debug("Find path")
debug(" :: " + from) logger.debug(" :: " + from)
debug(" -> " + to) logger.debug(" -> " + to)
findPath( findPath(
fromOn, fromOn,
toOn, toOn,
@ -49,10 +49,10 @@ object PathFinder extends LogSupport {
case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p
case (acc, p) => acc :+ p case (acc, p) => acc :+ p
} }
trace(s"PEER IDS: $optimized") logger.trace(s"PEER IDS: $optimized")
trace(s"PREFIX: $prefix") logger.trace(s"PREFIX: $prefix")
trace(s"SUFFIX: $suffix") logger.trace(s"SUFFIX: $suffix")
trace(s"OPTIMIZED WITH PREFIX AND SUFFIX: $optimized") logger.trace(s"OPTIMIZED WITH PREFIX AND SUFFIX: $optimized")
val noPrefix = skipPrefix(optimized, prefix, optimized) val noPrefix = skipPrefix(optimized, prefix, optimized)
skipSuffix(noPrefix, suffix, noPrefix) skipSuffix(noPrefix, suffix, noPrefix)
} }
@ -63,28 +63,28 @@ object PathFinder extends LogSupport {
fromPeer: Option[ValueModel], fromPeer: Option[ValueModel],
toPeer: Option[ValueModel] toPeer: Option[ValueModel]
): Chain[ValueModel] = { ): Chain[ValueModel] = {
trace(s"FROM ON: $fromOn") logger.trace(s"FROM ON: $fromOn")
trace(s"TO ON: $toOn") logger.trace(s"TO ON: $toOn")
val (from, to) = skipCommonPrefix(fromOn, toOn) val (from, to) = skipCommonPrefix(fromOn, toOn)
val fromFix = val fromFix =
if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from
val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to
trace("FIND PATH FROM | " + fromFix) logger.trace("FIND PATH FROM | " + fromFix)
trace(" TO | " + toFix) logger.trace(" TO | " + toFix)
val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via) 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 fromPeerCh = Chain.fromOption(fromPeer)
val toPeerCh = Chain.fromOption(toPeer) val toPeerCh = Chain.fromOption(toPeer)
val optimized = optimizePath(fromPeerCh ++ fromTo ++ toPeerCh, fromPeerCh, toPeerCh) 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")}'" s"FROM PEER '${fromPeer.map(_.toString).getOrElse("None")}' TO PEER '${toPeer.map(_.toString).getOrElse("None")}'"
) )
trace(" Optimized: " + optimized) logger.trace(" Optimized: " + optimized)
optimized optimized
} }

View File

@ -6,11 +6,12 @@ import cats.Eval
import cats.data.{Chain, NonEmptyList, OptionT} import cats.data.{Chain, NonEmptyList, OptionT}
import cats.free.Cofree import cats.free.Cofree
import cats.syntax.traverse._ import cats.syntax.traverse._
import wvlet.log.LogSupport import scribe.Logging
// Can be heavily optimized by caching parent cursors, not just list of zippers // Can be heavily optimized by caching parent cursors, not just list of zippers
case class RawCursor(tree: NonEmptyList[ChainZipper[FuncOp.Tree]]) 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 tag: RawTag = current.head
def parentTag: Option[RawTag] = parent.map(_.head) def parentTag: Option[RawTag] = parent.map(_.head)

View File

@ -10,9 +10,9 @@ import cats.data.Chain.nil
import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT} import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
import cats.free.Cofree import cats.free.Cofree
import cats.syntax.traverse._ 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 Tree = Cofree[Chain, RawTag]
type Res = Cofree[Chain, ResolvedOp] type Res = Cofree[Chain, ResolvedOp]
@ -50,7 +50,7 @@ object Topology extends LogSupport {
val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op))) val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
val resolvedCofree = cursor val resolvedCofree = cursor
.cata(wrap) { rc => .cata(wrap) { rc =>
debug(s"<:> $rc") logger.debug(s"<:> $rc")
val resolved = MakeRes val resolved = MakeRes
.resolve(rc.currentPeerId) .resolve(rc.currentPeerId)
.lift .lift
@ -64,12 +64,12 @@ object Topology extends LogSupport {
through(rc.pathToNext) through(rc.pathToNext)
) )
if (cz.next.nonEmpty || cz.prev.nonEmpty) { if (cz.next.nonEmpty || cz.prev.nonEmpty) {
debug(s"Resolved $rc -> $cofree") logger.debug(s"Resolved $rc -> $cofree")
if (cz.prev.nonEmpty) 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) if (cz.next.nonEmpty)
trace("To next: " + cz.next.map(_.head).toList.mkString(" -> ")) logger.trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
} else debug(s"EMPTY $rc -> $cofree") } else logger.debug(s"EMPTY $rc -> $cofree")
cz cz
} }
) )
@ -78,11 +78,11 @@ object Topology extends LogSupport {
resolvedCofree.map(NonEmptyChain.fromChain(_).map(_.uncons)).map { resolvedCofree.map(NonEmptyChain.fromChain(_).map(_.uncons)).map {
case None => case None =>
error("Topology emitted nothing") logger.error("Topology emitted nothing")
Cofree(SeqRes, MakeRes.nilTail) Cofree(SeqRes, MakeRes.nilTail)
case Some((el, `nil`)) => el case Some((el, `nil`)) => el
case Some((el, tail)) => 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)) Cofree(SeqRes, Eval.now(el +: tail))
} }
} }

View File

@ -7,9 +7,9 @@ import aqua.model.topology.Topology
import aqua.types.ScalarType import aqua.types.ScalarType
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree 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 { def defaultFilter(t: ResolvedOp): Boolean = t match {
case _: NoAir => false case _: NoAir => false

View File

@ -113,7 +113,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| <- v | <- v
|""".stripMargin |""".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 { "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, """func tryGen(a: string,
| b: string)""".stripMargin | 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 { "function with root expression without children" should "parse with error" in {
@ -131,7 +131,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| <- v | <- v
|""".stripMargin |""".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 { "multi function expression" should "parse" in {

View File

@ -1 +1,3 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") 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")

View File

@ -23,9 +23,9 @@ import cats.syntax.apply._
import cats.syntax.semigroup._ import cats.syntax.semigroup._
import monocle.Lens import monocle.Lens
import monocle.macros.GenLens import monocle.macros.GenLens
import wvlet.log.LogSupport import scribe.Logging
object Semantics extends LogSupport { object Semantics extends Logging {
def folder[F[_], G[_]](implicit def folder[F[_], G[_]](implicit
A: AbilitiesAlgebra[F, G], A: AbilitiesAlgebra[F, G],