Return T | null on optional results (#221)

This commit is contained in:
Dima 2021-07-29 13:49:25 +03:00 committed by GitHub
parent b6989058fc
commit 2eb4598093
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 56 deletions

View File

@ -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
func return_none() -> ?string:
result: *string
Op.noop()
<- result

View File

@ -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")
)
)
}

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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"
}

View File

@ -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
}