mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 14:40:17 +00:00
fix(compiler): Import abilities with use
[LNG-324] (#1077)
* Add IntoApply * Savepoint * Add backtrack * Return ability name * Add service resolution * Return import ability * Add test * Rewrite toDottedName * Rewrite ability resolution * Fix offset * Add tests * Add test * Add comments * Add test --------- Co-authored-by: Dima <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
parent
d7fef3db5f
commit
a6c8e75c27
@ -87,6 +87,25 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
inside(funcs)(test)
|
||||
)
|
||||
|
||||
def through(peer: ValueModel) =
|
||||
MakeRes.hop(peer)
|
||||
|
||||
val relay = VarRaw("-relay-", ScalarType.string)
|
||||
|
||||
def getDataSrv(name: String, varName: String, t: Type) = {
|
||||
CallServiceRes(
|
||||
LiteralModel.quote("getDataSrv"),
|
||||
name,
|
||||
CallRes(Nil, Some(CallModel.Export(varName, t))),
|
||||
LiteralModel.fromRaw(ValueRaw.InitPeerId)
|
||||
).leaf
|
||||
}
|
||||
|
||||
private val init = LiteralModel.fromRaw(ValueRaw.InitPeerId)
|
||||
|
||||
private def join(vm: VarModel, size: ValueModel) =
|
||||
ResBuilder.join(vm, size, init)
|
||||
|
||||
"aqua compiler" should "compile a simple snippet to the right context" in {
|
||||
|
||||
val src = Map(
|
||||
@ -115,25 +134,6 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
}
|
||||
}
|
||||
|
||||
def through(peer: ValueModel) =
|
||||
MakeRes.hop(peer)
|
||||
|
||||
val relay = VarRaw("-relay-", ScalarType.string)
|
||||
|
||||
def getDataSrv(name: String, varName: String, t: Type) = {
|
||||
CallServiceRes(
|
||||
LiteralModel.quote("getDataSrv"),
|
||||
name,
|
||||
CallRes(Nil, Some(CallModel.Export(varName, t))),
|
||||
LiteralModel.fromRaw(ValueRaw.InitPeerId)
|
||||
).leaf
|
||||
}
|
||||
|
||||
private val init = LiteralModel.fromRaw(ValueRaw.InitPeerId)
|
||||
|
||||
private def join(vm: VarModel, size: ValueModel) =
|
||||
ResBuilder.join(vm, size, init)
|
||||
|
||||
it should "create right topology" in {
|
||||
val src = Map(
|
||||
"index.aqua" ->
|
||||
@ -502,4 +502,385 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
val moduleNames = List("Test", "Imp", "Sub", "Path").inits
|
||||
.takeWhile(_.nonEmpty)
|
||||
.map(_.mkString("."))
|
||||
.toList
|
||||
|
||||
it should "import function with `use`" in {
|
||||
def test(name: String, rename: Option[String]) = {
|
||||
val src = Map(
|
||||
"main.aqua" ->
|
||||
s"""aqua Main
|
||||
|export main
|
||||
|use "import.aqua"${rename.fold("")(" as " + _)}
|
||||
|func main() -> i32:
|
||||
| <- ${rename.getOrElse(name)}.foo()
|
||||
|""".stripMargin
|
||||
)
|
||||
val imports = Map(
|
||||
"import.aqua" ->
|
||||
s"""aqua $name declares foo
|
||||
|func foo() -> i32:
|
||||
| <- 42
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
val transformCfg = TransformConfig(relayVarName = None)
|
||||
|
||||
insideRes(src, imports, transformCfg)(
|
||||
"main"
|
||||
) { case main :: _ =>
|
||||
val expected = XorRes.wrap(
|
||||
respCall(transformCfg, LiteralModel.number(42), initPeer),
|
||||
errorCall(transformCfg, 0, initPeer)
|
||||
)
|
||||
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
moduleNames.foreach { name =>
|
||||
val rename = "Imported"
|
||||
|
||||
withClue(s"Testing $name") {
|
||||
test(name, None)
|
||||
}
|
||||
withClue(s"Testing $name as $rename") {
|
||||
test(name, rename.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "import service with `use`" in {
|
||||
def test(name: String, rename: Option[String]) = {
|
||||
val srvName = rename.getOrElse(name) + ".Srv"
|
||||
val src = Map(
|
||||
"main.aqua" ->
|
||||
s"""aqua Main
|
||||
|export main
|
||||
|use "import.aqua"${rename.fold("")(" as " + _)}
|
||||
|
|
||||
|func main() -> i32:
|
||||
| a <- $srvName.call()
|
||||
| $srvName "res-id"
|
||||
| b <- $srvName.call()
|
||||
| <- a + b
|
||||
|""".stripMargin
|
||||
)
|
||||
val imports = Map(
|
||||
"import.aqua" ->
|
||||
s"""aqua $name declares *
|
||||
|service Srv("def-id"):
|
||||
| call() -> i32
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
val transformCfg = TransformConfig(relayVarName = None)
|
||||
|
||||
insideRes(src, imports, transformCfg)(
|
||||
"main"
|
||||
) { case main :: _ =>
|
||||
def call(id: String, exp: CallModel.Export) =
|
||||
CallServiceRes(
|
||||
LiteralModel.quote(id),
|
||||
"call",
|
||||
CallRes(Nil, Some(exp)),
|
||||
initPeer
|
||||
).leaf
|
||||
|
||||
val a = CallModel.Export("ret", ScalarType.i32)
|
||||
val b = CallModel.Export("ret-0", ScalarType.i32)
|
||||
val add = CallModel.Export("add", ScalarType.i32)
|
||||
val expected = XorRes.wrap(
|
||||
SeqRes.wrap(
|
||||
call("def-id", a),
|
||||
call("res-id", b),
|
||||
CallServiceRes(
|
||||
LiteralModel.quote("math"),
|
||||
"add",
|
||||
CallRes(List(a.asVar, b.asVar), Some(add)),
|
||||
initPeer
|
||||
).leaf,
|
||||
respCall(transformCfg, add.asVar, initPeer)
|
||||
),
|
||||
errorCall(transformCfg, 0, initPeer)
|
||||
)
|
||||
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
moduleNames.foreach { name =>
|
||||
val rename = "Imported"
|
||||
|
||||
withClue(s"Testing $name") {
|
||||
test(name, None)
|
||||
}
|
||||
withClue(s"Testing $name as $rename") {
|
||||
test(name, rename.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "import ability with `use`" in {
|
||||
def test(name: String, rename: Option[String]) = {
|
||||
val abName = rename.getOrElse(name) + ".Ab"
|
||||
val src = Map(
|
||||
"main.aqua" ->
|
||||
s"""aqua Main
|
||||
|export main
|
||||
|use "import.aqua"${rename.fold("")(" as " + _)}
|
||||
|func useAb{$abName}() -> i32:
|
||||
| <- $abName.a
|
||||
|
|
||||
|func main() -> i32:
|
||||
| ab = $abName(a = 42)
|
||||
| <- useAb{ab}()
|
||||
|""".stripMargin
|
||||
)
|
||||
val imports = Map(
|
||||
"import.aqua" ->
|
||||
s"""aqua $name declares *
|
||||
|ability Ab:
|
||||
| a: i32
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
val transformCfg = TransformConfig(relayVarName = None)
|
||||
|
||||
insideRes(src, imports, transformCfg)(
|
||||
"main"
|
||||
) { case main :: _ =>
|
||||
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
|
||||
val props = ap.copy(name = "literal_props")
|
||||
val expected = XorRes.wrap(
|
||||
SeqRes.wrap(
|
||||
// NOTE: Result of compilation is inefficient
|
||||
ApRes(LiteralModel.number(42), ap).leaf,
|
||||
ApRes(ap.asVar, props).leaf,
|
||||
respCall(transformCfg, props.asVar, initPeer)
|
||||
),
|
||||
errorCall(transformCfg, 0, initPeer)
|
||||
)
|
||||
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
moduleNames.foreach { name =>
|
||||
val rename = "Imported"
|
||||
|
||||
withClue(s"Testing $name") {
|
||||
test(name, None)
|
||||
}
|
||||
withClue(s"Testing $name as $rename") {
|
||||
test(name, rename.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "import ability (nested) with `use`" in {
|
||||
def test(name: String, rename: Option[String]) = {
|
||||
val impName = rename.getOrElse(name)
|
||||
val abName = impName + ".Ab"
|
||||
val src = Map(
|
||||
"main.aqua" ->
|
||||
s"""aqua Main
|
||||
|export main
|
||||
|use "import.aqua"${rename.fold("")(" as " + _)}
|
||||
|func useAb{$abName}() -> i32:
|
||||
| <- $abName.ab1.ab0.call($abName.ab1.ab0.a)
|
||||
|
|
||||
|func main() -> i32:
|
||||
| id = (x: i32) -> i32:
|
||||
| <- x
|
||||
| ab0 = $impName.Ab0(a = 42, call = id)
|
||||
| ab1 = $impName.Ab1(ab0 = ab0)
|
||||
| ab = $abName(ab1 = ab1)
|
||||
| <- useAb{ab}()
|
||||
|""".stripMargin
|
||||
)
|
||||
val imports = Map(
|
||||
"import.aqua" ->
|
||||
s"""aqua $name declares *
|
||||
|ability Ab0:
|
||||
| a: i32
|
||||
| call(x: i32) -> i32
|
||||
|
|
||||
|ability Ab1:
|
||||
| ab0: Ab0
|
||||
|
|
||||
|ability Ab:
|
||||
| ab1: Ab1
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
val transformCfg = TransformConfig(relayVarName = None)
|
||||
|
||||
insideRes(src, imports, transformCfg)(
|
||||
"main"
|
||||
) { case main :: _ =>
|
||||
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
|
||||
val props = ap.copy(name = "literal_props")
|
||||
val expected = XorRes.wrap(
|
||||
SeqRes.wrap(
|
||||
// NOTE: Result of compilation is inefficient
|
||||
ApRes(LiteralModel.number(42), ap).leaf,
|
||||
ApRes(ap.asVar, props).leaf,
|
||||
respCall(transformCfg, props.asVar, initPeer)
|
||||
),
|
||||
errorCall(transformCfg, 0, initPeer)
|
||||
)
|
||||
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
moduleNames.foreach { name =>
|
||||
val rename = "Imported"
|
||||
|
||||
withClue(s"Testing $name") {
|
||||
test(name, None)
|
||||
}
|
||||
withClue(s"Testing $name as $rename") {
|
||||
test(name, rename.some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "import abilities in chain of imports" in {
|
||||
case class Imp(header: String, rename: Option[String] = None) {
|
||||
val as = rename.fold("")(" as " + _)
|
||||
val name = rename.getOrElse(header)
|
||||
|
||||
override def toString: String = s"$header$as"
|
||||
}
|
||||
|
||||
def test(hierarchy: List[Imp]) = {
|
||||
hierarchy.nonEmpty should be(true)
|
||||
|
||||
def genImp(header: String, imported: Imp, path: String): String = {
|
||||
s"""aqua ${header} declares *
|
||||
|use "${path}"${imported.as}
|
||||
|
|
||||
|ability Inner:
|
||||
| ab: ${imported.name}.Outer
|
||||
|
|
||||
|ability Outer:
|
||||
| ab: Inner
|
||||
|
|
||||
|func create{${imported.name}.Outer}() -> Inner:
|
||||
| ab = Inner(ab = ${imported.name}.Outer)
|
||||
| <- ab
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
val base = Imp("Base")
|
||||
val basePath = "base.aqua"
|
||||
val baseCode = s"""aqua Base declares *
|
||||
|
|
||||
|ability Outer:
|
||||
| a: i32
|
||||
| call(x: i32) -> i32
|
||||
|""".stripMargin
|
||||
|
||||
val withPaths = hierarchy.zipWithIndex.map { case (imp, idx) =>
|
||||
imp -> s"import$idx.aqua"
|
||||
}
|
||||
val nexts = withPaths.tail :+ (base -> basePath)
|
||||
val imports = withPaths
|
||||
.zip(nexts)
|
||||
.map { case ((curImp, curPath), (nextImp, nextPath)) =>
|
||||
curPath -> genImp(curImp.header, nextImp, nextPath)
|
||||
}
|
||||
.appended(basePath -> baseCode)
|
||||
.toMap
|
||||
|
||||
val importStmts = withPaths.map { case (imp, path) =>
|
||||
s"use \"${path}\"${imp.as}"
|
||||
}.prepended(s"use \"${basePath}\"")
|
||||
val createStmts = hierarchy.reverse.zipWithIndex.flatMap { case (imp, idx) =>
|
||||
s"abIn${idx + 1} = ${imp.name}.create{abOut$idx}()" ::
|
||||
s"abOut${idx + 1} = ${imp.name}.Outer(ab = abIn${idx + 1})" ::
|
||||
Nil
|
||||
}.prepended("abOut0 = Base.Outer(a = 42, call = id)")
|
||||
|
||||
val lastAb = s"abOut${hierarchy.size}" + ".ab".repeat(hierarchy.size * 2)
|
||||
val main = s"""aqua Main
|
||||
|export main
|
||||
|${importStmts.mkString("\n")}
|
||||
|
|
||||
|func main() -> i32:
|
||||
| id = (x: i32) -> i32:
|
||||
| <- x
|
||||
| ${createStmts.mkString("\n ")}
|
||||
| <- $lastAb.call($lastAb.a)
|
||||
|""".stripMargin
|
||||
|
||||
val src = Map(
|
||||
"main.aqua" -> main
|
||||
)
|
||||
|
||||
val transformCfg = TransformConfig(relayVarName = None)
|
||||
|
||||
insideRes(src, imports, transformCfg)(
|
||||
"main"
|
||||
) { case main :: _ =>
|
||||
val ap = CallModel.Export("literal_ap", LiteralType.unsigned)
|
||||
val props = ap.copy(name = "literal_props")
|
||||
val expected = XorRes.wrap(
|
||||
SeqRes.wrap(
|
||||
// NOTE: Result of compilation is inefficient
|
||||
ApRes(LiteralModel.number(42), ap).leaf,
|
||||
ApRes(ap.asVar, props).leaf,
|
||||
respCall(transformCfg, props.asVar, initPeer)
|
||||
),
|
||||
errorCall(transformCfg, 0, initPeer)
|
||||
)
|
||||
|
||||
main.body.equalsOrShowDiff(expected) should be(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Simple
|
||||
(1 to 10).map(i => (1 to i).map(n => Imp(s"Imp$n")).toList).foreach { h =>
|
||||
withClue(s"Testing ${h.mkString(" -> ")}") {
|
||||
test(h)
|
||||
}
|
||||
}
|
||||
|
||||
// With renaming
|
||||
(1 to 10)
|
||||
.map(i =>
|
||||
(1 to i)
|
||||
.map(n =>
|
||||
// Rename every second one
|
||||
Imp(s"Imp$n", s"Renamed$n".some.filter(_ => n % 2 == 0))
|
||||
)
|
||||
.toList
|
||||
)
|
||||
.foreach { h =>
|
||||
withClue(s"Testing ${h.mkString(" -> ")}") {
|
||||
test(h)
|
||||
}
|
||||
}
|
||||
|
||||
// With subpath
|
||||
(1 to 10)
|
||||
.map(i =>
|
||||
(1 to i)
|
||||
.map(n => s"Imp$n")
|
||||
.inits
|
||||
.takeWhile(_.nonEmpty)
|
||||
.map(p => Imp(p.mkString(".")))
|
||||
.toList
|
||||
)
|
||||
.foreach { h =>
|
||||
withClue(s"Testing ${h.mkString(" -> ")}") {
|
||||
test(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,11 @@ enum NamedArg[F[_]] extends Token[F] {
|
||||
case Full(name, value) => Full(name.mapK(fk), value.mapK(fk))
|
||||
case Short(variable) => Short(variable.mapK(fk))
|
||||
}
|
||||
|
||||
override def toString: String = this match {
|
||||
case Full(name, value) => s"$name = $value"
|
||||
case Short(variable) => variable.toString
|
||||
}
|
||||
}
|
||||
|
||||
object NamedArg {
|
||||
|
@ -1,20 +1,20 @@
|
||||
package aqua.parser.lexer
|
||||
|
||||
import aqua.parser.lexer.CallArrowToken.CallBraces
|
||||
import aqua.parser.lexer.NamedArg.namedArgs
|
||||
import aqua.parser.lexer.Token.*
|
||||
import aqua.parser.lift.LiftParser
|
||||
import aqua.parser.lift.LiftParser.*
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
import aqua.types.LiteralType
|
||||
import aqua.parser.lexer.CallArrowToken.CallBraces
|
||||
import aqua.parser.lexer.NamedArg.namedArgs
|
||||
|
||||
import cats.~>
|
||||
import cats.data.{NonEmptyList, NonEmptyMap}
|
||||
import cats.parse.{Numbers, Parser as P, Parser0 as P0}
|
||||
import cats.syntax.comonad.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.{Comonad, Functor}
|
||||
import cats.~>
|
||||
import scala.language.postfixOps
|
||||
|
||||
sealed trait PropertyOp[F[_]] extends Token[F] {
|
||||
@ -36,7 +36,7 @@ case class IntoField[F[_]: Comonad](name: F[String]) extends PropertyOp[F] {
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): PropertyOp[K] = copy(fk(name))
|
||||
|
||||
def value: String = name.extract
|
||||
lazy val value: String = name.extract
|
||||
|
||||
override def toString: String = name.extract
|
||||
}
|
||||
@ -46,6 +46,8 @@ case class IntoIndex[F[_]: Comonad](point: F[Unit], idx: Option[ValueToken[F]])
|
||||
override def as[T](v: T): F[T] = point.as(v)
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): IntoIndex[K] = copy(fk(point), idx.map(_.mapK(fk)))
|
||||
|
||||
override def toString: String = s"[$idx]"
|
||||
}
|
||||
|
||||
case class IntoCopy[F[_]: Comonad](
|
||||
@ -56,6 +58,27 @@ case class IntoCopy[F[_]: Comonad](
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): IntoCopy[K] =
|
||||
copy(fk(point), args.map(_.mapK(fk)))
|
||||
|
||||
override def toString: String = s".copy(${args.map(_.toString).toList.mkString(", ")})"
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: This is parsed when we have parens after a name, but `IntoArrow` failed to parse.
|
||||
* This is a case of imported named type, e.g. `Some.Imported.Module.DefinedAbility(...)`
|
||||
* It is transformed into `NamedTypeValue` in `ValuesAlgebra`
|
||||
* TODO: Eliminate `IntoArrow`, unify it with this property
|
||||
*/
|
||||
case class IntoApply[F[_]: Comonad](
|
||||
argsF: F[NonEmptyList[NamedArg[F]]]
|
||||
) extends PropertyOp[F] {
|
||||
lazy val args: NonEmptyList[NamedArg[F]] = argsF.extract
|
||||
|
||||
override def as[T](v: T): F[T] = argsF.as(v)
|
||||
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): IntoApply[K] =
|
||||
copy(fk(argsF.map(_.map(_.mapK(fk)))))
|
||||
|
||||
override def toString: String = s"(${args.map(_.toString).toList.mkString(", ")})"
|
||||
}
|
||||
|
||||
object PropertyOp {
|
||||
@ -87,8 +110,19 @@ object PropertyOp {
|
||||
}
|
||||
}
|
||||
|
||||
private val parseApply: P[PropertyOp[Span.S]] =
|
||||
namedArgs.lift.map(IntoApply.apply)
|
||||
|
||||
private val parseOp: P[PropertyOp[Span.S]] =
|
||||
P.oneOf(parseCopy.backtrack :: parseArrow.backtrack :: parseField :: parseIdx :: Nil)
|
||||
P.oneOf(
|
||||
// NOTE: order is important here
|
||||
// intoApply has lower priority than intoArrow
|
||||
parseCopy.backtrack ::
|
||||
parseArrow.backtrack ::
|
||||
parseField ::
|
||||
parseIdx ::
|
||||
parseApply.backtrack :: Nil
|
||||
)
|
||||
|
||||
val ops: P[NonEmptyList[PropertyOp[Span.S]]] =
|
||||
parseOp.rep
|
||||
|
@ -123,7 +123,9 @@ case class ArrowTypeToken[S[_]: Comonad](
|
||||
abilities.map(_.mapK(fk))
|
||||
)
|
||||
def argTypes: List[TypeToken[S]] = abilities ++ args.map(_._2)
|
||||
lazy val absWithArgs: List[(Option[Name[S]], TypeToken[S])] = abilities.map(n => Some(n.asName) -> n) ++ args
|
||||
|
||||
lazy val absWithArgs: List[(Option[Name[S]], TypeToken[S])] =
|
||||
abilities.map(n => Some(n.asName) -> n) ++ args
|
||||
}
|
||||
|
||||
object ArrowTypeToken {
|
||||
@ -136,9 +138,9 @@ object ArrowTypeToken {
|
||||
).map(_.toList)
|
||||
|
||||
// {SomeAb, SecondAb} for NamedTypeToken
|
||||
def abilities(): P0[List[NamedTypeToken[S]]] =
|
||||
(`{` *> comma(`Class`.surroundedBy(`/s*`).lift.map(NamedTypeToken(_)))
|
||||
.map(_.toList) <* `}`).?.map(_.getOrElse(List.empty))
|
||||
def abilities(): P0[List[NamedTypeToken[S]]] = (
|
||||
`{` *> comma(NamedTypeToken.dotted).map(_.toList) <* `}`
|
||||
).?.map(_.getOrElse(List.empty))
|
||||
|
||||
def `arrowdef`(argTypeP: P[TypeToken[Span.S]]): P[ArrowTypeToken[Span.S]] =
|
||||
((abilities() ~ comma0(argTypeP)).with1 ~ ` -> `.lift ~
|
||||
|
@ -42,112 +42,105 @@ case class PropertyToken[F[_]: Comonad](
|
||||
name.forall(c => !c.isLetter || c.isUpper)
|
||||
|
||||
/**
|
||||
* This method tries to convert property token to
|
||||
* property token with dotted var name inside value token.
|
||||
*
|
||||
* Next properties pattern is untouched:
|
||||
* Class (field)*
|
||||
*
|
||||
* Next properties pattern is transformed:
|
||||
* (Class)* (CONST | field) ..props..
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* this part is transformed to dotted name.
|
||||
* Try to transform this token to imported ability access
|
||||
* e.g. in `Some.Imported.Module.Ab.innerAb.call(...)`:
|
||||
* - `Some.Imported.Module` is imported module name
|
||||
* - `Ab.innerAb.call(...)` is ability access
|
||||
* so it should be handled as `(Some.Imported.Module.Ab).innerAb.call(...)`
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* ability name inside `VarToken` as one string
|
||||
* but we don't know this in advance, so this method returns
|
||||
* a list of all possible (imported module name, ability access value token) pairs
|
||||
* so calling code can check what prefix is valid imported module name and
|
||||
* handle the corresponding ability access value token.
|
||||
*/
|
||||
private def toDottedName: Option[ValueToken[F]] = value match {
|
||||
case VarToken(name) =>
|
||||
// Pattern `Class (field)*` is ability access
|
||||
// and should not be transformed
|
||||
val isAbility = isClass(name.value) && properties.forall {
|
||||
case f @ IntoField(_) => isField(f.value)
|
||||
case _ => true
|
||||
def toAbility: List[(NamedTypeToken[F], ValueToken[F])] =
|
||||
value match {
|
||||
// NOTE: guard against recursion: if dot is alredy in the name, do not transform
|
||||
case VarToken(name) if !name.value.contains(".") =>
|
||||
val fields = properties.toList.takeWhile {
|
||||
case IntoField(_) => true
|
||||
case _ => false
|
||||
}.collect { case f @ IntoField(_) => f.value }.toList
|
||||
val names = name.value +: fields
|
||||
|
||||
fields.inits
|
||||
// do not use the last field
|
||||
// those cases are handled in `toCallArrow` and `toNamedValue`
|
||||
.drop(1)
|
||||
.map { init =>
|
||||
// Length of the import name
|
||||
val importLength = init.length + 1
|
||||
// Length of the imported name
|
||||
val nameLength = importLength + 1
|
||||
val newProps = NonEmptyList.fromList(
|
||||
properties.toList.drop(importLength)
|
||||
)
|
||||
val newName = name.rename(names.take(nameLength).mkString("."))
|
||||
val importAbility = name.rename(names.take(importLength).mkString(".")).asTypeToken
|
||||
|
||||
val varToken = VarToken(newName)
|
||||
val token = newProps.fold(varToken)(ps => PropertyToken(varToken, ps))
|
||||
|
||||
importAbility -> token
|
||||
}
|
||||
.toList
|
||||
// test shorter prefixes first
|
||||
.reverse
|
||||
case _ => Nil
|
||||
}
|
||||
|
||||
if (isAbility) none
|
||||
else {
|
||||
// Gather prefix of properties that are IntoField
|
||||
val props = name.value +: properties.toList.view.map {
|
||||
/**
|
||||
* Try to convert this token into `CallArrowToken`
|
||||
* e.g. `Some.Imported.Module.call(...)`
|
||||
* ^^^^^^^^^^^^^^^^^^^^ ^^^^
|
||||
* ability name function name
|
||||
*/
|
||||
def toCallArrow: Option[CallArrowToken[F]] = (
|
||||
value,
|
||||
properties.last
|
||||
) match {
|
||||
case (VarToken(name), IntoArrow(funcName, args)) =>
|
||||
properties.init.traverse {
|
||||
case IntoField(name) => name.extract.some
|
||||
case _ => none
|
||||
}.takeWhile(_.isDefined).flatten.toList
|
||||
|
||||
val propsWithIndex = props.zipWithIndex
|
||||
|
||||
// Find first property that is not Class
|
||||
val classesTill = propsWithIndex.find { case (name, _) =>
|
||||
!isClass(name)
|
||||
}.collect { case (_, idx) =>
|
||||
idx
|
||||
}.getOrElse(props.length)
|
||||
|
||||
// Find last property after classes
|
||||
// that is CONST or field
|
||||
val lastSuitable = propsWithIndex
|
||||
.take(classesTill)
|
||||
.findLast { case (name, _) =>
|
||||
isConst(name) || isField(name)
|
||||
}
|
||||
.collect { case (_, idx) => idx }
|
||||
|
||||
lastSuitable.map(last =>
|
||||
val newProps = NonEmptyList.fromList(
|
||||
properties.toList.drop(last + 1)
|
||||
}.map { fields =>
|
||||
val imported = name
|
||||
.rename(
|
||||
(name.value +: fields).mkString(".")
|
||||
)
|
||||
val newName = props.take(last + 1).mkString(".")
|
||||
val varToken = VarToken(name.rename(newName))
|
||||
.asTypeToken
|
||||
|
||||
newProps.fold(varToken)(props => PropertyToken(varToken, props))
|
||||
)
|
||||
}
|
||||
case _ => none
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to convert property token to
|
||||
* call arrow token.
|
||||
*
|
||||
* Next properties pattern is transformed:
|
||||
* (Class)+ arrow()
|
||||
* ^^^^^^^
|
||||
* this part is transformed to ability name.
|
||||
*/
|
||||
private def toCallArrow: Option[CallArrowToken[F]] = value match {
|
||||
case VarToken(name) =>
|
||||
val ability = properties.init.traverse {
|
||||
case f @ IntoField(_) => f.value.some
|
||||
case _ => none
|
||||
}.map(
|
||||
name.value +: _
|
||||
).filter(
|
||||
_.forall(isClass)
|
||||
).map(props => name.rename(props.mkString(".")))
|
||||
|
||||
(properties.last, ability) match {
|
||||
case (IntoArrow(funcName, args), Some(ability)) =>
|
||||
CallArrowToken(
|
||||
ability.asTypeToken.some,
|
||||
imported.some,
|
||||
funcName,
|
||||
args
|
||||
).some
|
||||
case _ => none
|
||||
)
|
||||
}
|
||||
case _ => none
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hacky method to adjust parsing result
|
||||
* to format that was used previously.
|
||||
* This method tries to convert property token to
|
||||
* call arrow token or property token with
|
||||
* dotted var name inside value token.
|
||||
*
|
||||
* @return Some(token) if token was adjusted, None otherwise
|
||||
* Try to convert this token into `NamedValueToken`,
|
||||
* e.g. `Some.Imported.Module.DefinedAbility(...)`
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
* type name
|
||||
*/
|
||||
def adjust: Option[ValueToken[F]] =
|
||||
toCallArrow.orElse(toDottedName)
|
||||
def toNamedValue: Option[NamedValueToken[F]] =
|
||||
(value, properties.last) match {
|
||||
case (v @ VarToken(name), IntoApply(args)) =>
|
||||
properties.init.traverse {
|
||||
case IntoField(name) => name.extract.some
|
||||
case _ => none
|
||||
}.map { props =>
|
||||
val typeName = name
|
||||
.rename(
|
||||
(name.value +: props).mkString(".")
|
||||
)
|
||||
.asTypeToken
|
||||
|
||||
lazy val leadingName: Option[NamedTypeToken[F]] =
|
||||
value match {
|
||||
case VarToken(name) => name.asTypeToken.some
|
||||
NamedValueToken(typeName, args.extract)
|
||||
}
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class HeaderHandler[S[_]: Comonad, C](using
|
||||
.toValidNec(
|
||||
error(
|
||||
tkn,
|
||||
s"Used module has no `module` header. Please add `module` header or use ... as ModuleName, or switch to import"
|
||||
s"Used module has no `aqua` header. Please add `aqua` header or use ... as ModuleName, or switch to import"
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package aqua.semantics.rules
|
||||
|
||||
import aqua.errors.Errors.internalError
|
||||
import aqua.helpers.syntax.optiont.*
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.parser.lexer.InfixToken.{BoolOp, CmpOp, EqOp, MathOp, Op as InfOp}
|
||||
@ -85,6 +86,8 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
idx <- OptionT(op.idx.fold(LiteralRaw.Zero.some.pure)(valueToRaw))
|
||||
valueType <- OptionT(T.resolveIntoIndex(op, rootType, idx.`type`))
|
||||
} yield IntoIndexRaw(idx, valueType)).value
|
||||
case op: IntoApply[S] =>
|
||||
internalError("Unexpected. `IntoApply` expected to be transformed into `NamedValueToken`")
|
||||
}
|
||||
|
||||
def valueToRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] =
|
||||
@ -129,14 +132,29 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
* so here we try to differentiate them and adjust property
|
||||
* token accordingly.
|
||||
*/
|
||||
prop.leadingName.fold(default)(name =>
|
||||
A.isDefinedAbility(name)
|
||||
.flatMap(isDefined =>
|
||||
prop.adjust
|
||||
.filter(_ => isDefined)
|
||||
.fold(default)(valueToRaw)
|
||||
|
||||
val callArrow = OptionT
|
||||
.fromOption(prop.toCallArrow)
|
||||
.filterF(ca =>
|
||||
ca.ability.fold(false.pure)(
|
||||
A.isDefinedAbility
|
||||
)
|
||||
)
|
||||
.widen[ValueToken[S]]
|
||||
|
||||
val ability = OptionT(
|
||||
prop.toAbility.findM { case (ab, _) =>
|
||||
// Test if name is an import
|
||||
A.isDefinedAbility(ab)
|
||||
}
|
||||
).map { case (_, token) => token }
|
||||
|
||||
val namedValue = OptionT
|
||||
.fromOption(prop.toNamedValue)
|
||||
.filterF(nv => T.resolveType(nv.typeName, mustBeDefined = false).map(_.isDefined))
|
||||
.widen[ValueToken[S]]
|
||||
|
||||
callArrow.orElse(ability).orElse(namedValue).foldF(default)(valueToRaw)
|
||||
|
||||
case dvt @ NamedValueToken(typeName, fields) =>
|
||||
(for {
|
||||
|
@ -19,7 +19,7 @@ trait TypesAlgebra[S[_], Alg[_]] {
|
||||
|
||||
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]]
|
||||
|
||||
def resolveServiceType(name: NamedTypeToken[S]): Alg[Option[ServiceType]]
|
||||
def resolveServiceType(name: NamedTypeToken[S], mustBeDefined: Boolean = true): Alg[Option[ServiceType]]
|
||||
|
||||
def defineAbilityType(
|
||||
name: NamedTypeToken[S],
|
||||
|
@ -76,14 +76,18 @@ class TypesInterpreter[S[_], X](using
|
||||
}.as(none)
|
||||
}
|
||||
|
||||
override def resolveServiceType(name: NamedTypeToken[S]): State[X, Option[ServiceType]] =
|
||||
resolveType(name).flatMap {
|
||||
override def resolveServiceType(
|
||||
name: NamedTypeToken[S],
|
||||
mustBeDefined: Boolean = true
|
||||
): State[X, Option[ServiceType]] =
|
||||
resolveType(name, mustBeDefined).flatMap {
|
||||
case Some(serviceType: ServiceType) =>
|
||||
serviceType.some.pure
|
||||
case Some(t) =>
|
||||
case Some(t) if mustBeDefined =>
|
||||
report.error(name, s"Type `$t` is not a service").as(none)
|
||||
case None =>
|
||||
case None if mustBeDefined =>
|
||||
report.error(name, s"Type `${name.value}` is not defined").as(none)
|
||||
case _ => none.pure
|
||||
}
|
||||
|
||||
override def defineAbilityType(
|
||||
|
Loading…
Reference in New Issue
Block a user