mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-12 17:55:33 +00:00
Abstract backend (#182)
This commit is contained in:
parent
bbf47628c6
commit
5e1ef6e227
17
backend/air/src/main/scala/aqua/backend/air/AirBackend.scala
Normal file
17
backend/air/src/main/scala/aqua/backend/air/AirBackend.scala
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package aqua.backend.air
|
||||||
|
|
||||||
|
import aqua.backend.{Backend, Compiled}
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import aqua.model.transform.BodyConfig
|
||||||
|
import cats.implicits.toShow
|
||||||
|
|
||||||
|
object AirBackend extends Backend {
|
||||||
|
|
||||||
|
val ext = ".air"
|
||||||
|
|
||||||
|
override def generate(context: AquaContext, bc: BodyConfig): Seq[Compiled] = {
|
||||||
|
context.funcs.values.toList.map(fc =>
|
||||||
|
Compiled("." + fc.funcName + ext, FuncAirGen(fc).generateAir(bc).show)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,4 @@ case class FuncAirGen(func: FuncCallable) {
|
|||||||
AirGen(
|
AirGen(
|
||||||
Transform.forClient(func, conf)
|
Transform.forClient(func, conf)
|
||||||
).generate
|
).generate
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates AIR from the optimized function body, assuming client is behind a relay
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
def generateClientAir(conf: BodyConfig = BodyConfig()): Air =
|
|
||||||
AirGen(
|
|
||||||
Transform.forClient(func, conf)
|
|
||||||
).generate
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package aqua.backend.js
|
||||||
|
|
||||||
|
import aqua.backend.{Backend, Compiled}
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import aqua.model.transform.BodyConfig
|
||||||
|
import cats.data.Chain
|
||||||
|
|
||||||
|
object JavaScriptBackend extends Backend {
|
||||||
|
|
||||||
|
val ext = ".js"
|
||||||
|
|
||||||
|
override def generate(context: AquaContext, bc: BodyConfig): Seq[Compiled] = {
|
||||||
|
val funcs = Chain.fromSeq(context.funcs.values.toSeq).map(JavaScriptFunc(_))
|
||||||
|
Seq(
|
||||||
|
Compiled(
|
||||||
|
ext,
|
||||||
|
JavaScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(bc)).toList.mkString("\n\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ case class JavaScriptFunc(func: FuncCallable) {
|
|||||||
|
|
||||||
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
|
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
|
||||||
|
|
||||||
val tsAir = FuncAirGen(func).generateClientAir(conf)
|
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||||
|
|
||||||
val returnCallback = func.ret.as {
|
val returnCallback = func.ret.as {
|
||||||
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
||||||
@ -86,8 +86,7 @@ case class JavaScriptFunc(func: FuncCallable) {
|
|||||||
object JavaScriptFunc {
|
object JavaScriptFunc {
|
||||||
|
|
||||||
def argsToTs(at: ArrowType): String =
|
def argsToTs(at: ArrowType): String =
|
||||||
at.args
|
at.args.zipWithIndex
|
||||||
.zipWithIndex
|
|
||||||
.map(_.swap)
|
.map(_.swap)
|
||||||
.map(kv => "arg" + kv._1)
|
.map(kv => "arg" + kv._1)
|
||||||
.mkString(", ")
|
.mkString(", ")
|
||||||
|
18
backend/src/main/scala/aqua/backend/Backend.scala
Normal file
18
backend/src/main/scala/aqua/backend/Backend.scala
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package aqua.backend
|
||||||
|
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import aqua.model.transform.BodyConfig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compilation result
|
||||||
|
* @param suffix extension or another info that will be added to a resulted file
|
||||||
|
* @param content a code that is used as an output
|
||||||
|
*/
|
||||||
|
case class Compiled(suffix: String, content: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes how context can be finalized
|
||||||
|
*/
|
||||||
|
trait Backend {
|
||||||
|
def generate(context: AquaContext, bc: BodyConfig): Seq[Compiled]
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package aqua.backend.ts
|
||||||
|
|
||||||
|
import aqua.backend.{Backend, Compiled}
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import aqua.model.transform.BodyConfig
|
||||||
|
import cats.data.Chain
|
||||||
|
|
||||||
|
object TypeScriptBackend extends Backend {
|
||||||
|
|
||||||
|
val ext = ".ts"
|
||||||
|
|
||||||
|
override def generate(context: AquaContext, bc: BodyConfig): Seq[Compiled] = {
|
||||||
|
val funcs = Chain.fromSeq(context.funcs.values.toSeq).map(TypeScriptFunc(_))
|
||||||
|
Seq(
|
||||||
|
Compiled(
|
||||||
|
ext,
|
||||||
|
TypeScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(bc)).toList.mkString("\n\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -4,16 +4,16 @@ import aqua.model.AquaContext
|
|||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
|
|
||||||
case class TypescriptFile(context: AquaContext) {
|
case class TypeScriptFile(context: AquaContext) {
|
||||||
|
|
||||||
def funcs: Chain[TypescriptFunc] =
|
def funcs: Chain[TypeScriptFunc] =
|
||||||
Chain.fromSeq(context.funcs.values.toSeq).map(TypescriptFunc(_))
|
Chain.fromSeq(context.funcs.values.toSeq).map(TypeScriptFunc(_))
|
||||||
|
|
||||||
def generateTS(conf: BodyConfig = BodyConfig()): String =
|
def generateTS(conf: BodyConfig = BodyConfig()): String =
|
||||||
TypescriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(conf)).toList.mkString("\n\n")
|
TypeScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(conf)).toList.mkString("\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
object TypescriptFile {
|
object TypeScriptFile {
|
||||||
|
|
||||||
val Header: String =
|
val Header: String =
|
||||||
s"""/**
|
s"""/**
|
@ -7,9 +7,9 @@ import aqua.types._
|
|||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
import cats.syntax.show._
|
import cats.syntax.show._
|
||||||
|
|
||||||
case class TypescriptFunc(func: FuncCallable) {
|
case class TypeScriptFunc(func: FuncCallable) {
|
||||||
|
|
||||||
import TypescriptFunc._
|
import TypeScriptFunc._
|
||||||
|
|
||||||
def argsTypescript: String =
|
def argsTypescript: String =
|
||||||
func.args.args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
func.args.args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
||||||
@ -25,7 +25,7 @@ case class TypescriptFunc(func: FuncCallable) {
|
|||||||
|
|
||||||
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
|
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
|
||||||
|
|
||||||
val tsAir = FuncAirGen(func).generateClientAir(conf)
|
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||||
|
|
||||||
val returnCallback = func.ret.as {
|
val returnCallback = func.ret.as {
|
||||||
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
||||||
@ -102,7 +102,7 @@ case class TypescriptFunc(func: FuncCallable) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object TypescriptFunc {
|
object TypeScriptFunc {
|
||||||
|
|
||||||
def typeToTs(t: Type): String = t match {
|
def typeToTs(t: Type): String = t match {
|
||||||
case OptionType(t) => typeToTs(t) + " | null"
|
case OptionType(t) => typeToTs(t) + " | null"
|
10
build.sbt
10
build.sbt
@ -55,7 +55,7 @@ lazy val cli = project
|
|||||||
"com.monovore" %% "decline-enumeratum" % declineEnumV
|
"com.monovore" %% "decline-enumeratum" % declineEnumV
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.dependsOn(semantics, `backend-air`, `backend-ts`, `backend-js`, linker)
|
.dependsOn(semantics, `backend-air`, `backend-ts`, `backend-js`, linker, backend)
|
||||||
|
|
||||||
lazy val types = project
|
lazy val types = project
|
||||||
.settings(commons)
|
.settings(commons)
|
||||||
@ -94,6 +94,7 @@ lazy val model = project
|
|||||||
.dependsOn(types)
|
.dependsOn(types)
|
||||||
|
|
||||||
lazy val `test-kit` = project
|
lazy val `test-kit` = project
|
||||||
|
.in(file("model/test-kit"))
|
||||||
.settings(commons: _*)
|
.settings(commons: _*)
|
||||||
.dependsOn(model)
|
.dependsOn(model)
|
||||||
|
|
||||||
@ -107,10 +108,15 @@ lazy val semantics = project
|
|||||||
)
|
)
|
||||||
.dependsOn(model, `test-kit` % Test, parser)
|
.dependsOn(model, `test-kit` % Test, parser)
|
||||||
|
|
||||||
|
lazy val backend = project
|
||||||
|
.in(file("backend"))
|
||||||
|
.settings(commons: _*)
|
||||||
|
.dependsOn(model)
|
||||||
|
|
||||||
lazy val `backend-air` = project
|
lazy val `backend-air` = project
|
||||||
.in(file("backend/air"))
|
.in(file("backend/air"))
|
||||||
.settings(commons: _*)
|
.settings(commons: _*)
|
||||||
.dependsOn(model)
|
.dependsOn(backend)
|
||||||
|
|
||||||
lazy val `backend-ts` = project
|
lazy val `backend-ts` = project
|
||||||
.in(file("backend/ts"))
|
.in(file("backend/ts"))
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
import aqua.backend.air.FuncAirGen
|
import aqua.backend.Backend
|
||||||
import aqua.backend.js.JavaScriptFile
|
import aqua.backend.air.AirBackend
|
||||||
import aqua.backend.ts.TypescriptFile
|
import aqua.backend.js.JavaScriptBackend
|
||||||
import aqua.io.{AquaFileError, AquaFiles, FileModuleId, Unresolvable}
|
import aqua.backend.ts.TypeScriptBackend
|
||||||
|
import aqua.io._
|
||||||
import aqua.linker.Linker
|
import aqua.linker.Linker
|
||||||
import aqua.model.AquaContext
|
import aqua.model.AquaContext
|
||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import aqua.semantics.{RulesViolated, SemanticError, Semantics}
|
import aqua.semantics.{RulesViolated, SemanticError, Semantics}
|
||||||
import cats.Applicative
|
|
||||||
import cats.data.Validated.{Invalid, Valid}
|
|
||||||
import cats.data._
|
import cats.data._
|
||||||
import cats.effect.kernel.Concurrent
|
import cats.effect.kernel.Concurrent
|
||||||
import cats.kernel.Monoid
|
import cats.kernel.Monoid
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
import cats.syntax.show._
|
import cats.{Applicative, Monad}
|
||||||
import fs2.io.file.Files
|
import fs2.io.file.Files
|
||||||
import fs2.text
|
|
||||||
import wvlet.log.LogSupport
|
import wvlet.log.LogSupport
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -29,36 +27,38 @@ object AquaCompiler extends LogSupport {
|
|||||||
case object JavaScriptTarget extends CompileTarget
|
case object JavaScriptTarget extends CompileTarget
|
||||||
case object AirTarget extends CompileTarget
|
case object AirTarget extends CompileTarget
|
||||||
|
|
||||||
case class Prepared(modFile: Path, srcPath: Path, targetPath: Path, context: AquaContext) {
|
private def gatherPreparedFiles(
|
||||||
|
srcPath: Path,
|
||||||
|
targetPath: Path,
|
||||||
|
files: Map[FileModuleId, ValidatedNec[SemanticError[FileSpan.F], AquaContext]]
|
||||||
|
): ValidatedNec[String, Chain[Prepared]] = {
|
||||||
|
val (errs, _, preps) = files.toSeq.foldLeft[(Chain[String], Set[String], Chain[Prepared])](
|
||||||
|
(Chain.empty, Set.empty, Chain.empty)
|
||||||
|
) { case ((errs, errsSet, preps), (modId, proc)) =>
|
||||||
|
proc.fold(
|
||||||
|
es => {
|
||||||
|
val newErrs = showProcErrors(es.toChain).filterNot(errsSet.contains)
|
||||||
|
(errs ++ newErrs, errsSet ++ newErrs.iterator, preps)
|
||||||
|
},
|
||||||
|
c => {
|
||||||
|
Prepared(modId.file, srcPath, targetPath, c) match {
|
||||||
|
case Validated.Valid(p) ⇒
|
||||||
|
(errs, errsSet, preps :+ p)
|
||||||
|
case Validated.Invalid(err) ⇒
|
||||||
|
(errs :+ err.getMessage, errsSet, preps)
|
||||||
|
}
|
||||||
|
|
||||||
def hasOutput(target: CompileTarget): Boolean = target match {
|
|
||||||
case _ => context.funcs.nonEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
def targetPath(ext: String): Validated[Throwable, Path] =
|
|
||||||
Validated.catchNonFatal {
|
|
||||||
val srcDir = if (srcPath.toFile.isDirectory) srcPath else srcPath.getParent
|
|
||||||
val srcFilePath = srcDir.toAbsolutePath
|
|
||||||
.normalize()
|
|
||||||
.relativize(modFile.toAbsolutePath.normalize())
|
|
||||||
|
|
||||||
val targetAqua =
|
|
||||||
targetPath.toAbsolutePath
|
|
||||||
.normalize()
|
|
||||||
.resolve(
|
|
||||||
srcFilePath
|
|
||||||
)
|
|
||||||
|
|
||||||
val fileName = targetAqua.getFileName
|
|
||||||
if (fileName == null) {
|
|
||||||
throw new Exception(s"Unexpected: 'fileName' is null in path $targetAqua")
|
|
||||||
} else {
|
|
||||||
// rename `.aqua` file name to `.ext`
|
|
||||||
targetAqua.getParent.resolve(fileName.toString.stripSuffix(".aqua") + s".$ext")
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
NonEmptyChain
|
||||||
|
.fromChain(errs)
|
||||||
|
.fold(Validated.validNec[String, Chain[Prepared]](preps))(Validated.invalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a structure that will be used to create output by a backend
|
||||||
|
*/
|
||||||
def prepareFiles[F[_]: Files: Concurrent](
|
def prepareFiles[F[_]: Files: Concurrent](
|
||||||
srcPath: Path,
|
srcPath: Path,
|
||||||
imports: LazyList[Path],
|
imports: LazyList[Path],
|
||||||
@ -81,21 +81,7 @@ object AquaCompiler extends LogSupport {
|
|||||||
ids => Unresolvable(ids.map(_.id.file.toString).mkString(" -> "))
|
ids => Unresolvable(ids.map(_.id.file.toString).mkString(" -> "))
|
||||||
) match {
|
) match {
|
||||||
case Validated.Valid(files) ⇒
|
case Validated.Valid(files) ⇒
|
||||||
val (errs, _, preps) =
|
gatherPreparedFiles(srcPath, targetPath, files)
|
||||||
files.toSeq.foldLeft[(Chain[String], Set[String], Chain[Prepared])](
|
|
||||||
(Chain.empty, Set.empty, Chain.empty)
|
|
||||||
) { case ((errs, errsSet, preps), (modId, proc)) =>
|
|
||||||
proc.fold(
|
|
||||||
es => {
|
|
||||||
val newErrs = showProcErrors(es.toChain).filterNot(errsSet.contains)
|
|
||||||
(errs ++ newErrs, errsSet ++ newErrs.iterator, preps)
|
|
||||||
},
|
|
||||||
c => (errs, errsSet, preps :+ Prepared(modId.file, srcPath, targetPath, c))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
NonEmptyChain
|
|
||||||
.fromChain(errs)
|
|
||||||
.fold(Validated.validNec[String, Chain[Prepared]](preps))(Validated.invalid)
|
|
||||||
|
|
||||||
case Validated.Invalid(errs) ⇒
|
case Validated.Invalid(errs) ⇒
|
||||||
Validated.invalid(
|
Validated.invalid(
|
||||||
@ -118,6 +104,38 @@ object AquaCompiler extends LogSupport {
|
|||||||
"Semantic error"
|
"Semantic error"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def targetToBackend(target: CompileTarget): Backend = {
|
||||||
|
target match {
|
||||||
|
case TypescriptTarget =>
|
||||||
|
TypeScriptBackend
|
||||||
|
case JavaScriptTarget =>
|
||||||
|
JavaScriptBackend
|
||||||
|
case AirTarget =>
|
||||||
|
AirBackend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def gatherResults[F[_]: Monad](
|
||||||
|
results: List[EitherT[F, String, Unit]]
|
||||||
|
): F[Validated[NonEmptyChain[String], Chain[String]]] = {
|
||||||
|
results
|
||||||
|
.foldLeft(
|
||||||
|
EitherT.rightT[F, NonEmptyChain[String]](Chain.empty[String])
|
||||||
|
) { case (accET, writeET) =>
|
||||||
|
EitherT(for {
|
||||||
|
acc <- accET.value
|
||||||
|
writeResult <- writeET.value
|
||||||
|
} yield (acc, writeResult) match {
|
||||||
|
case (Left(errs), Left(err)) => Left(errs :+ err)
|
||||||
|
case (Right(res), Right(_)) => Right(res)
|
||||||
|
case (Left(errs), _) => Left(errs)
|
||||||
|
case (_, Left(err)) => Left(NonEmptyChain.of(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.value
|
||||||
|
.map(Validated.fromEither)
|
||||||
|
}
|
||||||
|
|
||||||
def compileFilesTo[F[_]: Files: Concurrent](
|
def compileFilesTo[F[_]: Files: Concurrent](
|
||||||
srcPath: Path,
|
srcPath: Path,
|
||||||
imports: LazyList[Path],
|
imports: LazyList[Path],
|
||||||
@ -129,116 +147,44 @@ object AquaCompiler extends LogSupport {
|
|||||||
prepareFiles(srcPath, imports, targetPath)
|
prepareFiles(srcPath, imports, targetPath)
|
||||||
.map(_.map(_.filter { p =>
|
.map(_.map(_.filter { p =>
|
||||||
val hasOutput = p.hasOutput(compileTo)
|
val hasOutput = p.hasOutput(compileTo)
|
||||||
if (!hasOutput) info(s"Source ${p.modFile}: compilation OK (nothing to emit)")
|
if (!hasOutput) info(s"Source ${p.srcFile}: compilation OK (nothing to emit)")
|
||||||
hasOutput
|
hasOutput
|
||||||
}))
|
}))
|
||||||
.flatMap[ValidatedNec[String, Chain[String]]] {
|
.flatMap[ValidatedNec[String, Chain[String]]] {
|
||||||
case Validated.Invalid(e) =>
|
case Validated.Invalid(e) =>
|
||||||
Applicative[F].pure(Validated.invalid(e))
|
Applicative[F].pure(Validated.invalid(e))
|
||||||
case Validated.Valid(preps) =>
|
case Validated.Valid(preps) =>
|
||||||
(compileTo match {
|
val backend = targetToBackend(compileTo)
|
||||||
case TypescriptTarget =>
|
val results = preps.toList
|
||||||
preps.map { p =>
|
.flatMap(p =>
|
||||||
p.targetPath("ts") match {
|
backend.generate(p.context, bodyConfig).map { compiled =>
|
||||||
case Invalid(t) =>
|
val targetPath = p.targetPath(
|
||||||
EitherT.pure(t.getMessage)
|
p.srcFile.getFileName.toString.stripSuffix(".aqua") + compiled.suffix
|
||||||
case Valid(tp) =>
|
|
||||||
writeFile(tp, TypescriptFile(p.context).generateTS(bodyConfig)).flatTap { _ =>
|
|
||||||
EitherT.pure(
|
|
||||||
Validated.catchNonFatal(
|
|
||||||
info(
|
|
||||||
s"Result ${tp.toAbsolutePath}: compilation OK (${p.context.funcs.size} functions)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case JavaScriptTarget =>
|
|
||||||
preps.map { p =>
|
|
||||||
p.targetPath("js") match {
|
|
||||||
case Invalid(t) =>
|
|
||||||
EitherT.pure(t.getMessage)
|
|
||||||
case Valid(tp) =>
|
|
||||||
writeFile(tp, JavaScriptFile(p.context).generateJS(bodyConfig)).flatTap { _ =>
|
|
||||||
EitherT.pure(
|
|
||||||
Validated.catchNonFatal(
|
|
||||||
info(
|
|
||||||
s"Result ${tp.toAbsolutePath}: compilation OK (${p.context.funcs.size} functions)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add function name to AirTarget class
|
|
||||||
case AirTarget =>
|
|
||||||
preps
|
|
||||||
.flatMap(p =>
|
|
||||||
Chain
|
|
||||||
.fromSeq(p.context.funcs.values.toSeq)
|
|
||||||
.map(fc => fc.funcName -> FuncAirGen(fc).generateAir(bodyConfig).show)
|
|
||||||
.map { case (fnName, generated) =>
|
|
||||||
val tpV = p.targetPath(fnName + ".air")
|
|
||||||
tpV match {
|
|
||||||
case Invalid(t) =>
|
|
||||||
EitherT.pure(t.getMessage)
|
|
||||||
case Valid(tp) =>
|
|
||||||
writeFile(
|
|
||||||
tp,
|
|
||||||
generated
|
|
||||||
).flatTap { _ =>
|
|
||||||
EitherT.pure(
|
|
||||||
Validated.catchNonFatal(
|
|
||||||
info(
|
|
||||||
s"Result ${tp.toAbsolutePath}: compilation OK (${p.context.funcs.size} functions)"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}).foldLeft(
|
targetPath.fold(
|
||||||
EitherT.rightT[F, NonEmptyChain[String]](Chain.empty[String])
|
t => EitherT.leftT[F, Unit](t.getMessage),
|
||||||
) { case (accET, writeET) =>
|
tp =>
|
||||||
EitherT(for {
|
FileOps
|
||||||
a <- accET.value
|
.writeFile(
|
||||||
w <- writeET.value
|
tp,
|
||||||
} yield (a, w) match {
|
compiled.content
|
||||||
case (Left(errs), Left(err)) => Left(errs :+ err)
|
)
|
||||||
case (Right(res), Right(_)) => Right(res)
|
.flatTap { _ =>
|
||||||
case (Left(errs), _) => Left(errs)
|
EitherT.pure(
|
||||||
case (_, Left(err)) => Left(NonEmptyChain.of(err))
|
Validated.catchNonFatal(
|
||||||
})
|
info(
|
||||||
}.value
|
s"Result ${tp.toAbsolutePath}: compilation OK (${p.context.funcs.size} functions)"
|
||||||
.map(Validated.fromEither)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
gatherResults(results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, Unit] =
|
|
||||||
EitherT.right[String](Files[F].deleteIfExists(file)) >>
|
|
||||||
EitherT[F, String, Unit](
|
|
||||||
fs2.Stream
|
|
||||||
.emit(
|
|
||||||
content
|
|
||||||
)
|
|
||||||
.through(text.utf8Encode)
|
|
||||||
.through(Files[F].writeAll(file))
|
|
||||||
.attempt
|
|
||||||
.map { e =>
|
|
||||||
e.left
|
|
||||||
.map(t => s"Error on writing file $file" + t)
|
|
||||||
}
|
|
||||||
.compile
|
|
||||||
.drain
|
|
||||||
.map(_ => Right(()))
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
57
cli/src/main/scala/aqua/Prepared.scala
Normal file
57
cli/src/main/scala/aqua/Prepared.scala
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package aqua
|
||||||
|
|
||||||
|
import aqua.AquaCompiler.CompileTarget
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import cats.data.Validated
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
object Prepared {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param srcFile aqua source
|
||||||
|
* @param srcPath a main source path with all aqua files
|
||||||
|
* @param targetPath a main path where all output files will be written
|
||||||
|
* @param context processed aqua code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
srcFile: Path,
|
||||||
|
srcPath: Path,
|
||||||
|
targetPath: Path,
|
||||||
|
context: AquaContext
|
||||||
|
): Validated[Throwable, Prepared] =
|
||||||
|
Validated.catchNonFatal {
|
||||||
|
val srcDir = if (srcPath.toFile.isDirectory) srcPath else srcPath.getParent
|
||||||
|
val srcFilePath = srcDir.toAbsolutePath
|
||||||
|
.normalize()
|
||||||
|
.relativize(srcFile.toAbsolutePath.normalize())
|
||||||
|
|
||||||
|
val targetDir =
|
||||||
|
targetPath.toAbsolutePath
|
||||||
|
.normalize()
|
||||||
|
.resolve(
|
||||||
|
srcFilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
new Prepared(targetDir, srcFile, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All info that can be used to write a final output.
|
||||||
|
* @param targetDir a directory to write to
|
||||||
|
* @param srcFile file with a source (aqua code)
|
||||||
|
* @param context processed code
|
||||||
|
*/
|
||||||
|
case class Prepared private (targetDir: Path, srcFile: Path, context: AquaContext) {
|
||||||
|
|
||||||
|
def hasOutput(target: CompileTarget): Boolean = target match {
|
||||||
|
case _ => context.funcs.nonEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
def targetPath(fileName: String): Validated[Throwable, Path] =
|
||||||
|
Validated.catchNonFatal {
|
||||||
|
targetDir.getParent.resolve(fileName)
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ import cats.effect.Concurrent
|
|||||||
import cats.syntax.apply._
|
import cats.syntax.apply._
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
import fs2.io.file.Files
|
import fs2.io.file.Files
|
||||||
import fs2.text
|
|
||||||
|
|
||||||
import java.nio.file.{Path, Paths}
|
import java.nio.file.{Path, Paths}
|
||||||
|
|
||||||
@ -54,36 +53,25 @@ case class AquaFile(
|
|||||||
|
|
||||||
object AquaFile {
|
object AquaFile {
|
||||||
|
|
||||||
def readSourceText[F[_]: Files: Concurrent](
|
def readAst[F[_]: Files: Concurrent](
|
||||||
file: Path
|
file: Path
|
||||||
): fs2.Stream[F, Either[AquaFileError, String]] =
|
): fs2.Stream[F, Either[AquaFileError, (String, Ast[FileSpan.F])]] =
|
||||||
Files[F]
|
FileOps
|
||||||
.readAll(file, 4096)
|
.readSourceText[F](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)
|
|
||||||
.attempt
|
|
||||||
.map {
|
.map {
|
||||||
_.left
|
_.left
|
||||||
.map(t => FileSystemError(t))
|
.map(t => FileSystemError(t))
|
||||||
}
|
}
|
||||||
|
.map(
|
||||||
def readAst[F[_]: Files: Concurrent](
|
_.flatMap(source =>
|
||||||
file: Path
|
Aqua
|
||||||
): fs2.Stream[F, Either[AquaFileError, (String, Ast[FileSpan.F])]] =
|
.parseFileString(file.toString, source)
|
||||||
readSourceText[F](file).map(
|
.map(source -> _)
|
||||||
_.flatMap(source =>
|
.toEither
|
||||||
Aqua
|
.left
|
||||||
.parseFileString(file.toString, source)
|
.map(AquaScriptErrors(_))
|
||||||
.map(source -> _)
|
)
|
||||||
.toEither
|
|
||||||
.left
|
|
||||||
.map(AquaScriptErrors(_))
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def read[F[_]: Files: Concurrent](file: Path): EitherT[F, AquaFileError, AquaFile] =
|
def read[F[_]: Files: Concurrent](file: Path): EitherT[F, AquaFileError, AquaFile] =
|
||||||
EitherT(readAst[F](file).compile.last.map(_.getOrElse(Left(EmptyFileError(file))))).map {
|
EitherT(readAst[F](file).compile.last.map(_.getOrElse(Left(EmptyFileError(file))))).map {
|
||||||
|
47
cli/src/main/scala/aqua/io/FileOps.scala
Normal file
47
cli/src/main/scala/aqua/io/FileOps.scala
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package aqua.io
|
||||||
|
|
||||||
|
import cats.data.EitherT
|
||||||
|
import cats.effect.Concurrent
|
||||||
|
import cats.implicits.toFunctorOps
|
||||||
|
import fs2.io.file.Files
|
||||||
|
import fs2.text
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
object FileOps {
|
||||||
|
|
||||||
|
def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, Unit] =
|
||||||
|
EitherT
|
||||||
|
.right[String](Files[F].deleteIfExists(file))
|
||||||
|
.flatMap(_ =>
|
||||||
|
EitherT[F, String, Unit](
|
||||||
|
fs2.Stream
|
||||||
|
.emit(
|
||||||
|
content
|
||||||
|
)
|
||||||
|
.through(text.utf8Encode)
|
||||||
|
.through(Files[F].writeAll(file))
|
||||||
|
.attempt
|
||||||
|
.map { e =>
|
||||||
|
e.left
|
||||||
|
.map(t => s"Error on writing file $file" + t)
|
||||||
|
}
|
||||||
|
.compile
|
||||||
|
.drain
|
||||||
|
.map(_ => Right(()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def readSourceText[F[_]: Files: Concurrent](
|
||||||
|
file: Path
|
||||||
|
): fs2.Stream[F, Either[Throwable, String]] =
|
||||||
|
Files[F]
|
||||||
|
.readAll(file, 4096)
|
||||||
|
.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)
|
||||||
|
.attempt
|
||||||
|
}
|
18
cli/src/test/aqua/test.aqua
Normal file
18
cli/src/test/aqua/test.aqua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
service CustomId("cid"):
|
||||||
|
id() -> string
|
||||||
|
|
||||||
|
func first(node_id: string, viaAr: []string) -> string:
|
||||||
|
on node_id via viaAr:
|
||||||
|
p <- CustomId.id()
|
||||||
|
<- p
|
||||||
|
|
||||||
|
|
||||||
|
func second(node_id: string, viaStr: *string) -> string:
|
||||||
|
on node_id via viaStr:
|
||||||
|
p <- CustomId.id()
|
||||||
|
<- p
|
||||||
|
|
||||||
|
func third(relay: string, node_id: string, viaOpt: ?string) -> string:
|
||||||
|
on node_id via viaOpt:
|
||||||
|
p <- CustomId.id()
|
||||||
|
<- p
|
60
cli/src/test/scala/WriteFileSpec.scala
Normal file
60
cli/src/test/scala/WriteFileSpec.scala
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import aqua.AquaCompiler
|
||||||
|
import aqua.model.transform.BodyConfig
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.effect.unsafe.implicits.global
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
|
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 bc = BodyConfig()
|
||||||
|
AquaCompiler
|
||||||
|
.compileFilesTo[IO](src, LazyList.empty, targetTs, AquaCompiler.TypescriptTarget, bc)
|
||||||
|
.unsafeRunSync()
|
||||||
|
.leftMap { err =>
|
||||||
|
println(err)
|
||||||
|
err
|
||||||
|
}
|
||||||
|
.isValid should be(true)
|
||||||
|
val targetTsFile = targetTs.resolve("test.ts")
|
||||||
|
targetTsFile.toFile.exists() should be(true)
|
||||||
|
Files.deleteIfExists(targetTsFile)
|
||||||
|
|
||||||
|
AquaCompiler
|
||||||
|
.compileFilesTo[IO](src, LazyList.empty, targetJs, AquaCompiler.JavaScriptTarget, bc)
|
||||||
|
.unsafeRunSync()
|
||||||
|
.leftMap { err =>
|
||||||
|
println(err)
|
||||||
|
err
|
||||||
|
}
|
||||||
|
.isValid should be(true)
|
||||||
|
val targetJsFile = targetJs.resolve("test.js")
|
||||||
|
targetJsFile.toFile.exists() should be(true)
|
||||||
|
Files.deleteIfExists(targetJsFile)
|
||||||
|
|
||||||
|
AquaCompiler
|
||||||
|
.compileFilesTo[IO](src, LazyList.empty, targetAir, AquaCompiler.AirTarget, bc)
|
||||||
|
.unsafeRunSync()
|
||||||
|
.leftMap { err =>
|
||||||
|
println(err)
|
||||||
|
err
|
||||||
|
}
|
||||||
|
.isValid should be(true)
|
||||||
|
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)
|
||||||
|
|
||||||
|
Seq(targetAirFileFirst, targetAirFileSecond, targetAirFileThird).map(Files.deleteIfExists)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,7 +4,7 @@ import aqua.Node
|
|||||||
import aqua.model.VarModel
|
import aqua.model.VarModel
|
||||||
import aqua.model.func.Call
|
import aqua.model.func.Call
|
||||||
import aqua.model.func.raw.FuncOps
|
import aqua.model.func.raw.FuncOps
|
||||||
import aqua.model.func.resolved.{MakeRes, ResolvedOp, SeqRes, XorRes}
|
import aqua.model.func.resolved.{MakeRes, ResolvedOp, XorRes}
|
||||||
import aqua.types.ScalarType
|
import aqua.types.ScalarType
|
||||||
import cats.Eval
|
import cats.Eval
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
@ -295,31 +295,10 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
callRes(2, initPeer)
|
callRes(2, initPeer)
|
||||||
)
|
)
|
||||||
|
|
||||||
// println(Console.BLUE + init)
|
|
||||||
// println(Console.YELLOW + proc)
|
|
||||||
// println(Console.MAGENTA + expected)
|
|
||||||
// println(Console.RESET)
|
|
||||||
|
|
||||||
proc.equalsOrPrintDiff(expected) should be(true)
|
proc.equalsOrPrintDiff(expected) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"topology resolver" should "not stackoverflow" in {
|
"topology resolver" should "not stackoverflow" in {
|
||||||
/*
|
|
||||||
OnTag(LiteralModel(%init_peer_id%,ScalarType(string)),Chain(VarModel(-relay-,ScalarType(string),Chain()))) {
|
|
||||||
SeqTag{
|
|
||||||
CallServiceTag(LiteralModel("getDataSrv",ScalarType(string)),-relay-,Call(List(),Some(Export(-relay-,ScalarType(string)))),None)
|
|
||||||
CallServiceTag(LiteralModel("getDataSrv",ScalarType(string)),node_id,Call(List(),Some(Export(node_id,ScalarType(string)))),None)
|
|
||||||
CallServiceTag(LiteralModel("getDataSrv",ScalarType(string)),viaAr,Call(List(),Some(Export(viaAr,[]ScalarType(string)))),None)
|
|
||||||
OnTag(VarModel(node_id,ScalarType(string),Chain()),Chain(VarModel(viaAr,[]ScalarType(string),Chain()))) {
|
|
||||||
CallServiceTag(LiteralModel("cid",Literal(string)),ids,Call(List(),Some(Export(p,ScalarType(string)))),None)
|
|
||||||
}
|
|
||||||
OnTag(LiteralModel(%init_peer_id%,ScalarType(string)),Chain(VarModel(-relay-,ScalarType(string),Chain()))) {
|
|
||||||
CallServiceTag(LiteralModel("callbackSrv",ScalarType(string)),response,Call(List(VarModel(p,ScalarType(string),Chain())),None),None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
val init = on(
|
val init = on(
|
||||||
initPeer,
|
initPeer,
|
||||||
relay :: Nil,
|
relay :: Nil,
|
||||||
@ -394,11 +373,6 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
callRes(3, initPeer)
|
callRes(3, initPeer)
|
||||||
)
|
)
|
||||||
|
|
||||||
// println(Console.BLUE + init)
|
|
||||||
// println(Console.YELLOW + proc)
|
|
||||||
// println(Console.MAGENTA + expected)
|
|
||||||
// println(Console.RESET)
|
|
||||||
|
|
||||||
proc.equalsOrPrintDiff(expected) should be(true)
|
proc.equalsOrPrintDiff(expected) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,11 +427,6 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
callRes(4, initPeer)
|
callRes(4, initPeer)
|
||||||
)
|
)
|
||||||
|
|
||||||
// println(Console.BLUE + init)
|
|
||||||
println(Console.YELLOW + proc)
|
|
||||||
println(Console.MAGENTA + expected)
|
|
||||||
println(Console.RESET)
|
|
||||||
|
|
||||||
Node.equalsOrPrintDiff(proc, expected) should be(true)
|
Node.equalsOrPrintDiff(proc, expected) should be(true)
|
||||||
}
|
}
|
||||||
|
|
35
parser/src/test/scala/aqua/parser/CoExprSpec.scala
Normal file
35
parser/src/test/scala/aqua/parser/CoExprSpec.scala
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package aqua.parser
|
||||||
|
|
||||||
|
import aqua.AquaSpec
|
||||||
|
import aqua.parser.expr.{CallArrowExpr, CoExpr}
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
|
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||||
|
import cats.data.Chain
|
||||||
|
import cats.free.Cofree
|
||||||
|
import cats.{Eval, Id}
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
|
class CoExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||||
|
|
||||||
|
"co" should "be parsed" in {
|
||||||
|
CoExpr.readLine[Id].parseAll("co x <- y()").value should be(
|
||||||
|
Cofree[Chain, Expr[Id]](
|
||||||
|
CoExpr[Id](Token.lift[Id, Unit](())),
|
||||||
|
Eval.now(
|
||||||
|
Chain(
|
||||||
|
Cofree[Chain, Expr[Id]](
|
||||||
|
CallArrowExpr(
|
||||||
|
Some(AquaSpec.toName("x")),
|
||||||
|
None,
|
||||||
|
AquaSpec.toName("y"),
|
||||||
|
Nil
|
||||||
|
),
|
||||||
|
Eval.now(Chain.empty)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user