Capture and apply topology (#451)

This commit is contained in:
Dmitry Kurinskiy 2022-03-15 14:52:43 +03:00 committed by GitHub
parent fe3015a2cd
commit ab0990dd72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 232 additions and 77 deletions

View File

@ -1,21 +1,68 @@
module Closure declares *
export smth
import "builtin.aqua"
service Srv("srv"):
noop: string -> ()
export closureOut2, closureIn, closureOut, closureBig, closureOut3
service MyOp("op"):
identity(s: string) -> string
service LocalSrv("local_srv"):
inside: -> ()
trial: -> ()
func smth() -> bool, string:
func closureOut2():
on HOST_PEER_ID:
closure = () -> Info:
p2Id <- Peer.identify()
<- p2Id
closure()
func closureIn(peer1: string) -> string:
variable = "const"
co on "x":
Srv.trial()
closure = () -> string:
Srv.inside()
<- variable
co on peer1:
p1Id <- MyOp.identity("co on")
closure = (s: string) -> string:
if s == "in":
LocalSrv.inside()
p2Id <- MyOp.identity(s)
<- p2Id
p <- closure("in")
<- p
on "other":
c <- closure()
Srv.noop(c)
<- true, ""
func closureOut3(peer2: string) -> Info:
on peer2:
closure = (s: string) -> Info:
if s == "in":
LocalSrv.inside()
p2Id <- Peer.identify()
<- p2Id
p2Id <- closure("on")
<- p2Id
func closureOut(peer2: string) -> Info:
closure = func (s: string) -> Info:
if s == "in":
LocalSrv.inside()
p2Id <- Peer.identify()
<- p2Id
on peer2:
p2Id <- closure("on")
<- p2Id
func closureBig(peer1: string, peer2: string) -> string, string:
variable = "const"
co on peer1:
p1Id <- MyOp.identity("co on")
closure = func (s: string) -> string:
p2Id: *string
if s == "in":
p2 <- MyOp.identity(s)
p2Id <<- p2
else:
p2Info <- Peer.identify()
p2Id <<- p2Info.external_addresses!0
<- p2Id!
p <- closure("in")
on peer2:
p2Id <- closure("on")
<- p, p2Id

View File

@ -1,4 +1,12 @@
func optionSugar(numSome: ?u32, numNone: ?u32) -> []u32:
arr = ?[numNone!, numSome!, "123"]
<- arr
func emptySugar() -> []u32, []string, []string, *string, ?u32, []u32, ?string:
numOp = ?[]
strArr = []
strStream = *[]
strEmptyStream: *string
for i <- ?[]:
strEmptyStream <<- "some"
for i <- *[]:
strEmptyStream <<- "some"
for i <- []:
strEmptyStream <<- "some"
<- numOp, strArr, strStream, strEmptyStream, [], ?[], *[]

View File

@ -17,7 +17,7 @@ val scribeV = "3.6.6"
name := "aqua-hll"
val commons = Seq(
baseAquaVersion := "0.6.3",
baseAquaVersion := "0.6.4",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion,
libraryDependencies ++= Seq(

View File

@ -160,7 +160,8 @@ class Runner(
ArrowType(NilType, returnCodomain),
ret,
Map(func.name -> funcCallable),
Map.empty
Map.empty,
None
)
}
}

View File

@ -141,7 +141,8 @@ object ScriptOpts extends Logging {
ArrowType(NilType, NilType),
Nil,
Map(funcName -> callable),
Map.empty
Map.empty,
None
),
tConfig
)

View File

@ -22,11 +22,11 @@ object Test extends IOApp.Simple {
start <- IO(System.currentTimeMillis())
_ <- AquaPathCompiler
.compileFilesTo[IO](
Path("./aqua-src/hack.aqua"),
Path("./aqua-src/closure.aqua"),
List(Path("./aqua")),
Option(Path("./target")),
TypeScriptBackend,
TransformConfig(wrapWithXor = false)
TransformConfig(wrapWithXor = true)
)
.map {
case Validated.Invalid(errs) =>

View File

@ -41,7 +41,11 @@ object ArrowInliner extends Logging {
// Now, substitute the arrows that were received as function arguments
// Use the new op tree (args are replaced with values, names are unique & safe)
callableFuncBody <- TagInliner.handleTree(tree, fn.funcName)
callableFuncBodyNoTopology <- TagInliner.handleTree(tree, fn.funcName)
callableFuncBody =
fn.capturedTopology
.fold[OpModel](SeqModel)(ApplyTopologyModel.apply)
.wrap(callableFuncBodyNoTopology)
// Fix return values with exports collected in the body
resolvedResult <- RawValueInliner.valueListToModel(result)

View File

@ -120,8 +120,13 @@ object TagInliner extends Logging {
_ <- Exports[S].resolved(assignTo, cd._1)
} yield Some(SeqModel) -> cd._2
case ClosureTag(arrow) =>
Arrows[S].resolved(arrow).map(_ => None -> None)
case ClosureTag(arrow, detach) =>
if (detach) Arrows[S].resolved(arrow, None).map(_ => None -> None)
else
for {
t <- Mangler[S].findAndForbidName(arrow.name)
_ <- Arrows[S].resolved(arrow, Some(t))
} yield Some(CaptureTopologyModel(t)) -> None
case NextTag(item) =>
pure(NextModel(item))

View File

@ -25,11 +25,11 @@ trait Arrows[S] extends Scoped[S] {
* @param e
* contextual Exports that an arrow captures
*/
final def resolved(arrow: FuncRaw)(implicit e: Exports[S]): State[S, Unit] =
final def resolved(arrow: FuncRaw, topology: Option[String])(implicit e: Exports[S]): State[S, Unit] =
for {
exps <- e.exports
arrs <- arrows
funcArrow = FuncArrow.fromRaw(arrow, arrs, exps)
funcArrow = FuncArrow.fromRaw(arrow, arrs, exps, topology)
_ <- save(arrow.name, funcArrow)
} yield ()

View File

@ -22,7 +22,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
ArrowType(ProductType(Nil), ProductType(Nil)),
Nil,
Map.empty,
Map.empty
Map.empty,
None
),
CallModel(Nil, Nil)
)
@ -73,7 +74,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val model: OpModel.Tree = ArrowInliner
@ -97,7 +99,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map("cb" -> cbArrow),
Map.empty
Map.empty,
None
),
CallModel(cbVal :: Nil, Nil)
)
@ -162,7 +165,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val model: OpModel.Tree = ArrowInliner
@ -186,7 +190,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map("cb" -> cbArrow),
Map.empty
Map.empty,
None
),
CallModel(cbVal :: Nil, Nil)
)
@ -241,7 +246,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val model: OpModel.Tree = ArrowInliner
@ -260,7 +266,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
ArrowType(ProductType(Nil), ProductType(returnType :: Nil)),
Nil,
Map(innerName -> inner),
Map.empty
Map.empty,
None
),
CallModel(Nil, Nil)
)
@ -340,7 +347,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map.empty,
Map.empty
Map.empty,
None
)
// wrapper that export object and call inner function
@ -358,7 +366,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map(inner.funcName -> inner),
Map.empty
Map.empty,
None
),
CallModel(Nil, Nil)
)
@ -426,7 +435,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map.empty,
Map.empty
Map.empty,
None
)
// wrapper that export object and call inner function
@ -445,7 +455,8 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers {
),
Nil,
Map(inner.funcName -> inner),
Map.empty
Map.empty,
None
),
CallModel(Nil, Nil)
)

View File

@ -140,11 +140,12 @@ case class AssignmentTag(
}
case class ClosureTag(
func: FuncRaw
func: FuncRaw,
detach: Boolean
) extends NoExecTag {
override def mapValues(f: ValueRaw => ValueRaw): RawTag =
ClosureTag(
copy(
func.copy(arrow =
func.arrow.copy(
ret = func.arrow.ret.map(_.map(f)),

View File

@ -52,8 +52,7 @@ object MakeRes {
currentPeerId: Option[ValueModel],
i: Int
): PartialFunction[OpModel, ResolvedOp.Tree] = {
case SeqModel => SeqRes.leaf
case _: OnModel => SeqRes.leaf
case SeqModel | _: OnModel | _: ApplyTopologyModel => SeqRes.leaf
case MatchMismatchModel(a, b, s) =>
MatchMismatchRes(a, b, s).leaf
case ForModel(item, iter) if !isNillLiteral(iter) => FoldRes(item, iter).leaf

View File

@ -114,7 +114,8 @@ object AquaContext extends Logging {
arrowType,
ret.map(_.toRaw),
Map.empty,
Map.empty
Map.empty,
None
)
}
)
@ -188,7 +189,7 @@ object AquaContext extends Logging {
val (pctx, pcache) = fromRawContext(partContext, ctxCache)
logger.trace("Got " + func.name + " from raw")
val fr = FuncArrow.fromRaw(func, pctx.allFuncs, pctx.allValues)
val fr = FuncArrow.fromRaw(func, pctx.allFuncs, pctx.allValues, None)
logger.trace("Captured recursively for " + func.name)
val add = blank.copy(funcs = Map(func.name -> fr))

View File

@ -12,7 +12,8 @@ case class FuncArrow(
arrowType: ArrowType,
ret: List[ValueRaw],
capturedArrows: Map[String, FuncArrow],
capturedValues: Map[String, ValueModel]
capturedValues: Map[String, ValueModel],
capturedTopology: Option[String]
) {
lazy val args: List[(String, Type)] = arrowType.domain.toLabelledList()
@ -25,7 +26,8 @@ object FuncArrow {
def fromRaw(
raw: FuncRaw,
arrows: Map[String, FuncArrow],
constants: Map[String, ValueModel]
constants: Map[String, ValueModel],
topology: Option[String] = None
): FuncArrow =
FuncArrow(
raw.name,
@ -33,6 +35,7 @@ object FuncArrow {
raw.arrow.`type`,
raw.arrow.ret,
arrows,
constants
constants,
topology
)
}

View File

@ -150,5 +150,8 @@ case class JoinModel(operands: NonEmptyList[ValueModel]) extends ForceExecModel
operands.toList.flatMap(_.usesVarNames).toSet
}
case class CaptureTopologyModel(name: String) extends NoExecModel
case class ApplyTopologyModel(name: String) extends SeqGroupModel
case object EmptyModel extends NoExecModel
case object NullModel extends NoExecModel

View File

@ -4,8 +4,10 @@ import aqua.model.transform.pre.InitPeerCallable
import aqua.model.{
CallModel,
CallServiceModel,
ForceExecModel,
LiteralModel,
MatchMismatchModel,
NoExecModel,
OnModel,
OpModel,
SeqModel,
@ -26,12 +28,21 @@ case class ErrorsCatcher(
callable: InitPeerCallable
) {
private def hasExec(children: Chain[OpModel.Tree]): Boolean =
children.exists {
case Cofree(head: ForceExecModel, _) =>
true
case Cofree(_, tail) =>
hasExec(tail.value)
}
def transform(op: OpModel.Tree): OpModel.Tree =
if (enabled) {
var i = 0
Cofree
.cata[Chain, OpModel, OpModel.Tree](op) {
case (ot @ (OnModel(_, _) | MatchMismatchModel(_, _, _)), children) =>
case (ot @ (OnModel(_, _) | MatchMismatchModel(_, _, _)), children)
if hasExec(children) =>
i = i + 1
Eval now ot.wrap(
XorModel.wrap(

View File

@ -48,7 +48,8 @@ case class FuncPreTransformer(
arrowType,
ret.map(_.toRaw),
Map.empty,
Map.empty
Map.empty,
None
)
}
@ -92,7 +93,8 @@ case class FuncPreTransformer(
argName -> arrowToCallback(argName, arrowType)
}
.toMap,
Map.empty
Map.empty,
None
)
}
}

View File

@ -43,8 +43,11 @@ case class OpModelTreeCursor(
lazy val children: LazyList[OpModelTreeCursor] =
LazyList.unfold(toFirstChild)(_.map(c => c -> c.toNextSibling))
lazy val subtree: LazyList[OpModelTreeCursor] =
children.flatMap(c => c #:: c.subtree).prepended(this)
def findInside(f: OpModelTreeCursor => Boolean): LazyList[OpModelTreeCursor] =
children.flatMap(_.findInside(f)).prependedAll(Option.when(f(this))(this))
subtree.filter(f)
lazy val topology: Topology = Topology.make(this)

View File

@ -34,13 +34,51 @@ case class Topology private (
begins: Topology.Begins,
ends: Topology.Ends,
after: Topology.After
) {
) extends Logging {
val pathOn: Eval[List[OnModel]] = Eval
.later(cursor.tagsPath.collect { case o: OnModel =>
o
})
.memoize
val parent: Option[Topology] = cursor.moveUp.map(_.topology)
val parents: LazyList[Topology] =
LazyList.unfold(parent)(p => p.map(pp => pp -> pp.parent))
// Map of all previously-seen Captured Topologies -- see CaptureTopologyModel, ApplyTopologyModel
val capturedTopologies: Eval[Map[String, Topology]] =
cursor.moveLeft
.fold(Eval.now(Map.empty[String, Topology]))(_.topology.capturedTopologies)
.flatMap(tops =>
cursor.current.head match {
case CaptureTopologyModel(name) =>
logger.trace(s"Capturing topology `$name`")
Eval.now(tops + (name -> this))
case x =>
logger.trace(s"Skip $x")
cursor.toLastChild
.map(_.topology.capturedTopologies.map(_ ++ tops))
.getOrElse(Eval.now(tops))
}
)
.memoize
// Current topology location stack of OnModel's collected from parents branch
// ApplyTopologyModel shifts topology to pathOn where this topology was Captured
val pathOn: Eval[List[OnModel]] =
Eval
.later(cursor.current.head match {
case o: OnModel =>
parent.fold[Eval[List[OnModel]]](Eval.now(o :: Nil))(_.pathOn.map(o :: _))
case ApplyTopologyModel(name) =>
capturedTopologies.flatMap(
_.get(name).fold(
Eval.later {
logger.error(s"Captured topology `$name` not found")
List.empty[OnModel]
}
)(_.pathOn)
)
case _ => parent.fold[Eval[List[OnModel]]](Eval.now(Nil))(_.pathOn)
})
.flatMap(identity)
.memoize
lazy val firstExecutesOn: Eval[Option[List[OnModel]]] =
(cursor.op match {
@ -100,11 +138,6 @@ case class Topology private (
def findInside(f: Topology => Boolean): LazyList[Topology] =
children.flatMap(_.findInside(f)).prependedAll(Option.when(f(this))(this))
val parent: Option[Topology] = cursor.moveUp.map(_.topology)
val parents: LazyList[Topology] =
LazyList.unfold(parent)(p => p.map(pp => pp -> pp.parent))
lazy val forModel: Option[ForModel] = Option(cursor.op).collect { case ft: ForModel =>
ft
}

View File

@ -36,7 +36,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
stringArrow,
ret :: Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val bc = TransformConfig()
@ -85,7 +86,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
stringArrow,
ret :: Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val bc = TransformConfig(wrapWithXor = false)
@ -130,7 +132,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
stringArrow,
VarRaw("v", ScalarType.string) :: Nil,
Map.empty,
Map.empty
Map.empty,
None
)
val f2: FuncArrow =
@ -140,7 +143,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
stringArrow,
VarRaw("v", ScalarType.string) :: Nil,
Map("callable" -> f1),
Map.empty
Map.empty,
None
)
val bc = TransformConfig(wrapWithXor = false)

View File

@ -13,17 +13,18 @@ import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
case class ClosureExpr[F[_]](
name: Name[F]
name: Name[F],
detach: Option[F[Unit]]
) extends Expr[F](ClosureExpr, name) {
override def mapK[K[_]: Comonad](fk: F ~> K): ClosureExpr[K] =
copy(name.mapK(fk))
copy(name.mapK(fk), detach.map(fk.apply))
}
object ClosureExpr extends Expr.Prefix() {
override def continueWith: List[Expr.Lexem] = Expr.defer(ArrowExpr) :: Nil
override val p: Parser[ClosureExpr[Span.S]] =
(Name.p <* ` ` <* `=`).map(ClosureExpr(_))
((Name.p <* ` ` <* `=`) ~ (` ` *> `func`.lift).backtrack.?).map(ClosureExpr(_, _))
}

View File

@ -14,18 +14,24 @@ import cats.data.NonEmptyList
import scala.collection.mutable
class ClosureExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
val parser = Parser.spanParser
"closure header" should "parse" in {
closureExpr("someName =") should be(
ClosureExpr[Id](toName("someName"))
ClosureExpr[Id](toName("someName"), None)
)
closureExpr("someName = func") should be(
ClosureExpr[Id](toName("someName"), Some(()))
)
}
"closure" should "parse" in {
val script = """func f() -> string:
val script =
"""func f() -> string:
| closure = (s: string) -> string:
| LocalSrv.inside()
| p2Id <- Peer.identify()
@ -43,12 +49,22 @@ class ClosureExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
qTree.d() shouldBe RootExpr(Token.lift[Id, Unit](()))
qTree.d() shouldBe FuncExpr("f")
qTree.d() shouldBe ArrowExpr(toArrowType(Nil, Some(scToBt(string))))
qTree.d() shouldBe ClosureExpr("closure")
qTree.d() shouldBe ClosureExpr("closure", None)
qTree.d() shouldBe ArrowExpr(toNamedArrow(("s", scToBt(string)) :: Nil, scToBt(string) :: Nil))
qTree.d() shouldBe CallArrowExpr(Nil, Some(Ability[Id]("LocalSrv")), toName("inside"), Nil)
qTree.d() shouldBe CallArrowExpr(toName("p2Id") :: Nil, Some(Ability[Id]("Peer")), toName("identify"), Nil)
qTree.d() shouldBe CallArrowExpr(
toName("p2Id") :: Nil,
Some(Ability[Id]("Peer")),
toName("identify"),
Nil
)
qTree.d() shouldBe ReturnExpr(NonEmptyList(VarToken[Id](toName("p2Id")), Nil))
qTree.d() shouldBe CallArrowExpr(toName("v") :: Nil, None, toName("closure"), toStr("input") :: Nil)
qTree.d() shouldBe CallArrowExpr(
toName("v") :: Nil,
None,
toName("closure"),
toStr("input") :: Nil
)
qTree.d() shouldBe ReturnExpr(NonEmptyList(VarToken[Id](toName("v")), Nil))
}
}

View File

@ -21,11 +21,12 @@ class ClosureSem[S[_]](val expr: ClosureExpr[S]) extends AnyVal {
): Prog[Alg, Raw] =
Prog.after {
case arrow: ArrowRaw =>
// TODO: if detached, clear all locally-defined abilities
N.defineArrow(
expr.name,
arrow.`type`,
isRoot = false
) as ClosureTag(FuncRaw(expr.name.value, arrow)).funcOpLeaf
) as ClosureTag(FuncRaw(expr.name.value, arrow), expr.detach.isDefined).funcOpLeaf
case m =>
Raw.error("Closure must continue with an arrow definition").pure[Alg]

View File

@ -34,7 +34,7 @@ class ClosureSemSpec extends AnyFlatSpec with Matchers {
val program: Prog[State[CompilerState[cats.Id], *], Raw] = {
import CompilerState.*
val expr = ClosureExpr(Name[Id]("closure"))
val expr = ClosureExpr(Name[Id]("closure"), None)
val sem = new ClosureSem[Id](expr)
sem.program[State[CompilerState[Id], *]]