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(
|
||||
Transform.forClient(func, conf)
|
||||
).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 = {
|
||||
|
||||
val tsAir = FuncAirGen(func).generateClientAir(conf)
|
||||
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||
|
||||
val returnCallback = func.ret.as {
|
||||
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
||||
@ -86,8 +86,7 @@ case class JavaScriptFunc(func: FuncCallable) {
|
||||
object JavaScriptFunc {
|
||||
|
||||
def argsToTs(at: ArrowType): String =
|
||||
at.args
|
||||
.zipWithIndex
|
||||
at.args.zipWithIndex
|
||||
.map(_.swap)
|
||||
.map(kv => "arg" + kv._1)
|
||||
.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 cats.data.Chain
|
||||
|
||||
case class TypescriptFile(context: AquaContext) {
|
||||
case class TypeScriptFile(context: AquaContext) {
|
||||
|
||||
def funcs: Chain[TypescriptFunc] =
|
||||
Chain.fromSeq(context.funcs.values.toSeq).map(TypescriptFunc(_))
|
||||
def funcs: Chain[TypeScriptFunc] =
|
||||
Chain.fromSeq(context.funcs.values.toSeq).map(TypeScriptFunc(_))
|
||||
|
||||
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 =
|
||||
s"""/**
|
@ -7,9 +7,9 @@ import aqua.types._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.show._
|
||||
|
||||
case class TypescriptFunc(func: FuncCallable) {
|
||||
case class TypeScriptFunc(func: FuncCallable) {
|
||||
|
||||
import TypescriptFunc._
|
||||
import TypeScriptFunc._
|
||||
|
||||
def argsTypescript: String =
|
||||
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 = {
|
||||
|
||||
val tsAir = FuncAirGen(func).generateClientAir(conf)
|
||||
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||
|
||||
val returnCallback = func.ret.as {
|
||||
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 {
|
||||
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
|
||||
)
|
||||
)
|
||||
.dependsOn(semantics, `backend-air`, `backend-ts`, `backend-js`, linker)
|
||||
.dependsOn(semantics, `backend-air`, `backend-ts`, `backend-js`, linker, backend)
|
||||
|
||||
lazy val types = project
|
||||
.settings(commons)
|
||||
@ -94,6 +94,7 @@ lazy val model = project
|
||||
.dependsOn(types)
|
||||
|
||||
lazy val `test-kit` = project
|
||||
.in(file("model/test-kit"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(model)
|
||||
|
||||
@ -107,10 +108,15 @@ lazy val semantics = project
|
||||
)
|
||||
.dependsOn(model, `test-kit` % Test, parser)
|
||||
|
||||
lazy val backend = project
|
||||
.in(file("backend"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(model)
|
||||
|
||||
lazy val `backend-air` = project
|
||||
.in(file("backend/air"))
|
||||
.settings(commons: _*)
|
||||
.dependsOn(model)
|
||||
.dependsOn(backend)
|
||||
|
||||
lazy val `backend-ts` = project
|
||||
.in(file("backend/ts"))
|
||||
|
@ -1,24 +1,22 @@
|
||||
package aqua
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.backend.js.JavaScriptFile
|
||||
import aqua.backend.ts.TypescriptFile
|
||||
import aqua.io.{AquaFileError, AquaFiles, FileModuleId, Unresolvable}
|
||||
import aqua.backend.Backend
|
||||
import aqua.backend.air.AirBackend
|
||||
import aqua.backend.js.JavaScriptBackend
|
||||
import aqua.backend.ts.TypeScriptBackend
|
||||
import aqua.io._
|
||||
import aqua.linker.Linker
|
||||
import aqua.model.AquaContext
|
||||
import aqua.model.transform.BodyConfig
|
||||
import aqua.parser.lift.FileSpan
|
||||
import aqua.semantics.{RulesViolated, SemanticError, Semantics}
|
||||
import cats.Applicative
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data._
|
||||
import cats.effect.kernel.Concurrent
|
||||
import cats.kernel.Monoid
|
||||
import cats.syntax.flatMap._
|
||||
import cats.syntax.functor._
|
||||
import cats.syntax.show._
|
||||
import cats.{Applicative, Monad}
|
||||
import fs2.io.file.Files
|
||||
import fs2.text
|
||||
import wvlet.log.LogSupport
|
||||
|
||||
import java.nio.file.Path
|
||||
@ -29,36 +27,38 @@ object AquaCompiler extends LogSupport {
|
||||
case object JavaScriptTarget 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](
|
||||
srcPath: Path,
|
||||
imports: LazyList[Path],
|
||||
@ -81,21 +81,7 @@ object AquaCompiler extends LogSupport {
|
||||
ids => Unresolvable(ids.map(_.id.file.toString).mkString(" -> "))
|
||||
) match {
|
||||
case Validated.Valid(files) ⇒
|
||||
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 => (errs, errsSet, preps :+ Prepared(modId.file, srcPath, targetPath, c))
|
||||
)
|
||||
}
|
||||
NonEmptyChain
|
||||
.fromChain(errs)
|
||||
.fold(Validated.validNec[String, Chain[Prepared]](preps))(Validated.invalid)
|
||||
gatherPreparedFiles(srcPath, targetPath, files)
|
||||
|
||||
case Validated.Invalid(errs) ⇒
|
||||
Validated.invalid(
|
||||
@ -118,6 +104,38 @@ object AquaCompiler extends LogSupport {
|
||||
"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](
|
||||
srcPath: Path,
|
||||
imports: LazyList[Path],
|
||||
@ -129,116 +147,44 @@ object AquaCompiler extends LogSupport {
|
||||
prepareFiles(srcPath, imports, targetPath)
|
||||
.map(_.map(_.filter { p =>
|
||||
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
|
||||
}))
|
||||
.flatMap[ValidatedNec[String, Chain[String]]] {
|
||||
case Validated.Invalid(e) =>
|
||||
Applicative[F].pure(Validated.invalid(e))
|
||||
case Validated.Valid(preps) =>
|
||||
(compileTo match {
|
||||
case TypescriptTarget =>
|
||||
preps.map { p =>
|
||||
p.targetPath("ts") match {
|
||||
case Invalid(t) =>
|
||||
EitherT.pure(t.getMessage)
|
||||
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)"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val backend = targetToBackend(compileTo)
|
||||
val results = preps.toList
|
||||
.flatMap(p =>
|
||||
backend.generate(p.context, bodyConfig).map { compiled =>
|
||||
val targetPath = p.targetPath(
|
||||
p.srcFile.getFileName.toString.stripSuffix(".aqua") + compiled.suffix
|
||||
)
|
||||
|
||||
}).foldLeft(
|
||||
EitherT.rightT[F, NonEmptyChain[String]](Chain.empty[String])
|
||||
) { case (accET, writeET) =>
|
||||
EitherT(for {
|
||||
a <- accET.value
|
||||
w <- writeET.value
|
||||
} yield (a, w) 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)
|
||||
targetPath.fold(
|
||||
t => EitherT.leftT[F, Unit](t.getMessage),
|
||||
tp =>
|
||||
FileOps
|
||||
.writeFile(
|
||||
tp,
|
||||
compiled.content
|
||||
)
|
||||
.flatTap { _ =>
|
||||
EitherT.pure(
|
||||
Validated.catchNonFatal(
|
||||
info(
|
||||
s"Result ${tp.toAbsolutePath}: compilation OK (${p.context.funcs.size} functions)"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
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.functor._
|
||||
import fs2.io.file.Files
|
||||
import fs2.text
|
||||
|
||||
import java.nio.file.{Path, Paths}
|
||||
|
||||
@ -54,36 +53,25 @@ case class AquaFile(
|
||||
|
||||
object AquaFile {
|
||||
|
||||
def readSourceText[F[_]: Files: Concurrent](
|
||||
def readAst[F[_]: Files: Concurrent](
|
||||
file: Path
|
||||
): fs2.Stream[F, Either[AquaFileError, 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
|
||||
): fs2.Stream[F, Either[AquaFileError, (String, Ast[FileSpan.F])]] =
|
||||
FileOps
|
||||
.readSourceText[F](file)
|
||||
.map {
|
||||
_.left
|
||||
.map(t => FileSystemError(t))
|
||||
}
|
||||
|
||||
def readAst[F[_]: Files: Concurrent](
|
||||
file: Path
|
||||
): fs2.Stream[F, Either[AquaFileError, (String, Ast[FileSpan.F])]] =
|
||||
readSourceText[F](file).map(
|
||||
_.flatMap(source =>
|
||||
Aqua
|
||||
.parseFileString(file.toString, source)
|
||||
.map(source -> _)
|
||||
.toEither
|
||||
.left
|
||||
.map(AquaScriptErrors(_))
|
||||
.map(
|
||||
_.flatMap(source =>
|
||||
Aqua
|
||||
.parseFileString(file.toString, source)
|
||||
.map(source -> _)
|
||||
.toEither
|
||||
.left
|
||||
.map(AquaScriptErrors(_))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def read[F[_]: Files: Concurrent](file: Path): EitherT[F, AquaFileError, AquaFile] =
|
||||
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.func.Call
|
||||
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 cats.Eval
|
||||
import cats.data.Chain
|
||||
@ -295,31 +295,10 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
callRes(2, initPeer)
|
||||
)
|
||||
|
||||
// println(Console.BLUE + init)
|
||||
// println(Console.YELLOW + proc)
|
||||
// println(Console.MAGENTA + expected)
|
||||
// println(Console.RESET)
|
||||
|
||||
proc.equalsOrPrintDiff(expected) should be(true)
|
||||
}
|
||||
|
||||
"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(
|
||||
initPeer,
|
||||
relay :: Nil,
|
||||
@ -394,11 +373,6 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
callRes(3, initPeer)
|
||||
)
|
||||
|
||||
// println(Console.BLUE + init)
|
||||
// println(Console.YELLOW + proc)
|
||||
// println(Console.MAGENTA + expected)
|
||||
// println(Console.RESET)
|
||||
|
||||
proc.equalsOrPrintDiff(expected) should be(true)
|
||||
}
|
||||
|
||||
@ -453,11 +427,6 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
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)
|
||||
}
|
||||
|
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