diff --git a/aqua-src/test.aqua b/aqua-src/test.aqua index cd11cd10..a7968f4e 100644 --- a/aqua-src/test.aqua +++ b/aqua-src/test.aqua @@ -1,21 +1,7 @@ -data DT: - field: string +service Op("op"): + noop: -> () -service DTGetter("get-dt"): - get_dt(s: string) -> DT - -func use_name1(name: string) -> string: - results <- DTGetter.get_dt(name) - <- results.field - -func use_name2(name: string) -> []string: - results: *string - results <- use_name1(name) - results <- use_name1(name) - results <- use_name1(name) - <- results - -func use_name3(name: string) -> []string: - DTGetter.get_dt("yoyo literal") - results <- use_name2(name) - <- results \ No newline at end of file +func return_none() -> ?string: + result: *string + Op.noop() + <- result \ No newline at end of file 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 33cc2afb..79db25ba 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptBackend.scala @@ -14,7 +14,7 @@ object JavaScriptBackend extends Backend { Seq( Compiled( ext, - JavaScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(bc)).toList.mkString("\n\n") + JavaScriptFile.Header + "\n\n" + funcs.map(_.generateJavascript(bc)).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 fe0b9ef5..ea98b232 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFile.scala @@ -10,7 +10,7 @@ case class JavaScriptFile(context: AquaContext) { Chain.fromSeq(context.funcs.values.toSeq).map(JavaScriptFunc(_)) def generateJS(conf: BodyConfig = BodyConfig()): String = - JavaScriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(conf)).toList.mkString("\n\n") + 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 c7124f1a..85cc608d 100644 --- a/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala +++ b/backend/js/src/main/scala/aqua/backend/js/JavaScriptFunc.scala @@ -4,7 +4,6 @@ import aqua.backend.air.FuncAirGen import aqua.model.func.{ArgDef, FuncCallable} import aqua.model.transform.BodyConfig import aqua.types._ -import cats.syntax.functor._ import cats.syntax.show._ case class JavaScriptFunc(func: FuncCallable) { @@ -14,18 +13,34 @@ case class JavaScriptFunc(func: FuncCallable) { def argsJavaScript: String = func.args.args.map(ad => s"${ad.name}").mkString(", ") - def generateTypescript(conf: BodyConfig = BodyConfig()): String = { - - val tsAir = FuncAirGen(func).generateAir(conf) - - val returnCallback = func.ret.as { - s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => { - | const [res] = args; - | resolve(res); - |}); - |""".stripMargin + // TODO: use common functions between TypeScript and JavaScript backends + private def genReturnCallback( + retType: Type, + callbackService: String, + respFuncName: String + ): String = { + val body = retType match { + case OptionType(_) => + """ let [opt] = args; + | if (Array.isArray(opt)) { + | if (opt.length === 0) { resolve(null); } + | opt = opt[0]; + | } + | return resolve(opt);""".stripMargin + case _ => + """ const [res] = args; + | resolve(res);""".stripMargin } + s"""h.onEvent('$callbackService', '$respFuncName', (args) => { + | $body + |}); + |""".stripMargin + } + + def generateJavascript(conf: BodyConfig = BodyConfig()): String = { + + val tsAir = FuncAirGen(func).generateAir(conf) val setCallbacks = func.args.args.map { case ArgDef.Data(argName, OptionType(_)) => @@ -40,10 +55,16 @@ case class JavaScriptFunc(func: FuncCallable) { s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" }.mkString("\n") + val returnCallback = func.ret + .map(_._2) + .map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName)) + .getOrElse("") + val returnVal = func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise") - val configArgName ="config" + // TODO: it could be non-unique + val configArgName = "config" s""" |export async function ${func.funcName}(client${if (func.args.isEmpty) "" @@ -65,7 +86,7 @@ case class JavaScriptFunc(func: FuncCallable) { | });""".stripMargin }} | $setCallbacks - | ${returnCallback.getOrElse("")} + | $returnCallback | h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { | // assuming error is the single argument | const [err] = args; 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 e6192678..577e80b6 100644 --- a/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala +++ b/backend/ts/src/main/scala/aqua/backend/ts/TypeScriptFunc.scala @@ -4,7 +4,6 @@ import aqua.backend.air.FuncAirGen import aqua.model.func.{ArgDef, FuncCallable} import aqua.model.transform.BodyConfig import aqua.types._ -import cats.syntax.functor._ import cats.syntax.show._ case class TypeScriptFunc(func: FuncCallable) { @@ -23,17 +22,42 @@ case class TypeScriptFunc(func: FuncCallable) { 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(_) => + """ let [opt] = args; + | if (Array.isArray(opt)) { + | if (opt.length === 0) { resolve(null); } + | opt = opt[0]; + | } + | return resolve(opt);""".stripMargin + case _ => + """ const [res] = args; + | resolve(res);""".stripMargin + + } + s"""h.onEvent('$callbackService', '$respFuncName', (args) => { + | $body + |}); + |""".stripMargin + } + def generateTypescript(conf: BodyConfig = BodyConfig()): String = { val tsAir = FuncAirGen(func).generateAir(conf) - val returnCallback = func.ret.as { - s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => { - | const [res] = args; - | resolve(res); - |}); - |""".stripMargin - } + val retType = func.ret + .map(_._2) + .fold("void")(typeToTs) + + val returnCallback = func.ret + .map(_._2) + .map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName)) + .getOrElse("") val setCallbacks = func.args.args.map { case ArgDef.Data(argName, OptionType(_)) => @@ -48,10 +72,6 @@ case class TypeScriptFunc(func: FuncCallable) { s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});""" }.mkString("\n") - val retType = func.ret - .map(_._2) - .fold("void")(typeToTs) - val returnVal = func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise") @@ -80,7 +100,7 @@ case class TypeScriptFunc(func: FuncCallable) { | });""".stripMargin }} | $setCallbacks - | ${returnCallback.getOrElse("")} + | $returnCallback | h.onEvent('${conf.errorHandlingService}', '${conf.errorFuncName}', (args) => { | // assuming error is the single argument | const [err] = args; diff --git a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala index 396ac385..ed62e7b5 100644 --- a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala +++ b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala @@ -6,7 +6,7 @@ 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 aqua.semantics.{RulesViolated, SemanticError, Semantics, WrongAST} import cats.data._ import cats.kernel.Monoid import cats.syntax.flatMap._ @@ -91,7 +91,7 @@ object AquaCompiler extends LogSupport { .focus(2) .map(_.toConsoleStr(hint, Console.CYAN)) .getOrElse("(Dup error, but offset is beyond the script)") + "\n" - case _ => + case WrongAST(_) => "Semantic error" } diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index b6ecc2e1..61745a4e 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -1,7 +1,7 @@ package aqua.semantics -import aqua.model.{AquaContext, Model, ScriptModel} import aqua.model.func.raw.FuncOp +import aqua.model.{AquaContext, Model, ScriptModel} import aqua.parser.lexer.Token import aqua.parser.{Ast, Expr} import aqua.semantics.rules.ReportError @@ -16,13 +16,13 @@ import aqua.semantics.rules.types.{TypeOp, TypesAlgebra, TypesInterpreter, Types import cats.Eval import cats.arrow.FunctionK import cats.data.Validated.{Invalid, Valid} -import cats.data.{Chain, EitherK, NonEmptyChain, State, Validated, ValidatedNec} +import cats.data._ import cats.free.Free import cats.kernel.Monoid -import monocle.Lens -import monocle.macros.GenLens import cats.syntax.apply._ import cats.syntax.semigroup._ +import monocle.Lens +import monocle.macros.GenLens import wvlet.log.LogSupport object Semantics extends LogSupport { @@ -96,8 +96,11 @@ object Semantics extends LogSupport { NonEmptyChain .fromChain(state.errors) .fold[ValidatedNec[SemanticError[F], AquaContext]](Valid(ctx))(Invalid(_)) - case (_, _) => - Validated.invalidNec[SemanticError[F], AquaContext](WrongAST(ast)) + case (state, _) => + NonEmptyChain + .fromChain(state.errors) + .map(Invalid(_)) + .getOrElse(Validated.invalidNec[SemanticError[F], AquaContext](WrongAST(ast))) } .value }