mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-12 17:55:33 +00:00
Updating compiler backends (#243)
* Updating compiler backends: add FuncRes * TypeScriptService * ServiceRes
This commit is contained in:
parent
38fb824b68
commit
6c498b029b
@ -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))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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(", ")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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(", ")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
18
model/src/main/scala/aqua/model/res/AquaRes.scala
Normal file
18
model/src/main/scala/aqua/model/res/AquaRes.scala
Normal 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(_))
|
||||||
|
)
|
||||||
|
}
|
51
model/src/main/scala/aqua/model/res/FuncRes.scala
Normal file
51
model/src/main/scala/aqua/model/res/FuncRes.scala
Normal 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)
|
||||||
|
}
|
20
model/src/main/scala/aqua/model/res/ServiceRes.scala
Normal file
20
model/src/main/scala/aqua/model/res/ServiceRes.scala
Normal 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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user