From 6c498b029bc55f2a657c2ef1cdddb4681890de27 Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 16 Aug 2021 16:59:36 +0300 Subject: [PATCH] Updating compiler backends (#243) * Updating compiler backends: add FuncRes * TypeScriptService * ServiceRes --- .../scala/aqua/backend/air/AirBackend.scala | 12 +-- .../scala/aqua/backend/air/FuncAirGen.scala | 9 +- .../aqua/backend/js/JavaScriptBackend.scala | 12 ++- .../aqua/backend/js/JavaScriptFile.scala | 12 --- .../aqua/backend/js/JavaScriptFunc.scala | 72 +++++++------- .../src/main/scala/aqua/backend/Backend.scala | 10 +- .../aqua/backend/ts/TypeScriptBackend.scala | 22 +---- .../aqua/backend/ts/TypeScriptFile.scala | 21 +++-- .../aqua/backend/ts/TypeScriptFunc.scala | 93 +++++++------------ .../aqua/backend/ts/TypeScriptService.scala | 20 ++++ .../scala/aqua/compiler/AquaCompiler.scala | 11 ++- .../main/scala/aqua/model/res/AquaRes.scala | 18 ++++ .../main/scala/aqua/model/res/FuncRes.scala | 51 ++++++++++ .../scala/aqua/model/res/ServiceRes.scala | 20 ++++ .../aqua/model/transform/Transform.scala | 22 +++-- .../aqua/model/transform/TransformSpec.scala | 10 +- 16 files changed, 236 insertions(+), 179 deletions(-) create mode 100644 backend/ts/src/main/scala/aqua/backend/ts/TypeScriptService.scala create mode 100644 model/src/main/scala/aqua/model/res/AquaRes.scala create mode 100644 model/src/main/scala/aqua/model/res/FuncRes.scala create mode 100644 model/src/main/scala/aqua/model/res/ServiceRes.scala diff --git a/backend/air/src/main/scala/aqua/backend/air/AirBackend.scala b/backend/air/src/main/scala/aqua/backend/air/AirBackend.scala index 0bb02782..2eefe52a 100644 --- a/backend/air/src/main/scala/aqua/backend/air/AirBackend.scala +++ b/backend/air/src/main/scala/aqua/backend/air/AirBackend.scala @@ -1,17 +1,15 @@ package aqua.backend.air import aqua.backend.{Backend, Generated} -import aqua.model.AquaContext -import aqua.model.transform.GenerationConfig -import cats.implicits.toShow +import aqua.model.res.AquaRes +import cats.syntax.show.* object AirBackend extends Backend { val ext = ".air" - override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { - context.funcs.values.toList.map(fc => - Generated("." + fc.funcName + ext, FuncAirGen(fc).generateAir(genConf).show) - ) + override def generate(aqua: AquaRes): Seq[Generated] = { + aqua.funcs.toList + .map(fr => Generated("." + fr.funcName + ext, FuncAirGen(fr).generate.show)) } } diff --git a/backend/air/src/main/scala/aqua/backend/air/FuncAirGen.scala b/backend/air/src/main/scala/aqua/backend/air/FuncAirGen.scala index 3f5656b4..cfd6a2d3 100644 --- a/backend/air/src/main/scala/aqua/backend/air/FuncAirGen.scala +++ b/backend/air/src/main/scala/aqua/backend/air/FuncAirGen.scala @@ -1,15 +1,14 @@ package aqua.backend.air -import aqua.model.func.FuncCallable -import aqua.model.transform.{GenerationConfig, Transform} +import aqua.model.res.FuncRes -case class FuncAirGen(func: FuncCallable) { +case class FuncAirGen(func: FuncRes) { /** * Generates AIR from the function body */ - def generateAir(conf: GenerationConfig = GenerationConfig()): Air = + def generate: Air = AirGen( - Transform.forClient(func, conf) + func.body ).generate } diff --git a/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala b/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala index f03d4984..661e3a9c 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala @@ -1,23 +1,25 @@ package aqua.backend.js import aqua.backend.{Backend, Generated} -import aqua.model.AquaContext -import aqua.model.transform.GenerationConfig +import aqua.model.res.AquaRes import cats.data.NonEmptyChain object JavaScriptBackend extends Backend { val ext = ".js" - override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { - val funcs = NonEmptyChain.fromSeq(context.funcs.values.toSeq).map(_.map(JavaScriptFunc(_))) + override def generate(aqua: AquaRes): Seq[Generated] = { + val funcs = NonEmptyChain.fromChain( + aqua.funcs + .map(JavaScriptFunc(_)) + ) funcs .map(fs => Seq( Generated( ext, JavaScriptFile.Header + "\n\n" + fs - .map(_.generateJavascript(genConf)) + .map(_.generate) .toChain .toList .mkString("\n\n") diff --git a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala index 4293e22c..384e6281 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala @@ -1,18 +1,6 @@ package aqua.backend.js 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 { diff --git a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala index 49de8e86..09204d36 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala @@ -1,25 +1,22 @@ package aqua.backend.js import aqua.backend.air.FuncAirGen -import aqua.model.func.FuncCallable -import aqua.model.transform.GenerationConfig -import aqua.types._ -import cats.syntax.show._ +import aqua.model.res.FuncRes +import aqua.types.* +import cats.syntax.show.* -case class JavaScriptFunc(func: FuncCallable) { +case class JavaScriptFunc(func: FuncRes) { import JavaScriptFunc._ + import FuncRes._ + import func._ def argsJavaScript: String = - func.argNames.mkString(", ") + argNames.mkString(", ") // TODO: use common functions between TypeScript and JavaScript backends - private def genReturnCallback( - retType: Type, - callbackService: String, - respFuncName: String - ): String = { - val body = retType match { + private def returnCallback: String = returnType.fold("") { retType => + val respBody = retType match { case OptionType(_) => """ let [opt] = args; | if (Array.isArray(opt)) { @@ -44,42 +41,38 @@ case class JavaScriptFunc(func: FuncCallable) { | resolve(res);""".stripMargin } - s"""h.onEvent('$callbackService', '$respFuncName', (args) => { - | $body + s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => { + | $respBody |}); |""".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 { - case (argName, OptionType(_)) => - s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});""" - case (argName, _: DataType) => - s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});""" - case (argName, at: ArrowType) => + case Arg(argName, OptionType(_)) => + s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});""" + case Arg(argName, _: DataType) => + s"""h.on('$dataServiceId, '$argName', () => {return $argName;});""" + case Arg(argName, at: ArrowType) => val value = s"$argName(${argsCallToJs( at )})" - val expr = at.codomain.uncons.fold(s"$value; return {}")(_ => s"return $value") - s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" + val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value") + s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});""" } .mkString("\n") - val returnCallback = func.arrowType.codomain.uncons - .map(_ => genReturnCallback(func.arrowType.codomain, conf.callbackService, conf.respFuncName)) - .getOrElse("") - 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 configArgName = "config" + val clientArgName = genArgName("client") + val configArgName = genArgName("config") s""" - |export async function ${func.funcName}(client${if (func.args.isEmpty) "" + |export async function ${func.funcName}(${clientArgName}${if (func.args.isEmpty) "" else ", "}${argsJavaScript}, $configArgName) { | let request; | $configArgName = $configArgName || {}; @@ -88,18 +81,18 @@ case class JavaScriptFunc(func: FuncCallable) { | .disableInjections() | .withRawScript( | ` - |${tsAir.show} + |${jsAir.show} | `, | ) | .configHandler((h) => { - | ${conf.relayVarName.fold("") { r => - s"""h.on('${conf.getDataService}', '$r', () => { + | ${relayVarName.fold("") { r => + s"""h.on('$dataServiceId', '$r', () => { | return client.relayPeerId; | });""".stripMargin }} | $setCallbacks | $returnCallback - | h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { + | h.onEvent('$errorHandlerId', '$errorFuncName', (args) => { | // assuming error is the single argument | const [err] = args; | reject(err); @@ -107,14 +100,14 @@ case class JavaScriptFunc(func: FuncCallable) { | }) | .handleScriptError(reject) | .handleTimeout(() => { - | reject('Request timed out for ${func.funcName}'); + | reject('Request timed out for ${funcName}'); | }) | if(${configArgName}.ttl) { | r.withTTL(${configArgName}.ttl) | } | request = r.build(); | }); - | await client.initiateFlow(request); + | await ${clientArgName}.initiateFlow(request); | return ${returnVal}; |} """.stripMargin @@ -124,10 +117,7 @@ case class JavaScriptFunc(func: FuncCallable) { object JavaScriptFunc { - def argsToTs(at: ArrowType): String = - at.domain.toLabelledList().map(_._1).mkString(", ") - 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(", ") } diff --git a/backend/src/main/scala/aqua/backend/Backend.scala b/backend/src/main/scala/aqua/backend/Backend.scala index 8eeb365e..bf2003be 100644 --- a/backend/src/main/scala/aqua/backend/Backend.scala +++ b/backend/src/main/scala/aqua/backend/Backend.scala @@ -1,7 +1,6 @@ package aqua.backend -import aqua.model.AquaContext -import aqua.model.transform.GenerationConfig +import aqua.model.res.AquaRes /** * Compiler backend generates output based on the processed model @@ -9,11 +8,10 @@ import aqua.model.transform.GenerationConfig 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 genConf Generation configuration + * @param aqua Source file context, processed, transformed * @return Zero or more [[Generated]] objects, based on arguments */ - def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] + def generate(aqua: AquaRes): Seq[Generated] } diff --git a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptBackend.scala b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptBackend.scala index f1c6ecd0..c4687f4e 100644 --- a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptBackend.scala +++ b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptBackend.scala @@ -1,29 +1,13 @@ package aqua.backend.ts import aqua.backend.{Backend, Generated} -import aqua.model.AquaContext -import aqua.model.transform.GenerationConfig +import aqua.model.res.AquaRes import cats.data.NonEmptyChain object TypeScriptBackend extends Backend { val ext = ".ts" - override def generate(context: AquaContext, genConf: GenerationConfig): Seq[Generated] = { - val funcs = NonEmptyChain.fromSeq(context.funcs.values.toSeq).map(_.map(TypeScriptFunc(_))) - funcs - .map(fs => - Seq( - Generated( - ext, - TypeScriptFile.Header + "\n\n" + fs - .map(_.generateTypescript(genConf)) - .toChain - .toList - .mkString("\n\n") - ) - ) - ) - .getOrElse(Seq.empty) - } + override def generate(res: AquaRes): Seq[Generated] = + if (res.isEmpty) Nil else Generated(ext, TypeScriptFile(res).generate) :: Nil } diff --git a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFile.scala b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFile.scala index b441727c..aee2531c 100644 --- a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFile.scala +++ b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFile.scala @@ -1,17 +1,22 @@ package aqua.backend.ts import aqua.backend.Version -import aqua.model.AquaContext -import aqua.model.transform.GenerationConfig -import cats.data.Chain +import aqua.model.res.AquaRes -case class TypeScriptFile(context: AquaContext) { +case class TypeScriptFile(res: AquaRes) { - def funcs: Chain[TypeScriptFunc] = - Chain.fromSeq(context.funcs.values.toSeq).map(TypeScriptFunc(_)) + import TypeScriptFile.Header + + 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 { diff --git a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala index b6ada92a..4e80ad35 100644 --- a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala +++ b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala @@ -1,33 +1,21 @@ package aqua.backend.ts import aqua.backend.air.FuncAirGen -import aqua.model.func.FuncCallable -import aqua.model.transform.GenerationConfig -import aqua.types._ -import cats.syntax.show._ +import aqua.model.res.FuncRes +import aqua.types.* +import cats.syntax.show.* -case class TypeScriptFunc(func: FuncCallable) { +case class TypeScriptFunc(func: FuncRes) { import TypeScriptFunc._ + import FuncRes._ + import func._ 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 = { - val name = if (attempt == 0) { - 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 { + private def returnCallback: String = returnType.fold("") { retType => + val respBody = retType match { case OptionType(_) => """ let [opt] = args; | if (Array.isArray(opt)) { @@ -52,52 +40,43 @@ case class TypeScriptFunc(func: FuncCallable) { | resolve(res);""".stripMargin } - s"""h.onEvent('$callbackService', '$respFuncName', (args) => { - | $body + s"""h.onEvent('$callbackServiceId', '$respFuncName', (args) => { + | $respBody |}); |""".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 retType = - if (func.arrowType.codomain.length > 1) Some(func.arrowType.codomain) - else func.arrowType.codomain.uncons.map(_._1) - val retTypeTs = retType + val retTypeTs = func.returnType .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 - case (argName, OptionType(_)) => - s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});""" - case (argName, _: DataType) => - s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});""" - case (argName, at: ArrowType) => + case Arg(argName, OptionType(_)) => + s"""h.on('$dataServiceId', '$argName', () => {return $argName === null ? [] : [$argName];});""" + case Arg(argName, _: DataType) => + s"""h.on('$dataServiceId', '$argName', () => {return $argName;});""" + case Arg(argName, at: ArrowType) => val value = s"$argName(${argsCallToTs( at )})" - val expr = at.res.fold(s"$value; return {}")(_ => s"return $value") - s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" + val expr = arrowToRes(at).fold(s"$value; return {}")(_ => s"return $value") + s"""h.on('$callbackServiceId', '$argName', (args) => {$expr;});""" } .mkString("\n") - // TODO support multi return 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 configArgName = generateUniqueArgName(func.argNames, "config", 0) + val clientArgName = genArgName("client") + val configArgName = genArgName("config") val configType = "{ttl?: number}" 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> { | let request: RequestFlow; @@ -110,14 +89,14 @@ case class TypeScriptFunc(func: FuncCallable) { | `, | ) | .configHandler((h) => { - | ${conf.relayVarName.fold("") { r => - s"""h.on('${conf.getDataService}', '$r', () => { + | ${relayVarName.fold("") { r => + s"""h.on('$dataServiceId', '$r', () => { | return $clientArgName.relayPeerId!; | });""".stripMargin }} | $setCallbacks | $returnCallback - | h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { + | h.onEvent('$errorHandlerId', '$errorFuncName', (args) => { | // assuming error is the single argument | const [err] = args; | reject(err); @@ -125,7 +104,7 @@ case class TypeScriptFunc(func: FuncCallable) { | }) | .handleScriptError(reject) | .handleTimeout(() => { - | reject('Request timed out for ${func.funcName}'); + | reject('Request timed out for ${funcName}'); | }) | if(${configArgName} && ${configArgName}.ttl) { | r.withTTL(${configArgName}.ttl) @@ -158,20 +137,18 @@ object TypeScriptFunc { case lt: LiteralType if lt.oneOf(ScalarType.string) => "string" case _: DataType => "any" case at: ArrowType => - s"(${argsToTs(at)}) => ${at.res + s"(${argsToTs(at)}) => ${FuncRes + .arrowToRes(at) .fold("void")(typeToTs)}" - case _ => - // TODO: handle product types in returns - "any" } def argsToTs(at: ArrowType): String = - at.domain - .toLabelledList() - .map(nt => nt._1 + ": " + typeToTs(nt._2)) + FuncRes + .arrowArgs(at) + .map(nt => nt.name + ": " + typeToTs(nt.`type`)) .mkString(", ") 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(", ") } diff --git a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptService.scala b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptService.scala new file mode 100644 index 00000000..541f696d --- /dev/null +++ b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptService.scala @@ -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 +} diff --git a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala index f27ae591..add06840 100644 --- a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala +++ b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala @@ -3,15 +3,16 @@ package aqua.compiler import aqua.backend.Backend import aqua.linker.Linker import aqua.model.AquaContext +import aqua.model.res.AquaRes import aqua.model.transform.GenerationConfig import aqua.parser.lift.LiftParser import aqua.semantics.Semantics import cats.data.Validated.{validNec, Invalid, Valid} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} -import cats.syntax.applicative._ -import cats.syntax.flatMap._ -import cats.syntax.functor._ -import cats.syntax.traverse._ +import cats.syntax.applicative.* +import cats.syntax.flatMap.* +import cats.syntax.functor.* +import cats.syntax.traverse.* import cats.{Comonad, Monad} object AquaCompiler { @@ -47,7 +48,7 @@ object AquaCompiler { } .map( _.map { ap => - val compiled = backend.generate(ap.context, config) + val compiled = backend.generate(AquaRes.fromContext(ap.context, config)) AquaCompiled(ap.id, compiled) } ) diff --git a/model/src/main/scala/aqua/model/res/AquaRes.scala b/model/src/main/scala/aqua/model/res/AquaRes.scala new file mode 100644 index 00000000..9e42fe88 --- /dev/null +++ b/model/src/main/scala/aqua/model/res/AquaRes.scala @@ -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(_)) + ) +} diff --git a/model/src/main/scala/aqua/model/res/FuncRes.scala b/model/src/main/scala/aqua/model/res/FuncRes.scala new file mode 100644 index 00000000..238fe5fe --- /dev/null +++ b/model/src/main/scala/aqua/model/res/FuncRes.scala @@ -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) +} diff --git a/model/src/main/scala/aqua/model/res/ServiceRes.scala b/model/src/main/scala/aqua/model/res/ServiceRes.scala new file mode 100644 index 00000000..bba1fd3f --- /dev/null +++ b/model/src/main/scala/aqua/model/res/ServiceRes.scala @@ -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 + } + ) +} diff --git a/model/src/main/scala/aqua/model/transform/Transform.scala b/model/src/main/scala/aqua/model/transform/Transform.scala index e2cc51ee..969a2196 100644 --- a/model/src/main/scala/aqua/model/transform/Transform.scala +++ b/model/src/main/scala/aqua/model/transform/Transform.scala @@ -3,6 +3,7 @@ package aqua.model.transform import aqua.model.func.FuncCallable import aqua.model.VarModel import aqua.model.func.resolved.{NoAir, ResolvedOp} +import aqua.model.res.FuncRes import aqua.model.topology.Topology import aqua.types.ScalarType import cats.data.Chain @@ -22,7 +23,7 @@ object Transform extends Logging { ): Cofree[Chain, ResolvedOp] = 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( Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string)) ) @@ -48,13 +49,18 @@ object Transform extends Logging { callback, conf.respFuncName ) - clear( - Topology.resolve( - errorsCatcher - .transform( - wrapFunc.resolve(func).value - ) - .tree + + FuncRes( + func, + conf, + clear( + Topology.resolve( + errorsCatcher + .transform( + wrapFunc.resolve(func).value + ) + .tree + ) ) ) } diff --git a/model/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala b/model/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala index 1bb87aaa..51e17c3d 100644 --- a/model/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala +++ b/model/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala @@ -30,9 +30,9 @@ class TransformSpec extends AnyFlatSpec with Matchers { 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 = MakeRes.xor( @@ -80,9 +80,9 @@ class TransformSpec extends AnyFlatSpec with Matchers { 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 = MakeRes.seq( @@ -141,7 +141,7 @@ class TransformSpec extends AnyFlatSpec with Matchers { val bc = GenerationConfig(wrapWithXor = false) - val res = Transform.forClient(f2, bc): Node.Res + val res = Transform.fn(f2, bc).body: Node.Res res.equalsOrPrintDiff( MakeRes.seq(