Return many values from a single function (#229)

This commit is contained in:
Dmitry Kurinskiy 2021-08-09 21:33:55 +03:00 committed by GitHub
parent cd30ff8e8c
commit 3eb3ecc221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 400 additions and 276 deletions

View File

@ -49,7 +49,7 @@ jobs:
- name: Check .jar exists
run: |
JAR="cli/target/scala-2.13/aqua-cli-${{ env.VERSION }}.jar"
JAR="cli/target/scala-3.0.1/aqua-cli-${{ env.VERSION }}.jar"
stat "$JAR"
echo "JAR=$JAR" >> $GITHUB_ENV

View File

@ -1,16 +1,15 @@
data DT:
field: string
service Getter("test"):
createStr: u32 -> string
service DTGetter("get-dt"):
get_dt(s: string) -> DT
service OpO("op"):
identity: string -> string
func use_name1(name: string) -> string:
results <- DTGetter.get_dt(name)
<- results.field
-- a question mark means that this constant could be rewritten before this definition
const anotherConst ?= "default-str"
const uniqueConst ?= 5
func use_name2(name: string) -> []string:
results: *string
results <- use_name1(name)
results <- use_name1(name)
results <- use_name1(name)
<- results
func callConstant() -> []string, u8:
res: *string
res <- Getter.createStr(uniqueConst)
res <- OpO.identity(anotherConst)
<- res, 5

View File

@ -40,7 +40,7 @@ object AirGen extends LogSupport {
def opsToSingle(ops: Chain[AirGen]): AirGen = ops.toList match {
case Nil => NullGen
case h :: Nil => h
case list => list.reduceLeft(SeqGen)
case list => list.reduceLeft(SeqGen(_, _))
}
private def folder(op: ResolvedOp, ops: Chain[AirGen]): Eval[AirGen] =
@ -48,12 +48,12 @@ object AirGen extends LogSupport {
// case mt: MetaTag =>
// folder(mt.op, ops).map(ag => mt.comment.fold(ag)(CommentGen(_, ag)))
case SeqRes =>
Eval later ops.toList.reduceLeftOption(SeqGen).getOrElse(NullGen)
Eval later ops.toList.reduceLeftOption(SeqGen(_, _)).getOrElse(NullGen)
case ParRes =>
Eval later (ops.toList match {
case o :: Nil => ParGen(o, NullGen)
case _ =>
ops.toList.reduceLeftOption(ParGen).getOrElse {
ops.toList.reduceLeftOption(ParGen(_, _)).getOrElse {
warn("ParRes with no children converted to Null")
NullGen
}
@ -62,7 +62,7 @@ object AirGen extends LogSupport {
Eval later (ops.toList match {
case o :: Nil => XorGen(o, NullGen)
case _ =>
ops.toList.reduceLeftOption(XorGen).getOrElse {
ops.toList.reduceLeftOption(XorGen(_, _)).getOrElse {
warn("XorRes with no children converted to Null")
NullGen
}

View File

@ -27,6 +27,18 @@ case class JavaScriptFunc(func: FuncCallable) {
| opt = opt[0];
| }
| return resolve(opt);""".stripMargin
case pt: ProductType =>
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
s"""
| if(Array.isArray(opt[$i])) {
| if (opt[$i].length === 0) { opt[$i] = null; }
| else {opt[$i] = opt[$i][0]; }
| }""".stripMargin
}.mkString
s""" let opt = args;
|$unwrapOpts
| return resolve(opt);""".stripMargin
case _ =>
""" const [res] = args;
| resolve(res);""".stripMargin
@ -51,18 +63,15 @@ case class JavaScriptFunc(func: FuncCallable) {
val value = s"$argName(${argsCallToJs(
at
)})"
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value")
val expr = at.codomain.uncons.fold(s"$value; return {}")(_ => s"return $value")
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});"""
}
.mkString("\n")
// TODO support multi-return
val returnCallback = func.arrowType.codomain.uncons
.map(_._1)
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
.map(_ => genReturnCallback(func.arrowType.codomain, conf.callbackService, conf.respFuncName))
.getOrElse("")
// TODO support multi-return
val returnVal =
func.ret.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")

View File

@ -35,6 +35,18 @@ case class TypeScriptFunc(func: FuncCallable) {
| opt = opt[0];
| }
| return resolve(opt);""".stripMargin
case pt: ProductType =>
val unwrapOpts = pt.toList.zipWithIndex.collect { case (OptionType(_), i) =>
s"""
| if(Array.isArray(opt[$i])) {
| if (opt[$i].length === 0) { opt[$i] = null; }
| else {opt[$i] = opt[$i][0]; }
| }""".stripMargin
}.mkString
s""" let opt: any = args;
|$unwrapOpts
| return resolve(opt);""".stripMargin
case _ =>
""" const [res] = args;
| resolve(res);""".stripMargin
@ -51,8 +63,9 @@ case class TypeScriptFunc(func: FuncCallable) {
val tsAir = FuncAirGen(func).generateAir(conf)
// TODO: support multi return
val retType = func.arrowType.codomain.uncons
.map(_._1)
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)
@ -133,8 +146,10 @@ object TypeScriptFunc {
case OptionType(t) => typeToTs(t) + " | null"
case ArrayType(t) => typeToTs(t) + "[]"
case StreamType(t) => typeToTs(t) + "[]"
case pt: StructType =>
s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
case pt: ProductType =>
"[" + pt.toList.map(typeToTs).mkString(", ") + "]"
case st: StructType =>
s"{${st.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
case st: ScalarType if ScalarType.number(st) => "number"
case ScalarType.bool => "boolean"
case ScalarType.string => "string"
@ -157,6 +172,6 @@ object TypeScriptFunc {
.mkString(", ")
def argsCallToTs(at: ArrowType): String =
at.args.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
at.domain.toList.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
}

View File

@ -24,7 +24,7 @@ val cats = "org.typelevel" %% "cats-core" % catsV
name := "aqua-hll"
val commons = Seq(
baseAquaVersion := "0.1.12",
baseAquaVersion := "0.1.13",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion,
libraryDependencies ++= Seq(

View File

@ -1,3 +1,3 @@
package aqua.model
object ReturnModel extends Model {}
object ReturnModel extends Model

View File

@ -16,11 +16,11 @@ case class ResolveFunc(
private val returnVar: String = "-return-"
def returnCallback(retModel: ValueModel): FuncOp =
def returnCallback(retModel: List[ValueModel]): FuncOp =
callback(
respFuncName,
Call(
retModel :: Nil,
retModel,
Nil
)
)
@ -55,9 +55,9 @@ case class ResolveFunc(
func.arrowType.domain.toLabelledList().map(ad => VarModel(ad._1, ad._2)),
returnType.map { case (l, t) => Call.Export(l, t) }
)
) ::
returnType.map { case (l, t) => VarModel(l, t) }
.map(returnCallback): _*
) :: returnType.headOption
.map(_ => returnCallback(returnType.map { case (l, t) => VarModel(l, t) }))
.toList: _*
)
),
ArrowType(ConsType.cons(func.funcName, func.arrowType, NilType), NilType),

View File

@ -1,8 +1,8 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.lexer.Token._
import aqua.parser.lexer.{ArrowTypeToken, Name}
import aqua.parser.lexer.Token.*
import aqua.parser.lexer.{ArrowTypeToken, DataTypeToken, Name}
import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
@ -14,8 +14,9 @@ object ArrowTypeExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: Parser[ArrowTypeExpr[F]] =
(Name
.p[F] ~ ((` : ` *> ArrowTypeToken.`arrowdef`[F]) | ArrowTypeToken.`arrowWithNames`)).map {
case (name, t) =>
ArrowTypeExpr(name, t)
.p[F] ~ ((` : ` *> ArrowTypeToken.`arrowdef`[F](
DataTypeToken.`datatypedef`[F]
)) | ArrowTypeToken.`arrowWithNames`(DataTypeToken.`datatypedef`[F]))).map { case (name, t) =>
ArrowTypeExpr(name, t)
}
}

View File

@ -8,7 +8,7 @@ import cats.Comonad
import cats.parse.{Parser => P}
case class CallArrowExpr[F[_]](
variable: Option[Name[F]],
variables: List[Name[F]],
ability: Option[Ability[F]],
funcName: Name[F],
args: List[Value[F]]
@ -17,12 +17,12 @@ case class CallArrowExpr[F[_]](
object CallArrowExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: P[CallArrowExpr[F]] =
((Name.p[F] <* ` <- `).backtrack.?.with1 ~
((comma(Name.p[F]) <* ` <- `).backtrack.?.with1 ~
((Ability.ab[F] <* `.`).?.with1 ~
Name.p[F] ~
comma0(Value.`value`[F].surroundedBy(`/s*`)).between(`(` <* `/s*`, `/s*` *> `)`))).map {
case (variable, ((ability, funcName), args)) =>
CallArrowExpr(variable, ability, funcName, args)
case (variables, ((ability, funcName), args)) =>
CallArrowExpr(variables.toList.flatMap(_.toList), ability, funcName, args)
}
}

View File

@ -1,7 +1,7 @@
package aqua.parser.expr
import aqua.parser.lexer.Token._
import aqua.parser.lexer.{Arg, DataTypeToken, Name, Value}
import aqua.parser.lexer.Token.*
import aqua.parser.lexer.{Arg, ArrowTypeToken, DataTypeToken, Name, TypeToken, Value}
import aqua.parser.lift.LiftParser
import aqua.parser.{Ast, Expr, FuncReturnError, ParserError}
import cats.Comonad
@ -11,10 +11,11 @@ import cats.parse.Parser
case class FuncExpr[F[_]](
name: Name[F],
args: List[Arg[F]],
ret: Option[DataTypeToken[F]],
retValue: Option[Value[F]]
) extends Expr[F](FuncExpr, name)
arrowTypeExpr: ArrowTypeToken[F],
retValue: List[Value[F]]
) extends Expr[F](FuncExpr, name) {
def ret = arrowTypeExpr.res
}
object FuncExpr extends Expr.AndIndented {
@ -36,10 +37,10 @@ object FuncExpr extends Expr.AndIndented {
Nil
override def p[F[_]: LiftParser: Comonad]: Parser[FuncExpr[F]] =
((`func` *> ` ` *> Name.p[F])
~ comma0(Arg.p.surroundedBy(`/s*`)).between(`(` <* `/s*`, `/s*` *> `)`)
~ (` -> ` *> DataTypeToken.`datatypedef`).?).map { case ((name, args), ret) =>
FuncExpr(name, args, ret, None)
((`func` *> ` ` *> Name.p[F]) ~ ArrowTypeToken.`arrowWithNames`[F](
TypeToken.`typedef`[F]
)).map { case (name, arrow) =>
FuncExpr(name, arrow.copy(unit = name.unit), Nil)
}
override def ast[F[_]: LiftParser: Comonad](): Parser[ValidatedNec[ParserError[F], Ast.Tree[F]]] =
@ -49,34 +50,65 @@ object FuncExpr extends Expr.AndIndented {
_.andThen(tree =>
tree.head match {
case funcExpr: FuncExpr[F] =>
// Find the return expression which might be the last one in the function body
val maybeReturn =
tree.tail.value.lastOption.map(_.head).collect { case re: ReturnExpr[F] =>
re
}
// Find correspondance between returned values and declared return types
funcExpr.ret match {
case Some(ret) =>
tree.tail.value.lastOption.map(_.head) match {
case Some(re: ReturnExpr[F]) =>
Validated
.validNec(Cofree(funcExpr.copy(retValue = Some(re.value)), tree.tail))
case _ =>
Validated.invalidNec(
FuncReturnError[F](
ret.unit,
"Return type is defined for function, but nothing returned. Use `<- value` as the last expression inside function body."
)
case Nil =>
// Nothing should be returned
maybeReturn.fold(Validated.validNec(tree))(re =>
// Declared nothing, but smth is returned
Validated.invalidNec(
FuncReturnError[F](
re.token.unit,
"Trying to return a value from function that has no return type. Please add return type to function declaration, e.g. `func foo() -> RetType:`"
)
)
)
case rets =>
// Something is expected to be returned
maybeReturn.fold(
// No values are returned at all, no return expression
Validated.invalidNec(
FuncReturnError[F](
rets.head.unit,
"Return type is defined for function, but nothing returned. Use `<- value` as the last expression inside function body."
)
)
) { re =>
// Something is returned, so check that numbers are the same
def checkRet(typeDef: List[DataTypeToken[F]], values: List[Value[F]])
: ValidatedNec[ParserError[F], Ast.Tree[F]] =
(typeDef, values) match {
case (Nil, Nil) =>
// Everything checked, ok
Validated
.validNec(Cofree(funcExpr.copy(retValue = re.values.toList), tree.tail))
case (_ :: tTail, _ :: vTail) =>
// One more element checked, advance
checkRet(tTail, vTail)
case (t :: _, Nil) =>
// No more values, but still have declared types
Validated.invalidNec(
FuncReturnError[F](
t.unit,
"Return type is defined for function, but nothing returned. Use `<- value, ...` as the last expression inside function body."
)
)
case (_, v :: _) =>
Validated.invalidNec(
FuncReturnError[F](
v.unit,
"Return type is not defined for function, but something is returned."
)
)
}
checkRet(rets, re.values.toList)
}
case None =>
tree.tail.value.lastOption.map(_.head) match {
case Some(re: ReturnExpr[F]) =>
Validated.invalidNec(
FuncReturnError[F](
re.value.unit,
"Trying to return a value from function that has no return type. Please add return type to function declaration, e.g. `func foo() -> RetType:`"
)
)
case _ =>
Validated.validNec(tree)
}
}
case _ => Validated.validNec(tree)

View File

@ -1,16 +1,17 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.lexer.Token._
import aqua.parser.lexer.Token.*
import aqua.parser.lexer.Value
import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.data.NonEmptyList
import cats.parse.Parser
case class ReturnExpr[F[_]](value: Value[F]) extends Expr[F](ReturnExpr, value)
case class ReturnExpr[F[_]](values: NonEmptyList[Value[F]]) extends Expr[F](ReturnExpr, values.head)
object ReturnExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: Parser[ReturnExpr[F]] =
(`<-` *> ` ` *> Value.`value`[F]).map(ReturnExpr(_))
(`<-` *> ` ` *> comma(Value.`value`[F])).map(ReturnExpr(_))
}

View File

@ -72,38 +72,32 @@ object BasicTypeToken {
.map(BasicTypeToken(_))
}
sealed trait ArrowDef[F[_]] {
def argTypes: List[TypeToken[F]]
def resType: Option[DataTypeToken[F]]
}
case class ArrowTypeToken[F[_]: Comonad](
override val unit: F[Unit],
args: List[DataTypeToken[F]],
res: Option[DataTypeToken[F]]
) extends TypeToken[F] with ArrowDef[F] {
args: List[(Option[Name[F]], TypeToken[F])],
res: List[DataTypeToken[F]]
) extends TypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v)
override def argTypes: List[TypeToken[F]] = args
override def resType: Option[DataTypeToken[F]] = res
def argTypes: List[TypeToken[F]] = args.map(_._2)
}
object ArrowTypeToken {
def `arrowdef`[F[_]: LiftParser: Comonad]: P[ArrowTypeToken[F]] =
(comma0(DataTypeToken.`datatypedef`).with1 ~ ` -> `.lift ~
(DataTypeToken.`datatypedef`
.map(Some(_)) | `()`.as(None))).map { case ((args, point), res)
ArrowTypeToken(point, args, res)
def `arrowdef`[F[_]: LiftParser: Comonad](argTypeP: P[TypeToken[F]]): P[ArrowTypeToken[F]] =
(comma0(argTypeP).with1 ~ ` -> `.lift ~
(comma(DataTypeToken.`datatypedef`).map(_.toList)
| `()`.as(Nil))).map { case ((args, point), res)
ArrowTypeToken(point, args.map(Option.empty[Name[F]] -> _), res)
}
def `arrowWithNames`[F[_]: LiftParser: Comonad]: P[ArrowTypeToken[F]] =
def `arrowWithNames`[F[_]: LiftParser: Comonad](argTypeP: P[TypeToken[F]]): P[ArrowTypeToken[F]] =
(((`(`.lift <* `/s*`) ~ comma0(
(Name.p[F] *> ` : ` *> DataTypeToken.`datatypedef`).surroundedBy(`/s*`)
(Name.p[F].map(Option(_)) ~ (` : ` *> (argTypeP | argTypeP.between(`(`, `)`))))
.surroundedBy(`/s*`)
) <* (`/s*` *> `)`)) ~
(` -> ` *> DataTypeToken.`datatypedef`).?).map { case ((point, args), res) =>
ArrowTypeToken(point, args, res)
(` -> ` *> comma(DataTypeToken.`datatypedef`)).?).map { case ((point, args), res) =>
ArrowTypeToken(point, args, res.toList.flatMap(_.toList))
}
}
@ -130,7 +124,9 @@ object TypeToken {
def `typedef`[F[_]: LiftParser: Comonad]: P[TypeToken[F]] =
P.oneOf(
ArrowTypeToken.`arrowdef`.backtrack :: DataTypeToken.`datatypedef` :: Nil
ArrowTypeToken
.`arrowdef`((DataTypeToken.`datatypedef`[F]))
.backtrack :: DataTypeToken.`datatypedef` :: Nil
)
}

View File

@ -39,7 +39,13 @@ object AquaSpec {
args: List[DataTypeToken[Id]],
res: Option[DataTypeToken[Id]]
): ArrowTypeToken[Id] =
ArrowTypeToken[Id]((), args, res)
ArrowTypeToken[Id]((), args.map(None -> _), res.toList)
def toNamedArrow(
args: List[(String, TypeToken[Id])],
res: List[DataTypeToken[Id]]
): ArrowTypeToken[Id] =
ArrowTypeToken[Id]((), args.map(ab => Some(Name[Id](ab._1)) -> ab._2), res)
implicit def toCustomArg(str: String, customType: String): Arg[Id] =
Arg[Id](str, toCustomType(customType))

View File

@ -2,6 +2,7 @@ package aqua.parser
import aqua.AquaSpec
import aqua.parser.expr.ArrowTypeExpr
import aqua.parser.lexer.ArrowTypeToken
import aqua.types.ScalarType.{string, u32}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
@ -21,7 +22,10 @@ class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
)
parseArrow("onIn(a: Custom, b: Custom2)") should be(
ArrowTypeExpr[Id]("onIn", toArrowType(List("Custom", "Custom2"), None))
ArrowTypeExpr[Id](
"onIn",
toNamedArrow(List("a" -> toCustomType("Custom"), "b" -> toCustomType("Custom2")), Nil)
)
)
parseArrow("onIn: Custom, string, u32, Custom3 -> Custom2") should be(
@ -30,5 +34,16 @@ class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
toArrowType(List("Custom", string, u32, "Custom3"), Some("Custom2"))
)
)
parseArrow("onIn: Custom, string, u32, Custom3 -> Custom2, string") should be(
ArrowTypeExpr[Id](
"onIn",
ArrowTypeToken[Id](
(),
List(toCustomType("Custom"), scToBt(string), scToBt(u32), toCustomType("Custom3"))
.map(None -> _),
List(toCustomType("Custom2"), scToBt(string))
)
)
)
}
}

View File

@ -11,10 +11,10 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"func calls" should "parse func()" in {
parseExpr("func()") should be(CallArrowExpr[Id](None, None, toName("func"), List()))
parseExpr("func()") should be(CallArrowExpr[Id](Nil, None, toName("func"), List()))
parseExpr("Ab.func(arg)") should be(
CallArrowExpr[Id](
None,
Nil,
Some(toAb("Ab")),
Name[Id]("func"),
List(VarLambda[Id](toName("arg")))
@ -23,7 +23,7 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseExpr("func(arg.doSomething)") should be(
CallArrowExpr[Id](
None,
Nil,
None,
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething")))
@ -32,7 +32,7 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseExpr("func(arg.doSomething.and.doSomethingElse)") should be(
CallArrowExpr[Id](
None,
Nil,
None,
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething", "and", "doSomethingElse")))
@ -41,7 +41,7 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseExpr("func(arg.doSomething.and.doSomethingElse)") should be(
CallArrowExpr[Id](
None,
Nil,
None,
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething", "and", "doSomethingElse")))
@ -50,7 +50,7 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseExpr("Ab.func(arg.doSomething.and.doSomethingElse, arg2.someFunc)") should be(
CallArrowExpr[Id](
None,
Nil,
Some(toAb("Ab")),
Name[Id]("func"),
List(
@ -62,7 +62,18 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
parseExpr("x <- func(arg.doSomething)") should be(
CallArrowExpr[Id](
Some(toName("x")),
List(toName("x")),
None,
Name[Id]("func"),
List(
toVarLambda("arg", List("doSomething"))
)
)
)
parseExpr("x, y, z <- func(arg.doSomething)") should be(
CallArrowExpr[Id](
toName("x") :: toName("y") :: toName("z") :: Nil,
None,
Name[Id]("func"),
List(

View File

@ -20,7 +20,7 @@ class CoExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
Chain(
Cofree[Chain, Expr[Id]](
CallArrowExpr(
Some(AquaSpec.toName("x")),
List(AquaSpec.toName("x")),
None,
AquaSpec.toName("y"),
Nil

View File

@ -7,7 +7,7 @@ import aqua.parser.lexer.{ArrowTypeToken, BasicTypeToken, EqOp, Literal, Token,
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.types.ScalarType.*
import cats.Id
import cats.data.Chain
import cats.data.{Chain, NonEmptyList}
import cats.free.Cofree
import cats.syntax.foldable.*
import org.scalatest.flatspec.AnyFlatSpec
@ -21,43 +21,43 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
"func header" should "parse" in {
funcExpr("func some() -> bool") should be(
FuncExpr("some", List(), Some(bool: BasicTypeToken[Id]), None)
FuncExpr("some", toNamedArrow(Nil, List(bool: BasicTypeToken[Id])), Nil)
)
funcExpr("func some()") should be(FuncExpr("some", List(), None, None))
funcExpr("func some()") should be(FuncExpr("some", toNamedArrow(Nil, Nil), Nil))
val arrowToken =
ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u8)), Some(BasicTypeToken[Id](bool)))
ArrowTypeToken[Id]((), List(None -> BasicTypeToken[Id](u8)), List(BasicTypeToken[Id](bool)))
funcExpr("func some(peer: PeerId, other: u8 -> bool)") should be(
FuncExpr(
toName("some"),
List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken)),
None,
None
toNamedArrow(("peer" -> toCustomType("PeerId")) :: ("other" -> arrowToken) :: Nil, Nil),
Nil
)
)
val arrowToken2 =
ArrowTypeToken[Id](
(),
List(BasicTypeToken[Id](u32), BasicTypeToken[Id](u64)),
Some(BasicTypeToken[Id](bool))
List(None -> BasicTypeToken[Id](u32), None -> BasicTypeToken[Id](u64)),
List(BasicTypeToken[Id](bool))
)
funcExpr("func some(peer: PeerId, other: u32, u64 -> bool)") should be(
FuncExpr(
toName("some"),
List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken2)),
None,
None
toNamedArrow(("peer" -> toCustomType("PeerId")) :: ("other" -> arrowToken2) :: Nil, Nil),
Nil
)
)
val arrowToken3 = ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32)), None)
funcExpr("func getTime(peer: PeerId, ret: u32 -> ()) -> string") should be(
val arrowToken3 = ArrowTypeToken[Id]((), List(None -> BasicTypeToken[Id](u32)), Nil)
funcExpr("func getTime(peer: PeerId, ret: u32 -> ()) -> string, u32") should be(
FuncExpr(
toName("getTime"),
List(toCustomArg("peer", "PeerId"), toArg("ret", arrowToken3)),
Some(BasicTypeToken[Id](string)),
None
toNamedArrow(
("peer" -> toCustomType("PeerId")) :: ("ret" -> arrowToken3) :: Nil,
BasicTypeToken[Id](string) :: BasicTypeToken[Id](u32) :: Nil
),
Nil
)
)
}
@ -88,7 +88,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
| call(true)""".stripMargin
val tree = FuncExpr.ast[Id]().parseAll(script).value.toEither.value
val funcBody = checkHeadGetTail(tree, FuncExpr("a", Nil, None, None), 1).toList
val funcBody = checkHeadGetTail(tree, FuncExpr("a", toNamedArrow(Nil, Nil), Nil), 1).toList
val ifBody =
checkHeadGetTail(
@ -98,10 +98,10 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
).toList
ifBody.head.head should be(
CallArrowExpr(Some(toName("x")), Some(toAb("Ab")), "func", Nil)
CallArrowExpr(List(toName("x")), Some(toAb("Ab")), "func", Nil)
)
ifBody(1).head should be(AbilityIdExpr(toAb("Peer"), toStr("some id")))
ifBody(2).head should be(CallArrowExpr(None, None, "call", List(toBool(true))))
ifBody(2).head should be(CallArrowExpr(Nil, None, "call", List(toBool(true))))
}
@ -161,22 +161,25 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
// Local service
qTree.d() shouldBe ServiceExpr(toAb("Local"), Some(toStr("local")))
qTree.d() shouldBe ArrowTypeExpr("gt", toArrowType(Nil, Some(scToBt(bool))))
qTree.d() shouldBe FuncExpr("tryGen", Nil, Some(scToBt(bool)), Some("v": VarLambda[Id]))
qTree.d() shouldBe FuncExpr(
"tryGen",
toNamedArrow(Nil, scToBt(bool) :: Nil),
List("v": VarLambda[Id])
)
qTree.d() shouldBe OnExpr(toStr("deeper"), List(toStr("deep")))
qTree.d() shouldBe CallArrowExpr(Some("v"), Some(toAb("Local")), "gt", Nil)
qTree.d() shouldBe ReturnExpr(toVar("v"))
qTree.d() shouldBe CallArrowExpr(List("v"), Some(toAb("Local")), "gt", Nil)
qTree.d() shouldBe ReturnExpr(NonEmptyList.one(toVar("v")))
// genC function
qTree.d() shouldBe FuncExpr(
"genC",
List(toArgSc("val", string)),
Some(boolSc),
Some("two": VarLambda[Id])
toNamedArrow(("val" -> string) :: Nil, boolSc :: Nil),
List("two": VarLambda[Id])
)
qTree.d() shouldBe CallArrowExpr(Some("one"), Some(toAb("Local")), "gt", List())
qTree.d() shouldBe CallArrowExpr(List("one"), Some(toAb("Local")), "gt", List())
qTree.d() shouldBe OnExpr(toStr("smth"), List(toStr("else")))
qTree.d() shouldBe CallArrowExpr(Some("two"), None, "tryGen", List())
qTree.d() shouldBe CallArrowExpr(Some("three"), Some(toAb("Local")), "gt", List())
qTree.d() shouldBe ReturnExpr(toVar("two"))
qTree.d() shouldBe CallArrowExpr(List("two"), None, "tryGen", List())
qTree.d() shouldBe CallArrowExpr(List("three"), Some(toAb("Local")), "gt", List())
qTree.d() shouldBe ReturnExpr(NonEmptyList.one(toVar("two")))
/* TODO this is semantics, not parser test
val f =

View File

@ -20,7 +20,7 @@ class ParExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
Chain(
Cofree[Chain, Expr[Id]](
CallArrowExpr(
Some(AquaSpec.toName("x")),
List(AquaSpec.toName("x")),
None,
AquaSpec.toName("y"),
Nil

View File

@ -3,6 +3,7 @@ package aqua.parser
import aqua.AquaSpec
import aqua.parser.expr.ReturnExpr
import cats.Id
import cats.data.NonEmptyList
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
@ -11,7 +12,7 @@ class ReturnExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
"return" should "be parsed" in {
parseReturn("<- true") should be(
ReturnExpr[Id](toBool(true))
ReturnExpr[Id](NonEmptyList.one(toBool(true)))
)
}
}

View File

@ -20,38 +20,56 @@ class TypeTokenSpec extends AnyFlatSpec with Matchers with EitherValues {
}
"Arrow type" should "parse" in {
val arrowdef = ArrowTypeToken.`arrowdef`[Id](DataTypeToken.`datatypedef`[Id])
val arrowWithNames = ArrowTypeToken.`arrowWithNames`[Id](DataTypeToken.`datatypedef`[Id])
ArrowTypeToken.`arrowdef`.parseAll("-> B").value should be(
ArrowTypeToken[Id]((), Nil, Some(CustomTypeToken[Id]("B")))
arrowdef.parseAll("-> B").value should be(
ArrowTypeToken[Id]((), Nil, List(CustomTypeToken[Id]("B")))
)
ArrowTypeToken.`arrowdef`.parseAll("A -> B").value should be(
ArrowTypeToken[Id]((), CustomTypeToken[Id]("A") :: Nil, Some(CustomTypeToken[Id]("B")))
)
ArrowTypeToken.`arrowWithNames`.parseAll("(a: A) -> B").value should be(
ArrowTypeToken[Id]((), CustomTypeToken[Id]("A") :: Nil, Some(CustomTypeToken[Id]("B")))
)
ArrowTypeToken.`arrowdef`.parseAll("u32 -> Boo").value should be(
ArrowTypeToken[Id]((), (u32: BasicTypeToken[Id]) :: Nil, Some(CustomTypeToken[Id]("Boo")))
)
TypeToken.`typedef`.parseAll("u32 -> ()").value should be(
ArrowTypeToken[Id]((), (u32: BasicTypeToken[Id]) :: Nil, None)
)
ArrowTypeToken.`arrowdef`.parseAll("A, u32 -> B").value should be(
arrowdef.parseAll("A -> B").value should be(
ArrowTypeToken[Id](
(),
CustomTypeToken[Id]("A") :: (u32: BasicTypeToken[Id]) :: Nil,
Some(CustomTypeToken[Id]("B"))
(None -> CustomTypeToken[Id]("A")) :: Nil,
List(CustomTypeToken[Id]("B"))
)
)
ArrowTypeToken.`arrowdef`.parseAll("[]Absolutely, u32 -> B").value should be(
arrowWithNames.parseAll("(a: A) -> B").value should be(
ArrowTypeToken[Id](
(),
ArrayTypeToken[Id]((), CustomTypeToken[Id]("Absolutely")) :: (u32: BasicTypeToken[
(Some(Name[Id]("a")) -> CustomTypeToken[Id]("A")) :: Nil,
List(CustomTypeToken[Id]("B"))
)
)
arrowdef.parseAll("u32 -> Boo").value should be(
ArrowTypeToken[Id](
(),
(None -> (u32: BasicTypeToken[Id])) :: Nil,
List(CustomTypeToken[Id]("Boo"))
)
)
TypeToken.`typedef`.parseAll("u32 -> ()").value should be(
ArrowTypeToken[Id]((), (None -> (u32: BasicTypeToken[Id])) :: Nil, Nil)
)
arrowdef.parseAll("A, u32 -> B").value should be(
ArrowTypeToken[Id](
(),
(None -> CustomTypeToken[Id]("A")) :: (None -> (u32: BasicTypeToken[Id])) :: Nil,
List(CustomTypeToken[Id]("B"))
)
)
arrowdef.parseAll("[]Absolutely, u32 -> B, C").value should be(
ArrowTypeToken[Id](
(),
(Option.empty[Name[Id]] -> ArrayTypeToken[Id](
(),
CustomTypeToken[Id]("Absolutely")
)) :: (Option.empty[Name[Id]] -> (u32: BasicTypeToken[
Id
]) :: Nil,
Some(CustomTypeToken[Id]("B"))
])) :: Nil,
CustomTypeToken[Id]("B") ::
CustomTypeToken[Id]("C") :: Nil
)
)

View File

@ -29,21 +29,24 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
N: NamesAlgebra[F, Alg],
T: TypesAlgebra[F, Alg],
V: ValuesAlgebra[F, Alg]
): Free[Alg, (List[ValueModel], Option[Type])] =
V.checkArguments(expr.funcName, at, args) >> variable
.fold(freeUnit[Alg].as(Option.empty[Type]))(exportVar =>
at.res.fold(
// TODO: error! we're trying to export variable, but function has no export type
freeUnit[Alg].as(Option.empty[Type])
)(resType =>
N.read(exportVar, mustBeDefined = false).flatMap {
case Some(t @ StreamType(st)) =>
T.ensureTypeMatches(exportVar, st, resType).as(Option(t))
case _ => N.define(exportVar, resType).as(at.res)
}
)
) >>= { (v: Option[Type]) =>
Traverse[List].traverse(args)(V.valueToModel).map(_.flatten).map(_ -> v)
): Free[Alg, (List[ValueModel], List[Type])] =
V.checkArguments(expr.funcName, at, args) >> variables
.foldLeft(freeUnit[Alg].as((List.empty[Type], at.codomain.toList)))((f, exportVar) =>
f.flatMap {
case (exports, Nil) =>
freeUnit[Alg].as(exports -> Nil)
case (exports, resType :: codom) =>
N.read(exportVar, mustBeDefined = false).flatMap {
case Some(t @ StreamType(st)) =>
T.ensureTypeMatches(exportVar, st, resType).as((t :: exports, codom))
case _ =>
N.define(exportVar, resType).as((resType :: exports, codom))
}
}
)
.map(_._1) >>= { (v: List[Type]) =>
Traverse[List].traverse(args)(V.valueToModel).map(_.flatten -> v.reverse)
}
private def toModel[Alg[_]](implicit
@ -74,30 +77,30 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
T: TypesAlgebra[F, Alg],
V: ValuesAlgebra[F, Alg]
): Free[Alg, FuncOp] = {
checkArgsRes(arrowType).flatMap { case (argsResolved, tOp) =>
((variable, tOp) match {
case (Some(v), Some(t)) =>
Free.pure[Alg, Option[Call.Export]](Option(Call.Export(v.value, t)))
case (Some(v), None) =>
T.expectNoExport(v).map(_ => None)
case _ =>
Free.pure[Alg, Option[Call.Export]](None)
}).map(maybeExport =>
FuncOp.leaf(serviceId match {
case Some(sid) =>
CallServiceTag(
serviceId = sid,
funcName = funcName.value,
Call(argsResolved, maybeExport.toList)
)
case None =>
CallArrowTag(
funcName = funcName.value,
Call(argsResolved, maybeExport.toList)
)
})
)
checkArgsRes(arrowType).flatMap { (argsResolved, resTypes) =>
variables
.drop(arrowType.codomain.length)
.headOption
.fold(
Free.pure((variables zip resTypes).map { case (v, t) =>
Call.Export(v.value, t)
})
)(T.expectNoExport(_).as(Nil))
.map(maybeExport =>
FuncOp.leaf(serviceId match {
case Some(sid) =>
CallServiceTag(
serviceId = sid,
funcName = funcName.value,
Call(argsResolved, maybeExport)
)
case None =>
CallArrowTag(
funcName = funcName.value,
Call(argsResolved, maybeExport)
)
})
)
}
}

View File

@ -29,35 +29,40 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
A.beginScope(name) >> Applicative[Free[Alg, *]]
.product(
// Collect argument types, define local variables
args
arrowTypeExpr.args
.foldLeft(
// Begin scope -- for mangling
N.beginScope(name).as[Chain[(String, Type)]](Chain.empty)
) { case (f, Arg(argName, argType)) =>
// Resolve arg type, remember it
f.flatMap(acc =>
T.resolveType(argType).flatMap {
case Some(t: ArrowType) =>
N.defineArrow(argName, t, isRoot = false).as(acc.append(argName.value -> t))
case Some(t) =>
N.define(argName, t).as(acc.append(argName.value -> t))
case None =>
Free.pure(acc)
}
)
) {
case (f, (Some(argName), argType)) =>
// Resolve arg type, remember it
f.flatMap(acc =>
T.resolveType(argType).flatMap {
case Some(t: ArrowType) =>
N.defineArrow(argName, t, isRoot = false).as(acc.append(argName.value -> t))
case Some(t) =>
N.define(argName, t).as(acc.append(argName.value -> t))
case None =>
Free.pure(acc)
}
)
// Unnamed argument
case (f, _) => f
}
.map(_.toList),
// Resolve return type
ret.fold(Free.pure[Alg, Option[Type]](None))(T.resolveType(_))
ret.foldLeft(Free.pure[Alg, List[Type]](Nil))((f, t) =>
f.flatMap(ts => T.resolveType(t).map(ts.prependedAll))
)
)
.map(argsAndRes =>
ArrowType(ProductType.labelled(argsAndRes._1), ProductType(argsAndRes._2.toList))
ArrowType(ProductType.labelled(argsAndRes._1), ProductType(argsAndRes._2.reverse))
)
def generateFuncModel[Alg[_]](funcArrow: ArrowType, retModel: List[ValueModel], body: FuncOp)(
implicit N: NamesAlgebra[F, Alg]
): Free[Alg, Model] = {
val argNames = args.map(_.name.value)
val argNames = arrowTypeExpr.args.collect { case (Some(n), _) => n.value }
val model = FuncModel(
name = name.value,
@ -80,27 +85,27 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
A: AbilitiesAlgebra[F, Alg]
): Free[Alg, Model] =
// Check return value type
((funcArrow.res, retValue) match {
case (Some(t), Some(v)) =>
V.valueToModel(v)
.flatTap {
case Some(vt) => T.ensureTypeMatches(v, t, vt.lastType).void
case None => Free.pure[Alg, Unit](())
}
.map(_.toList)
case (_, _) =>
Free.pure[Alg, List[ValueModel]](Nil)
// Erase arguments and internal variables
}).flatMap(retModel =>
A.endScope() >> N.endScope() >> (bodyGen match {
case body: FuncOp if ret.isDefined == retValue.isDefined =>
generateFuncModel[Alg](funcArrow, retModel, body)
case ReturnModel =>
generateFuncModel[Alg](funcArrow, retModel, FuncOps.empty)
case m => Free.pure[Alg, Model](Model.error("Function body is not a funcOp, it's " + m))
((funcArrow.codomain.toList zip retValue)
.foldLeft(Free.pure[Alg, List[ValueModel]](Nil)) { case (f, (t, v)) =>
f.flatMap(vs =>
V.valueToModel(v)
.flatTap {
case Some(vt) => T.ensureTypeMatches(v, t, vt.lastType).void
case None => Free.pure[Alg, Unit](())
}
.map(vs.prependedAll)
)
})
)
.flatMap(retModel =>
// Erase arguments and internal variables
A.endScope() >> N.endScope() >> (bodyGen match {
case body: FuncOp if ret.length == retValue.length =>
generateFuncModel[Alg](funcArrow, retModel, body)
case ReturnModel =>
generateFuncModel[Alg](funcArrow, retModel, FuncOps.empty)
case m => Free.pure[Alg, Model](Model.error("Function body is not a funcOp, it's " + m))
})
)
def program[Alg[_]](implicit
T: TypesAlgebra[F, Alg],

View File

@ -5,9 +5,10 @@ import aqua.parser.expr.ReturnExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import cats.syntax.functor._
import cats.syntax.traverse._
class ReturnSem[F[_]](val expr: ReturnExpr[F]) extends AnyVal {
def program[Alg[_]](implicit V: ValuesAlgebra[F, Alg]): Prog[Alg, Model] =
V.resolveType(expr.value) as (ReturnModel: Model)
expr.values.traverse(V.resolveType) as (ReturnModel: Model)
}

View File

@ -59,23 +59,33 @@ case class TypesState[F[_]](
case ctt: CustomTypeToken[F] => strict.get(ctt.value)
case btt: BasicTypeToken[F] => Some(btt.value)
case ArrowTypeToken(_, args, res) =>
val strictArgs = args.map(resolveTypeToken).collect { case Some(dt: DataType) =>
val strictArgs = args.map(_._2).map(resolveTypeToken).collect { case Some(dt: DataType) =>
dt
}
val strictRes = res.flatMap(resolveTypeToken).collect { case dt: DataType =>
val strictRes: List[DataType] = res.flatMap(resolveTypeToken).collect { case dt: DataType =>
dt
}
Option.when(strictRes.isDefined == res.isDefined && strictArgs.length == args.length)(
Option.when(strictRes.length == res.length && strictArgs.length == args.length)(
ArrowType(ProductType(strictArgs), ProductType(strictRes.toList))
)
}
def resolveArrowDef(ad: ArrowTypeToken[F]): ValidatedNec[(Token[F], String), ArrowType] =
ad.resType.flatMap(resolveTypeToken) match {
case resType if resType.isDefined == ad.resType.isDefined =>
val (errs, argTypes) = ad.argTypes
.map(tt => resolveTypeToken(tt).toRight(tt -> s"Type unresolved"))
.foldLeft[(Chain[(Token[F], String)], Chain[Type])]((Chain.empty, Chain.empty)) {
def resolveArrowDef(ad: ArrowTypeToken[F]): ValidatedNec[(Token[F], String), ArrowType] = {
val resType = ad.res.map(resolveTypeToken)
NonEmptyChain
.fromChain(Chain.fromSeq(ad.res.zip(resType).collect { case (dt, None) =>
dt -> "Cannot resolve the result type"
}))
.fold[ValidatedNec[(Token[F], String), ArrowType]] {
val (errs, argTypes) = ad.args.map { (argName, tt) =>
resolveTypeToken(tt)
.toRight(tt -> s"Type unresolved")
.map(argName.map(_.value) -> _)
}
.foldLeft[(Chain[(Token[F], String)], Chain[(Option[String], Type)])](
(Chain.empty, Chain.empty)
) {
case ((errs, argTypes), Right(at)) => (errs, argTypes.append(at))
case ((errs, argTypes), Left(e)) => (errs.append(e), argTypes)
}
@ -83,12 +93,15 @@ case class TypesState[F[_]](
NonEmptyChain
.fromChain(errs)
.fold[ValidatedNec[(Token[F], String), ArrowType]](
Valid(ArrowType(ProductType(argTypes.toList), ProductType(resType.toList)))
Valid(
ArrowType(
ProductType.maybeLabelled(argTypes.toList),
ProductType(resType.flatten.toList)
)
)
)(Invalid(_))
case _ =>
Invalid(NonEmptyChain.one(ad.resType.getOrElse(ad) -> "Cannot resolve the result type"))
}
}(Invalid(_))
}
def resolveOps(
rootT: Type,

View File

@ -70,7 +70,7 @@ object CompareTypes {
) -1.0
else NaN
private def compareProducts(l: ProductType, r: ProductType): Double = (l, r) match {
private def compareProducts(l: ProductType, r: ProductType): Double = ((l, r): @unchecked) match {
case (NilType, NilType) => 0.0
case (_: ConsType, NilType) => -1.0
case (NilType, _: ConsType) => 1.0

View File

@ -59,6 +59,14 @@ object ProductType {
case _ => NilType
}
def maybeLabelled(types: List[(Option[String], Type)]): ProductType = types match {
case (Some(l), h) :: t =>
ConsType.cons(l, h, ProductType.maybeLabelled(t))
case (None, h) :: t =>
ConsType.cons(h, ProductType.maybeLabelled(t))
case _ => NilType
}
def labelled(types: List[(String, Type)]): ProductType = types match {
case (l, h) :: t =>
ConsType.cons(l, h, ProductType.labelled(t))
@ -181,24 +189,11 @@ case class StructType(name: String, fields: NonEmptyMap[String, Type]) extends D
*/
case class ArrowType(domain: ProductType, codomain: ProductType) extends Type {
@deprecated(
"Use .domain to get arguments, add .args helper to the typed object, if needed",
"5.08.2021"
)
def args: List[Type] = domain.toList
@deprecated(
"Use .codomain to get results, add .res helper to the typed object, if needed; consider multi-value return",
"5.08.2021"
)
def res: Option[Type] = codomain.uncons.map(_._1)
@deprecated(
"Replace with this function's body",
"5.08.2021"
)
def acceptsAsArguments(valueTypes: List[Type]): Boolean =
domain.acceptsValueOf(ProductType(valueTypes))
lazy val res: Option[Type] = codomain.toList match {
case Nil => None
case a :: Nil => Some(a)
case _ => Some(codomain)
}
override def toString: String =
s"$domain -> $codomain"