Updating compiler backends (#243)

* Updating compiler backends: add FuncRes

* TypeScriptService

* ServiceRes
This commit is contained in:
Dmitry Kurinskiy 2021-08-16 16:59:36 +03:00 committed by GitHub
parent 38fb824b68
commit 6c498b029b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 236 additions and 179 deletions

View File

@ -1,17 +1,15 @@
package aqua.backend.air package aqua.backend.air
import aqua.backend.{Backend, Generated} import aqua.backend.{Backend, Generated}
import aqua.model.AquaContext import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig import cats.syntax.show.*
import cats.implicits.toShow
object AirBackend extends Backend { object AirBackend extends Backend {
val ext = ".air" val ext = ".air"
override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { override def generate(aqua: AquaRes): Seq[Generated] = {
context.funcs.values.toList.map(fc => aqua.funcs.toList
Generated("." + fc.funcName + ext, FuncAirGen(fc).generateAir(genConf).show) .map(fr => Generated("." + fr.funcName + ext, FuncAirGen(fr).generate.show))
)
} }
} }

View File

@ -1,15 +1,14 @@
package aqua.backend.air package aqua.backend.air
import aqua.model.func.FuncCallable import aqua.model.res.FuncRes
import aqua.model.transform.{GenerationConfig, Transform}
case class FuncAirGen(func: FuncCallable) { case class FuncAirGen(func: FuncRes) {
/** /**
* Generates AIR from the function body * Generates AIR from the function body
*/ */
def generateAir(conf: GenerationConfig = GenerationConfig()): Air = def generate: Air =
AirGen( AirGen(
Transform.forClient(func, conf) func.body
).generate ).generate
} }

View File

@ -1,23 +1,25 @@
package aqua.backend.js package aqua.backend.js
import aqua.backend.{Backend, Generated} import aqua.backend.{Backend, Generated}
import aqua.model.AquaContext import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig
import cats.data.NonEmptyChain import cats.data.NonEmptyChain
object JavaScriptBackend extends Backend { object JavaScriptBackend extends Backend {
val ext = ".js" val ext = ".js"
override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { override def generate(aqua: AquaRes): Seq[Generated] = {
val funcs = NonEmptyChain.fromSeq(context.funcs.values.toSeq).map(_.map(JavaScriptFunc(_))) val funcs = NonEmptyChain.fromChain(
aqua.funcs
.map(JavaScriptFunc(_))
)
funcs funcs
.map(fs => .map(fs =>
Seq( Seq(
Generated( Generated(
ext, ext,
JavaScriptFile.Header + "\n\n" + fs JavaScriptFile.Header + "\n\n" + fs
.map(_.generateJavascript(genConf)) .map(_.generate)
.toChain .toChain
.toList .toList
.mkString("\n\n") .mkString("\n\n")

View File

@ -1,18 +1,6 @@
package aqua.backend.js package aqua.backend.js
import aqua.backend.Version import aqua.backend.Version
import aqua.model.AquaContext
import aqua.model.transform.GenerationConfig
import cats.data.Chain
case class JavaScriptFile(context: AquaContext) {
def funcs: Chain[JavaScriptFunc] =
Chain.fromSeq(context.funcs.values.toSeq).map(JavaScriptFunc(_))
def generateJS(conf: GenerationConfig = GenerationConfig()): String =
JavaScriptFile.Header + "\n\n" + funcs.map(_.generateJavascript(conf)).toList.mkString("\n\n")
}
object JavaScriptFile { object JavaScriptFile {

View File

@ -1,25 +1,22 @@
package aqua.backend.js package aqua.backend.js
import aqua.backend.air.FuncAirGen import aqua.backend.air.FuncAirGen
import aqua.model.func.FuncCallable import aqua.model.res.FuncRes
import aqua.model.transform.GenerationConfig import aqua.types.*
import aqua.types._ import cats.syntax.show.*
import cats.syntax.show._
case class JavaScriptFunc(func: FuncCallable) { case class JavaScriptFunc(func: FuncRes) {
import JavaScriptFunc._ import JavaScriptFunc._
import FuncRes._
import func._
def argsJavaScript: String = def argsJavaScript: String =
func.argNames.mkString(", ") argNames.mkString(", ")
// TODO: use common functions between TypeScript and JavaScript backends // TODO: use common functions between TypeScript and JavaScript backends
private def genReturnCallback( private def returnCallback: String = returnType.fold("") { retType =>
retType: Type, val respBody = retType match {
callbackService: String,
respFuncName: String
): String = {
val body = retType match {
case OptionType(_) => case OptionType(_) =>
""" let [opt] = args; """ let [opt] = args;
| if (Array.isArray(opt)) { | if (Array.isArray(opt)) {
@ -44,42 +41,38 @@ case class JavaScriptFunc(func: FuncCallable) {
| resolve(res);""".stripMargin | resolve(res);""".stripMargin
} }
s"""h.onEvent('$callbackService', '$respFuncName', (args) => { s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
| $body | $respBody
|}); |});
|""".stripMargin |""".stripMargin
} }
def generateJavascript(conf: GenerationConfig = GenerationConfig()): String = { def generate: String = {
val tsAir = FuncAirGen(func).generateAir(conf) val jsAir = FuncAirGen(func).generate
val setCallbacks = func.args.collect { val setCallbacks = func.args.collect {
case (argName, OptionType(_)) => case Arg(argName, OptionType(_)) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});""" s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});"""
case (argName, _: DataType) => case Arg(argName, _: DataType) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});""" s"""h.on('$dataServiceId, '$argName', () => {return $argName;});"""
case (argName, at: ArrowType) => case Arg(argName, at: ArrowType) =>
val value = s"$argName(${argsCallToJs( val value = s"$argName(${argsCallToJs(
at at
)})" )})"
val expr = at.codomain.uncons.fold(s"$value; return {}")(_ => s"return $value") val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
} }
.mkString("\n") .mkString("\n")
val returnCallback = func.arrowType.codomain.uncons
.map(_ => genReturnCallback(func.arrowType.codomain, conf.callbackService, conf.respFuncName))
.getOrElse("")
val returnVal = val returnVal =
func.ret.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise") returnType.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
// TODO: it could be non-unique val clientArgName = genArgName("client")
val configArgName = "config" val configArgName = genArgName("config")
s""" s"""
|export async function ${func.funcName}(client${if (func.args.isEmpty) "" |export async function ${func.funcName}(${clientArgName}${if (func.args.isEmpty) ""
else ", "}${argsJavaScript}, $configArgName) { else ", "}${argsJavaScript}, $configArgName) {
| let request; | let request;
| $configArgName = $configArgName || {}; | $configArgName = $configArgName || {};
@ -88,18 +81,18 @@ case class JavaScriptFunc(func: FuncCallable) {
| .disableInjections() | .disableInjections()
| .withRawScript( | .withRawScript(
| ` | `
|${tsAir.show} |${jsAir.show}
| `, | `,
| ) | )
| .configHandler((h) => { | .configHandler((h) => {
| ${conf.relayVarName.fold("") { r => | ${relayVarName.fold("") { r =>
s"""h.on('${conf.getDataService}', '$r', () => { s"""h.on('$dataServiceId', '$r', () => {
| return client.relayPeerId; | return client.relayPeerId;
| });""".stripMargin | });""".stripMargin
}} }}
| $setCallbacks | $setCallbacks
| $returnCallback | $returnCallback
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { | h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
| // assuming error is the single argument | // assuming error is the single argument
| const [err] = args; | const [err] = args;
| reject(err); | reject(err);
@ -107,14 +100,14 @@ case class JavaScriptFunc(func: FuncCallable) {
| }) | })
| .handleScriptError(reject) | .handleScriptError(reject)
| .handleTimeout(() => { | .handleTimeout(() => {
| reject('Request timed out for ${func.funcName}'); | reject('Request timed out for ${funcName}');
| }) | })
| if(${configArgName}.ttl) { | if(${configArgName}.ttl) {
| r.withTTL(${configArgName}.ttl) | r.withTTL(${configArgName}.ttl)
| } | }
| request = r.build(); | request = r.build();
| }); | });
| await client.initiateFlow(request); | await ${clientArgName}.initiateFlow(request);
| return ${returnVal}; | return ${returnVal};
|} |}
""".stripMargin """.stripMargin
@ -124,10 +117,7 @@ case class JavaScriptFunc(func: FuncCallable) {
object JavaScriptFunc { object JavaScriptFunc {
def argsToTs(at: ArrowType): String =
at.domain.toLabelledList().map(_._1).mkString(", ")
def argsCallToJs(at: ArrowType): String = def argsCallToJs(at: ArrowType): String =
at.domain.toList.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ") FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]").mkString(", ")
} }

View File

@ -1,7 +1,6 @@
package aqua.backend package aqua.backend
import aqua.model.AquaContext import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig
/** /**
* Compiler backend generates output based on the processed model * Compiler backend generates output based on the processed model
@ -9,11 +8,10 @@ import aqua.model.transform.GenerationConfig
trait Backend { trait Backend {
/** /**
* Generate the result based on the given [[AquaContext]] and [[GenerationConfig]] * Generate the result based on the given [[AquaRes]]
* *
* @param context Source file context, processed, transformed * @param aqua Source file context, processed, transformed
* @param genConf Generation configuration
* @return Zero or more [[Generated]] objects, based on arguments * @return Zero or more [[Generated]] objects, based on arguments
*/ */
def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] def generate(aqua: AquaRes): Seq[Generated]
} }

View File

@ -1,29 +1,13 @@
package aqua.backend.ts package aqua.backend.ts
import aqua.backend.{Backend, Generated} import aqua.backend.{Backend, Generated}
import aqua.model.AquaContext import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig
import cats.data.NonEmptyChain import cats.data.NonEmptyChain
object TypeScriptBackend extends Backend { object TypeScriptBackend extends Backend {
val ext = ".ts" val ext = ".ts"
override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { override def generate(res: AquaRes): Seq[Generated] =
val funcs = NonEmptyChain.fromSeq(context.funcs.values.toSeq).map(_.map(TypeScriptFunc(_))) if (res.isEmpty) Nil else Generated(ext, TypeScriptFile(res).generate) :: Nil
funcs
.map(fs =>
Seq(
Generated(
ext,
TypeScriptFile.Header + "\n\n" + fs
.map(_.generateTypescript(genConf))
.toChain
.toList
.mkString("\n\n")
)
)
)
.getOrElse(Seq.empty)
}
} }

View File

@ -1,17 +1,22 @@
package aqua.backend.ts package aqua.backend.ts
import aqua.backend.Version import aqua.backend.Version
import aqua.model.AquaContext import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig
import cats.data.Chain
case class TypeScriptFile(context: AquaContext) { case class TypeScriptFile(res: AquaRes) {
def funcs: Chain[TypeScriptFunc] = import TypeScriptFile.Header
Chain.fromSeq(context.funcs.values.toSeq).map(TypeScriptFunc(_))
def generate: String =
s"""${Header}
|
|// Services
|${res.services.map(TypeScriptService(_)).map(_.generate).toList.mkString("\n\n")}
|
|// Functions
|${res.funcs.map(TypeScriptFunc(_)).map(_.generate).toList.mkString("\n\n")}
|""".stripMargin
def generateTS(conf: GenerationConfig = GenerationConfig()): String =
TypeScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(conf)).toList.mkString("\n\n")
} }
object TypeScriptFile { object TypeScriptFile {

View File

@ -1,33 +1,21 @@
package aqua.backend.ts package aqua.backend.ts
import aqua.backend.air.FuncAirGen import aqua.backend.air.FuncAirGen
import aqua.model.func.FuncCallable import aqua.model.res.FuncRes
import aqua.model.transform.GenerationConfig import aqua.types.*
import aqua.types._ import cats.syntax.show.*
import cats.syntax.show._
case class TypeScriptFunc(func: FuncCallable) { case class TypeScriptFunc(func: FuncRes) {
import TypeScriptFunc._ import TypeScriptFunc._
import FuncRes._
import func._
def argsTypescript: String = def argsTypescript: String =
func.arrowType.domain.toLabelledList().map(ad => s"${ad._1}: " + typeToTs(ad._2)).mkString(", ") args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
def generateUniqueArgName(args: List[String], basis: String, attempt: Int): String = { private def returnCallback: String = returnType.fold("") { retType =>
val name = if (attempt == 0) { val respBody = retType match {
basis
} else {
basis + attempt
}
args.find(_ == name).map(_ => generateUniqueArgName(args, basis, attempt + 1)).getOrElse(name)
}
private def genReturnCallback(
retType: Type,
callbackService: String,
respFuncName: String
): String = {
val body = retType match {
case OptionType(_) => case OptionType(_) =>
""" let [opt] = args; """ let [opt] = args;
| if (Array.isArray(opt)) { | if (Array.isArray(opt)) {
@ -52,52 +40,43 @@ case class TypeScriptFunc(func: FuncCallable) {
| resolve(res);""".stripMargin | resolve(res);""".stripMargin
} }
s"""h.onEvent('$callbackService', '$respFuncName', (args) => { s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => {
| $body | $respBody
|}); |});
|""".stripMargin |""".stripMargin
} }
def generateTypescript(conf: GenerationConfig = GenerationConfig()): String = { def generate: String = {
val tsAir = FuncAirGen(func).generateAir(conf) val tsAir = FuncAirGen(func).generate
// TODO: support multi return val retTypeTs = func.returnType
val retType =
if (func.arrowType.codomain.length > 1) Some(func.arrowType.codomain)
else func.arrowType.codomain.uncons.map(_._1)
val retTypeTs = retType
.fold("void")(typeToTs) .fold("void")(typeToTs)
val returnCallback = retType
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
.getOrElse("")
val setCallbacks = func.args.collect { // Product types are not handled val setCallbacks = func.args.collect { // Product types are not handled
case (argName, OptionType(_)) => case Arg(argName, OptionType(_)) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});""" s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});"""
case (argName, _: DataType) => case Arg(argName, _: DataType) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});""" s"""h.on('$dataServiceId', '$argName', () => {return $argName;});"""
case (argName, at: ArrowType) => case Arg(argName, at: ArrowType) =>
val value = s"$argName(${argsCallToTs( val value = s"$argName(${argsCallToTs(
at at
)})" )})"
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value") val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value")
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});"""
} }
.mkString("\n") .mkString("\n")
// TODO support multi return
val returnVal = val returnVal =
func.ret.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise") returnType.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
val clientArgName = generateUniqueArgName(func.argNames, "client", 0) val clientArgName = genArgName("client")
val configArgName = generateUniqueArgName(func.argNames, "config", 0) val configArgName = genArgName("config")
val configType = "{ttl?: number}" val configType = "{ttl?: number}"
s""" s"""
|export async function ${func.funcName}($clientArgName: FluenceClient${if (func.args.isEmpty) |export async function ${funcName}($clientArgName: FluenceClient${if (args.isEmpty)
"" ""
else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retTypeTs> { else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retTypeTs> {
| let request: RequestFlow; | let request: RequestFlow;
@ -110,14 +89,14 @@ case class TypeScriptFunc(func: FuncCallable) {
| `, | `,
| ) | )
| .configHandler((h) => { | .configHandler((h) => {
| ${conf.relayVarName.fold("") { r => | ${relayVarName.fold("") { r =>
s"""h.on('${conf.getDataService}', '$r', () => { s"""h.on('$dataServiceId', '$r', () => {
| return $clientArgName.relayPeerId!; | return $clientArgName.relayPeerId!;
| });""".stripMargin | });""".stripMargin
}} }}
| $setCallbacks | $setCallbacks
| $returnCallback | $returnCallback
| h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { | h.onEvent('$errorHandlerId', '$errorFuncName', (args) => {
| // assuming error is the single argument | // assuming error is the single argument
| const [err] = args; | const [err] = args;
| reject(err); | reject(err);
@ -125,7 +104,7 @@ case class TypeScriptFunc(func: FuncCallable) {
| }) | })
| .handleScriptError(reject) | .handleScriptError(reject)
| .handleTimeout(() => { | .handleTimeout(() => {
| reject('Request timed out for ${func.funcName}'); | reject('Request timed out for ${funcName}');
| }) | })
| if(${configArgName} && ${configArgName}.ttl) { | if(${configArgName} && ${configArgName}.ttl) {
| r.withTTL(${configArgName}.ttl) | r.withTTL(${configArgName}.ttl)
@ -158,20 +137,18 @@ object TypeScriptFunc {
case lt: LiteralType if lt.oneOf(ScalarType.string) => "string" case lt: LiteralType if lt.oneOf(ScalarType.string) => "string"
case _: DataType => "any" case _: DataType => "any"
case at: ArrowType => case at: ArrowType =>
s"(${argsToTs(at)}) => ${at.res s"(${argsToTs(at)}) => ${FuncRes
.arrowToRes(at)
.fold("void")(typeToTs)}" .fold("void")(typeToTs)}"
case _ =>
// TODO: handle product types in returns
"any"
} }
def argsToTs(at: ArrowType): String = def argsToTs(at: ArrowType): String =
at.domain FuncRes
.toLabelledList() .arrowArgs(at)
.map(nt => nt._1 + ": " + typeToTs(nt._2)) .map(nt => nt.name + ": " + typeToTs(nt.`type`))
.mkString(", ") .mkString(", ")
def argsCallToTs(at: ArrowType): String = def argsCallToTs(at: ArrowType): String =
at.domain.toList.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ") FuncRes.arrowArgIndices(at).map(idx => s"args[$idx]").mkString(", ")
} }

View File

@ -0,0 +1,20 @@
package aqua.backend.ts
import aqua.model.res.ServiceRes
case class TypeScriptService(srv: ServiceRes) {
import TypeScriptFunc.typeToTs
def generate: String =
s"""
|//${srv.name}
|//defaultId = ${srv.defaultId.getOrElse("undefined")}
|
|${srv.members.map { case (n, v) =>
s"//${n}: ${typeToTs(v)}"
}.mkString("\n")}
|//END ${srv.name}
|
|""".stripMargin
}

View File

@ -3,15 +3,16 @@ package aqua.compiler
import aqua.backend.Backend import aqua.backend.Backend
import aqua.linker.Linker import aqua.linker.Linker
import aqua.model.AquaContext import aqua.model.AquaContext
import aqua.model.res.AquaRes
import aqua.model.transform.GenerationConfig import aqua.model.transform.GenerationConfig
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
import aqua.semantics.Semantics import aqua.semantics.Semantics
import cats.data.Validated.{validNec, Invalid, Valid} import cats.data.Validated.{validNec, Invalid, Valid}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.syntax.applicative._ import cats.syntax.applicative.*
import cats.syntax.flatMap._ import cats.syntax.flatMap.*
import cats.syntax.functor._ import cats.syntax.functor.*
import cats.syntax.traverse._ import cats.syntax.traverse.*
import cats.{Comonad, Monad} import cats.{Comonad, Monad}
object AquaCompiler { object AquaCompiler {
@ -47,7 +48,7 @@ object AquaCompiler {
} }
.map( .map(
_.map { ap => _.map { ap =>
val compiled = backend.generate(ap.context, config) val compiled = backend.generate(AquaRes.fromContext(ap.context, config))
AquaCompiled(ap.id, compiled) AquaCompiled(ap.id, compiled)
} }
) )

View File

@ -0,0 +1,18 @@
package aqua.model.res
import aqua.model.AquaContext
import aqua.model.transform.{GenerationConfig, Transform}
import cats.data.Chain
case class AquaRes(funcs: Chain[FuncRes], services: Chain[ServiceRes]) {
def isEmpty: Boolean = funcs.isEmpty && services.isEmpty
}
object AquaRes {
def fromContext(ctx: AquaContext, conf: GenerationConfig): AquaRes =
AquaRes(
funcs = Chain.fromSeq(ctx.funcs.values.toSeq).map(Transform.fn(_, conf)),
services = Chain.fromSeq(ctx.services.values.toSeq).map(ServiceRes.fromModel(_))
)
}

View File

@ -0,0 +1,51 @@
package aqua.model.res
import aqua.model.func.FuncCallable
import aqua.model.func.resolved.ResolvedOp
import aqua.model.transform.GenerationConfig
import aqua.types.{ArrowType, Type}
import cats.data.Chain
import cats.free.Cofree
case class FuncRes(
source: FuncCallable,
conf: GenerationConfig,
body: Cofree[Chain, ResolvedOp]
) {
import FuncRes._
lazy val funcName = source.funcName
lazy val args: List[Arg] = arrowArgs(source.arrowType)
def argNames: List[String] = source.argNames
def relayVarName: Option[String] = conf.relayVarName
def dataServiceId: String = conf.getDataService
def callbackServiceId: String = conf.callbackService
def respFuncName: String = conf.respFuncName
def errorHandlerId: String = conf.errorHandlingService
def errorFuncName: String = conf.errorFuncName
def genArgName(basis: String): String = {
val forbidden = args.map(_._1).toSet
def genIter(i: Int): String = {
val n = if (i < 0) basis else basis + i
if (forbidden(n)) genIter(i + 1) else n
}
genIter(-1)
}
def returnType: Option[Type] = arrowToRes(source.arrowType)
}
object FuncRes {
case class Arg(name: String, `type`: Type)
def arrowArgs(at: ArrowType): List[Arg] = at.domain.toLabelledList().map(Arg(_, _))
def arrowArgIndices(at: ArrowType): List[Int] =
LazyList.from(0).take(at.domain.length).toList
def arrowToRes(at: ArrowType): Option[Type] =
if (at.codomain.length > 1) Some(at.codomain)
else at.codomain.uncons.map(_._1)
}

View File

@ -0,0 +1,20 @@
package aqua.model.res
import aqua.model.ServiceModel
import aqua.types.{ArrowType, ScalarType}
import aqua.model.LiteralModel
case class ServiceRes(name: String, members: List[(String, ArrowType)], defaultId: Option[String])
object ServiceRes {
def fromModel(sm: ServiceModel): ServiceRes =
ServiceRes(
name = sm.name,
members = sm.arrows.toNel.toList,
defaultId = sm.defaultId.collect {
case LiteralModel(value, t) if ScalarType.string.acceptsValueOf(t) =>
value
}
)
}

View File

@ -3,6 +3,7 @@ package aqua.model.transform
import aqua.model.func.FuncCallable import aqua.model.func.FuncCallable
import aqua.model.VarModel import aqua.model.VarModel
import aqua.model.func.resolved.{NoAir, ResolvedOp} import aqua.model.func.resolved.{NoAir, ResolvedOp}
import aqua.model.res.FuncRes
import aqua.model.topology.Topology import aqua.model.topology.Topology
import aqua.types.ScalarType import aqua.types.ScalarType
import cats.data.Chain import cats.data.Chain
@ -22,7 +23,7 @@ object Transform extends Logging {
): Cofree[Chain, ResolvedOp] = ): Cofree[Chain, ResolvedOp] =
tree.copy(tail = tree.tail.map(_.filter(t => filter(t.head)).map(clear(_, filter)))) tree.copy(tail = tree.tail.map(_.filter(t => filter(t.head)).map(clear(_, filter))))
def forClient(func: FuncCallable, conf: GenerationConfig): Cofree[Chain, ResolvedOp] = { def fn(func: FuncCallable, conf: GenerationConfig): FuncRes = {
val initCallable: InitPeerCallable = InitViaRelayCallable( val initCallable: InitPeerCallable = InitViaRelayCallable(
Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string)) Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string))
) )
@ -48,13 +49,18 @@ object Transform extends Logging {
callback, callback,
conf.respFuncName conf.respFuncName
) )
clear(
Topology.resolve( FuncRes(
errorsCatcher func,
.transform( conf,
wrapFunc.resolve(func).value clear(
) Topology.resolve(
.tree errorsCatcher
.transform(
wrapFunc.resolve(func).value
)
.tree
)
) )
) )
} }

View File

@ -30,9 +30,9 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val bc = GenerationConfig() val bc = GenerationConfig()
val fc = Transform.forClient(func, bc) val fc = Transform.fn(func, bc)
val procFC: Node.Res = fc val procFC: Node.Res = fc.body
val expectedFC: Node.Res = val expectedFC: Node.Res =
MakeRes.xor( MakeRes.xor(
@ -80,9 +80,9 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val bc = GenerationConfig(wrapWithXor = false) val bc = GenerationConfig(wrapWithXor = false)
val fc = Transform.forClient(func, bc) val fc = Transform.fn(func, bc)
val procFC: Res = fc val procFC: Res = fc.body
val expectedFC: Res = val expectedFC: Res =
MakeRes.seq( MakeRes.seq(
@ -141,7 +141,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val bc = GenerationConfig(wrapWithXor = false) val bc = GenerationConfig(wrapWithXor = false)
val res = Transform.forClient(f2, bc): Node.Res val res = Transform.fn(f2, bc).body: Node.Res
res.equalsOrPrintDiff( res.equalsOrPrintDiff(
MakeRes.seq( MakeRes.seq(