diff --git a/aqua-src/closure.aqua b/aqua-src/closure.aqua index 0443ff5e..7b0e78b2 100644 --- a/aqua-src/closure.aqua +++ b/aqua-src/closure.aqua @@ -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, "" \ No newline at end of file +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 \ No newline at end of file diff --git a/aqua-src/hack.aqua b/aqua-src/hack.aqua index 3655788b..4a12ffa2 100644 --- a/aqua-src/hack.aqua +++ b/aqua-src/hack.aqua @@ -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, [], ?[], *[] diff --git a/build.sbt b/build.sbt index f44a3f30..9d3db06b 100644 --- a/build.sbt +++ b/build.sbt @@ -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( diff --git a/cli/.js/src/main/scala/aqua/run/Runner.scala b/cli/.js/src/main/scala/aqua/run/Runner.scala index 30c37feb..d12f13f2 100644 --- a/cli/.js/src/main/scala/aqua/run/Runner.scala +++ b/cli/.js/src/main/scala/aqua/run/Runner.scala @@ -160,7 +160,8 @@ class Runner( ArrowType(NilType, returnCodomain), ret, Map(func.name -> funcCallable), - Map.empty + Map.empty, + None ) } } diff --git a/cli/.js/src/main/scala/aqua/script/ScriptOpts.scala b/cli/.js/src/main/scala/aqua/script/ScriptOpts.scala index a157efc5..d36e7c28 100644 --- a/cli/.js/src/main/scala/aqua/script/ScriptOpts.scala +++ b/cli/.js/src/main/scala/aqua/script/ScriptOpts.scala @@ -141,7 +141,8 @@ object ScriptOpts extends Logging { ArrowType(NilType, NilType), Nil, Map(funcName -> callable), - Map.empty + Map.empty, + None ), tConfig ) diff --git a/cli/.jvm/src/main/scala/aqua/Test.scala b/cli/.jvm/src/main/scala/aqua/Test.scala index faed71c9..34b70d82 100644 --- a/cli/.jvm/src/main/scala/aqua/Test.scala +++ b/cli/.jvm/src/main/scala/aqua/Test.scala @@ -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) => diff --git a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala index e7880113..41af45fe 100644 --- a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala @@ -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) diff --git a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala index 91cf30f5..2cf5fa11 100644 --- a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala @@ -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)) diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala index 9e3ab21a..e6640343 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala @@ -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 () diff --git a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala index df88cc61..2477b8d9 100644 --- a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala @@ -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) ) diff --git a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala index a2ed9df9..070cef20 100644 --- a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala +++ b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala @@ -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)), diff --git a/model/res/src/main/scala/aqua/res/MakeRes.scala b/model/res/src/main/scala/aqua/res/MakeRes.scala index f2e87419..a994202e 100644 --- a/model/res/src/main/scala/aqua/res/MakeRes.scala +++ b/model/res/src/main/scala/aqua/res/MakeRes.scala @@ -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 diff --git a/model/src/main/scala/aqua/model/AquaContext.scala b/model/src/main/scala/aqua/model/AquaContext.scala index f92d3bd3..d4a9c6cc 100644 --- a/model/src/main/scala/aqua/model/AquaContext.scala +++ b/model/src/main/scala/aqua/model/AquaContext.scala @@ -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)) diff --git a/model/src/main/scala/aqua/model/FuncArrow.scala b/model/src/main/scala/aqua/model/FuncArrow.scala index 4c71b448..df19f1d2 100644 --- a/model/src/main/scala/aqua/model/FuncArrow.scala +++ b/model/src/main/scala/aqua/model/FuncArrow.scala @@ -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 ) } diff --git a/model/src/main/scala/aqua/model/OpModel.scala b/model/src/main/scala/aqua/model/OpModel.scala index 8af27023..827740f4 100644 --- a/model/src/main/scala/aqua/model/OpModel.scala +++ b/model/src/main/scala/aqua/model/OpModel.scala @@ -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 diff --git a/model/transform/src/main/scala/aqua/model/transform/funcop/ErrorsCatcher.scala b/model/transform/src/main/scala/aqua/model/transform/funcop/ErrorsCatcher.scala index f476aae4..46b7e01c 100644 --- a/model/transform/src/main/scala/aqua/model/transform/funcop/ErrorsCatcher.scala +++ b/model/transform/src/main/scala/aqua/model/transform/funcop/ErrorsCatcher.scala @@ -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( diff --git a/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala b/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala index fd425e88..9a10b863 100644 --- a/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala +++ b/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala @@ -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 ) } } diff --git a/model/transform/src/main/scala/aqua/model/transform/topology/OpModelTreeCursor.scala b/model/transform/src/main/scala/aqua/model/transform/topology/OpModelTreeCursor.scala index b8eea69e..9f7b230e 100644 --- a/model/transform/src/main/scala/aqua/model/transform/topology/OpModelTreeCursor.scala +++ b/model/transform/src/main/scala/aqua/model/transform/topology/OpModelTreeCursor.scala @@ -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) diff --git a/model/transform/src/main/scala/aqua/model/transform/topology/Topology.scala b/model/transform/src/main/scala/aqua/model/transform/topology/Topology.scala index f5d53c35..02b331d2 100644 --- a/model/transform/src/main/scala/aqua/model/transform/topology/Topology.scala +++ b/model/transform/src/main/scala/aqua/model/transform/topology/Topology.scala @@ -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 } diff --git a/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala b/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala index 84543746..e82178da 100644 --- a/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala +++ b/model/transform/src/test/scala/aqua/model/transform/TransformSpec.scala @@ -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) diff --git a/parser/src/main/scala/aqua/parser/expr/func/ClosureExpr.scala b/parser/src/main/scala/aqua/parser/expr/func/ClosureExpr.scala index 77c1a60c..1515371f 100644 --- a/parser/src/main/scala/aqua/parser/expr/func/ClosureExpr.scala +++ b/parser/src/main/scala/aqua/parser/expr/func/ClosureExpr.scala @@ -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(_, _)) } diff --git a/parser/src/test/scala/aqua/parser/ClosureExprSpec.scala b/parser/src/test/scala/aqua/parser/ClosureExprSpec.scala index 4c6bbf07..fe88a39f 100644 --- a/parser/src/test/scala/aqua/parser/ClosureExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/ClosureExprSpec.scala @@ -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)) } } diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala index bd92e7c1..f9847ba6 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala @@ -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] diff --git a/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala b/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala index 036dd1fc..4473be3f 100644 --- a/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala @@ -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], *]]