diff --git a/aqua-src/demo.aqua b/aqua-src/demo.aqua index f70ca00d..88c744a7 100644 --- a/aqua-src/demo.aqua +++ b/aqua-src/demo.aqua @@ -1,22 +1,5 @@ -import "builtin.aqua" - -service OpH("op"): - puk(s: string) -> string - pek(s: string, -- trgtr - c: string) -> string - -func a( -- ferkjn - b: string, -- fr - c: string, -- asdf - g: string - ) -> string: -- rgtr - - try: - f = "world" - OpH "planet" - OpH.pek("TRY THIS", -- gtrg - c) - catch err: - OpH.puk(err.msg) - <- f +import "print.aqua" +func iterateAndPrint(strings: []string): + for s <- strings: + print(s) \ No newline at end of file diff --git a/aqua-src/print.aqua b/aqua-src/print.aqua new file mode 100644 index 00000000..5ffdcc24 --- /dev/null +++ b/aqua-src/print.aqua @@ -0,0 +1,5 @@ +service Println("println-service-id"): + print: string -> () + +func print(str: string): + Println.print(str) \ No newline at end of file diff --git a/aqua/builtin.aqua b/aqua/builtin.aqua deleted file mode 100644 index 21cc653c..00000000 --- a/aqua/builtin.aqua +++ /dev/null @@ -1,150 +0,0 @@ -alias Field : []string -alias Argument : []string -alias Bytes : []u8 -alias PeerId : string - -data Service: - id: string - blueprint_id: string - owner_id: string - -data FunctionSignature: - arguments: []Argument - name: string - output_types: []string - -data RecordType: - fields: []Field - id: u64 - name: string - -data Interface: - function_signatures: []FunctionSignature - record_types: []RecordType - -data ServiceInfo: - blueprint_id: string - service_id: string - interface: Interface - -data Info: - external_addresses: []string - -data ModuleConfig: - name: string - -data Module: - name: string - hash: string - config: ModuleConfig - -data AddBlueprint: - name: string - dependencies: []string - -data Blueprint: - id: string - name: string - dependencies: []string - -data ScriptInfo: - id: string - src: string - failures: u32 - interval: string - owner: string - -data Contact: - peer_id: string - addresses: []string - -service Op("op"): - identity: -> () - -service Peer("peer"): - -- Checks if there is a direct connection to the peer identified by a given PeerId - -- Argument: PeerId – id of the peer to check if there's a connection with - -- Returns: bool - true if connected to the peer, false otherwise - is_connected: PeerId -> bool - - -- Initiates a connection to the specified peer - -- Arguments: - -- PeerId – id of the target peer - -- [Multiaddr] – an array of target peer's addresses - -- Returns: bool - true if connection was successful - connect: PeerId, []string -> bool - -- Resolves the contact of a peer via Kademlia - -- Argument: PeerId – id of the target peer - -- Returns: Contact - true if connection was successful - get_contact: PeerId -> Contact - - -- Get information about the peer - identify: -> Info - - -- Get Unix timestamp in milliseconds - timestamp_ms: -> u64 - - -- Get Unix timestamp in seconds - timestamp_sec: -> u64 - -service Kademlia("kad"): - -- Instructs node to return the locally-known nodes - -- in the Kademlia neighborhood for a given key - neighborhood: PeerId -> []PeerId - -service Srv("srv"): - -- Used to create a service on a certain node - -- Arguments: - -- blueprint_id – ID of the blueprint that has been added to the node specified in the service call by the dist add_blueprint service. - -- Returns: service_id – the service ID of the created service. - create: string -> string - - -- Returns a list of services running on a peer - list: -> []Service - - -- Adds an alias on service, so, service could be called - -- not only by service_id but by alias as well. - -- Argument: - -- alias - settable service name - -- service_id – ID of the service whose interface you want to name. - add_alias: string, string -> () - - -- Retrieves the functional interface of a service running - -- on the node specified in the service call - -- Argument: service_id – ID of the service whose interface you want to retrieve. - get_interface: string -> ServiceInfo - -service Dist("dist"): - -- Used to add modules to the node specified in the service call - -- Arguments: - -- bytes – a base64 string containing the .wasm module to add. - -- config – module info - -- Returns: blake3 hash of the module - add_module: Bytes, ModuleConfig -> string - - -- Get a list of modules available on the node - list_modules: -> []Module - - -- Get the interface of a module - get_interface: string -> Interface - - -- Used to add a blueprint to the node specified in the service call - add_blueprint: AddBlueprint -> string - - -- Used to get the blueprints available on the node specified in the service call. - -- A blueprint is an object of the following structure - list_blueprints: -> []Blueprint - -service Script("script"): - -- Adds the given script to a node - add: string, string -> string - - -- Removes recurring script from a node. Only a creator of the script can delete it - remove: string -> bool - - -- Returns a list of existing scripts on the node. - -- Each object in the list is of the following structure - list: -> ScriptInfo - -func id(): - Op.identity() diff --git a/backend/air/src/main/scala/aqua/backend/air/AirGen.scala b/backend/air/src/main/scala/aqua/backend/air/AirGen.scala index c12a9024..ea155ad8 100644 --- a/backend/air/src/main/scala/aqua/backend/air/AirGen.scala +++ b/backend/air/src/main/scala/aqua/backend/air/AirGen.scala @@ -2,22 +2,19 @@ package aqua.backend.air import aqua.model._ import aqua.model.func.Call -import aqua.model.func.body._ +import aqua.model.func.resolved._ import aqua.types.StreamType import cats.Eval import cats.data.Chain import cats.free.Cofree -import wvlet.log.Logger +import wvlet.log.LogSupport sealed trait AirGen { def generate: Air } -object AirGen { - - private val logger = Logger.of[AirGen.type] - import logger._ +object AirGen extends LogSupport { def lambdaToString(ls: List[LambdaModel]): String = ls match { case Nil => "" @@ -46,22 +43,27 @@ object AirGen { case list => list.reduceLeft(SeqGen) } - private def folder(op: OpTag, ops: Chain[AirGen]): Eval[AirGen] = + private def folder(op: ResolvedOp, ops: Chain[AirGen]): Eval[AirGen] = op match { - case mt: MetaTag => - folder(mt.op, ops).map(ag => mt.comment.fold(ag)(CommentGen(_, ag))) - case SeqTag => +// 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) - case ParTag => + case ParRes => Eval later ops.toList.reduceLeftOption(ParGen).getOrElse(NullGen) - case XorTag => - Eval later ops.toList.reduceLeftOption(XorGen).getOrElse(NullGen) - case XorTag.LeftBiased => - Eval later XorGen(opsToSingle(ops), NullGen) + case XorRes => + Eval later (ops.toList match { + case o :: Nil => XorGen(o, NullGen) + case _ => + ops.toList.reduceLeftOption(XorGen).getOrElse { + warn("XorRes with no children converted to Null") + NullGen + } + }) - case NextTag(item) => + case NextRes(item) => Eval later NextGen(item) - case MatchMismatchTag(left, right, shouldMatch) => + case MatchMismatchRes(left, right, shouldMatch) => Eval later MatchMismatchGen( valueToData(left), valueToData(right), @@ -69,12 +71,12 @@ object AirGen { opsToSingle(ops) ) - case ForTag(item, iterable) => + case FoldRes(item, iterable) => Eval later ForGen(valueToData(iterable), item, opsToSingle(ops)) - case CallServiceTag(serviceId, funcName, Call(args, exportTo), peerId) => + case CallServiceRes(serviceId, funcName, Call(args, exportTo), peerId) => Eval.later( ServiceCallGen( - peerId.map(valueToData).getOrElse(DataView.InitPeerId), + valueToData(peerId), valueToData(serviceId), funcName, args.map(valueToData), @@ -85,35 +87,14 @@ object AirGen { ) ) - case CallArrowTag(funcName, _) => - // TODO: should be already resolved & removed from tree - error( - s"Unresolved arrow in AirGen: $funcName" - ) + case _: NoAir => Eval later NullGen - case OnTag(_, _) => - // TODO should be resolved - Eval later opsToSingle( - ops - ) - case _: NoAirTag => - // TODO: should be already resolved & removed from tree - Eval later NullGen - case XorParTag(opsx, opsy) => - // TODO should be resolved - error( - "XorParTag reached AirGen, most likely it's an error" - ) - Eval later opsToSingle( - Chain(apply(opsx.tree), apply(opsy.tree)) - ) - } - def apply(op: Cofree[Chain, OpTag]): AirGen = + def apply(op: Cofree[Chain, ResolvedOp]): AirGen = Cofree - .cata[Chain, OpTag, AirGen](op)(folder) + .cata[Chain, ResolvedOp, AirGen](op)(folder) .value } diff --git a/build.sbt b/build.sbt index a4959566..3488e14b 100644 --- a/build.sbt +++ b/build.sbt @@ -24,7 +24,7 @@ val airframeLog = "org.wvlet.airframe" %% "airframe-log" % airframeLogV name := "aqua-hll" val commons = Seq( - baseAquaVersion := "0.1.6", + baseAquaVersion := "0.1.7", version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), scalaVersion := dottyVersion, libraryDependencies ++= Seq( diff --git a/model/src/main/scala/aqua/model/AquaContext.scala b/model/src/main/scala/aqua/model/AquaContext.scala index 77486e5f..49db757d 100644 --- a/model/src/main/scala/aqua/model/AquaContext.scala +++ b/model/src/main/scala/aqua/model/AquaContext.scala @@ -1,6 +1,6 @@ package aqua.model -import aqua.model.func.body.{CallServiceTag, FuncOp} +import aqua.model.func.raw.{CallServiceTag, FuncOp} import aqua.model.func.{ArgsCall, FuncCallable, FuncModel} import aqua.types.{ProductType, Type} import cats.Monoid @@ -8,6 +8,7 @@ import cats.data.NonEmptyMap import cats.syntax.apply._ import cats.syntax.functor._ import cats.syntax.monoid._ +import wvlet.log.LogSupport import scala.collection.immutable.SortedMap @@ -64,7 +65,7 @@ case class AquaContext( .map(ProductType(name, _)) } -object AquaContext { +object AquaContext extends LogSupport { trait Implicits { implicit val aquaContextMonoid: Monoid[AquaContext] @@ -99,7 +100,7 @@ object AquaContext { FuncCallable( fnName, // TODO: capture ability resolution, get ID from the call context - FuncOp.leaf(CallServiceTag(serviceId, fnName, call, None)), + FuncOp.leaf(CallServiceTag(serviceId, fnName, call)), args, (ret.map(_.model), arrowType.res).mapN(_ -> _), Map.empty, diff --git a/model/src/main/scala/aqua/model/Model.scala b/model/src/main/scala/aqua/model/Model.scala index 38f48727..0880a9fc 100644 --- a/model/src/main/scala/aqua/model/Model.scala +++ b/model/src/main/scala/aqua/model/Model.scala @@ -1,6 +1,6 @@ package aqua.model -import aqua.model.func.body.FuncOp +import aqua.model.func.raw.FuncOp import cats.kernel.Semigroup trait Model diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index 35d0ef0b..1791ec56 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -18,10 +18,18 @@ object ValueModel { implicit object ValueModelEq extends Eq[ValueModel] { override def eqv(x: ValueModel, y: ValueModel): Boolean = x == y } + + def varName(vm: ValueModel): Option[String] = + vm match { + case VarModel(name, _, _) => Some(name) + case _ => None + } } case class LiteralModel(value: String, `type`: Type) extends ValueModel { override def lastType: Type = `type` + + override def toString: String = s"{$value: ${`type`}}" } object LiteralModel { diff --git a/model/src/main/scala/aqua/model/cursor/ChainCursor.scala b/model/src/main/scala/aqua/model/cursor/ChainCursor.scala new file mode 100644 index 00000000..e05e1b3a --- /dev/null +++ b/model/src/main/scala/aqua/model/cursor/ChainCursor.scala @@ -0,0 +1,51 @@ +package aqua.model.cursor + +import cats.data.{Chain, NonEmptyList} + +abstract class ChainCursor[C <: ChainCursor[C, T], T](make: NonEmptyList[ChainZipper[T]] => C) { + self => + + val tree: NonEmptyList[ChainZipper[T]] + + def parent: Option[T] = tree.tail.headOption.map(_.current) + + def current: T = tree.head.current + + def leftSiblings: Chain[T] = tree.head.prev + def rightSiblings: Chain[T] = tree.head.next + + def mapParent(f: T => T): C = + make( + NonEmptyList( + tree.head, + tree.tail match { + case h :: t => h.copy(current = f(h.current)) :: t + case t => t + } + ) + ) + + def path: NonEmptyList[T] = tree.map(_.current) + + def moveUp: Option[C] = NonEmptyList.fromList(tree.tail).map(make) + + def pathToRoot: LazyList[C] = LazyList.unfold(this)(_.moveUp.map(c => c -> c)) + + def moveDown(focusOn: ChainZipper[T]): C = make(focusOn :: tree) + + def moveLeft: Option[C] = + toPrevSibling orElse moveUp.flatMap(_.moveLeft) + + def moveRight: Option[C] = + toNextSibling orElse moveUp.flatMap(_.moveRight) + + def toNextSibling: Option[C] = tree.head.moveRight.map(p => make(tree.copy(p))) + + def toPrevSibling: Option[C] = tree.head.moveLeft.map(p => make(tree.copy(p))) + + def allToLeft: LazyList[C] = + LazyList.unfold(this)(_.moveLeft.map(c => c -> c)) + + def allToRight: LazyList[C] = + LazyList.unfold(this)(_.moveRight.map(c => c -> c)) +} diff --git a/model/src/main/scala/aqua/model/topology/ChainZipper.scala b/model/src/main/scala/aqua/model/cursor/ChainZipper.scala similarity index 51% rename from model/src/main/scala/aqua/model/topology/ChainZipper.scala rename to model/src/main/scala/aqua/model/cursor/ChainZipper.scala index 4e8129b2..f1e49393 100644 --- a/model/src/main/scala/aqua/model/topology/ChainZipper.scala +++ b/model/src/main/scala/aqua/model/cursor/ChainZipper.scala @@ -1,4 +1,4 @@ -package aqua.model.topology +package aqua.model.cursor import cats.data.Chain import cats.free.Cofree @@ -24,17 +24,36 @@ case class ChainZipper[T](prev: Chain[T], current: T, next: Chain[T]) { object ChainZipper { def one[T](el: T): ChainZipper[T] = ChainZipper(Chain.empty, el, Chain.empty) - def fromChain[T](chain: Chain[T], prev: Chain[T] = Chain.empty): Chain[ChainZipper[T]] = - chain.uncons.fold(Chain.empty[ChainZipper[T]]) { case (t, next) => - ChainZipper(prev, t, next) +: fromChain(next, prev :+ t) + def first[T](chain: Chain[T]): Option[ChainZipper[T]] = + chain.uncons.map { case (current, next) => + ChainZipper(Chain.empty, current, next) } - def fromChainMap[T](chain: Chain[T], prev: Chain[T] = Chain.empty)( + def last[T](chain: Chain[T]): Option[ChainZipper[T]] = + chain.initLast.map { case (prev, current) => + ChainZipper(prev, current, Chain.empty) + } + + def traverseChain[T](chain: Chain[T], prev: Chain[T] = Chain.empty): Chain[ChainZipper[T]] = + chain.uncons.fold(Chain.empty[ChainZipper[T]]) { case (t, next) => + ChainZipper(prev, t, next) +: traverseChain(next, prev :+ t) + } + + /** + * Walks through the given chain as a zipper, applying the given function to it + * + * @param chain Chain to traverse and map + * @param prev Accumulator: previous steps + * @param f Function that converts a zipper (meaning an element with its horizontal context) to element + * @tparam T Type + * @return Resulting chain + */ + def traverseChainMap[T](chain: Chain[T], prev: Chain[T] = Chain.empty)( f: ChainZipper[T] => Option[T] ): Chain[T] = chain.uncons.fold(Chain.empty[T]) { case (t, next) => - f(ChainZipper(prev, t, next)).fold(fromChainMap(next, prev)(f))(r => - r +: fromChainMap(next, prev :+ r)(f) + f(ChainZipper(prev, t, next)).fold(traverseChainMap(next, prev)(f))(r => + r +: traverseChainMap(next, prev :+ r)(f) ) } diff --git a/model/src/main/scala/aqua/model/func/Call.scala b/model/src/main/scala/aqua/model/func/Call.scala index 029f94dc..f715ce41 100644 --- a/model/src/main/scala/aqua/model/func/Call.scala +++ b/model/src/main/scala/aqua/model/func/Call.scala @@ -16,6 +16,9 @@ case class Call(args: List[ValueModel], exportTo: Option[Call.Export]) { def argVarNames: Set[String] = args.collect { case VarModel(name, _, _) => name }.toSet + + override def toString: String = + s"[${args.mkString(" ")}]${exportTo.map(_.model).map(" " + _).getOrElse("")}" } object Call { diff --git a/model/src/main/scala/aqua/model/func/FuncCallable.scala b/model/src/main/scala/aqua/model/func/FuncCallable.scala index d51ec280..37d00634 100644 --- a/model/src/main/scala/aqua/model/func/FuncCallable.scala +++ b/model/src/main/scala/aqua/model/func/FuncCallable.scala @@ -1,6 +1,6 @@ package aqua.model.func -import aqua.model.func.body.{AssignmentTag, CallArrowTag, CallServiceTag, FuncOp, OpTag} +import aqua.model.func.raw.{AssignmentTag, CallArrowTag, CallServiceTag, FuncOp, RawTag} import aqua.model.{Model, ValueModel, VarModel} import aqua.types.{ArrowType, Type} import cats.Eval @@ -86,7 +86,7 @@ case class FuncCallable( ( noNames, resolvedExports + (assignTo -> value.resolveWith(resolvedExports)) - ) -> Cofree[Chain, OpTag]( + ) -> Cofree[Chain, RawTag]( tag.mapValues(_.resolveWith(resolvedExports)), Eval.now(Chain.empty) ) @@ -119,7 +119,7 @@ case class FuncCallable( } // All the other tags are already resolved and need no substitution - acc -> Cofree[Chain, OpTag]( + acc -> Cofree[Chain, RawTag]( tag.mapValues(_.resolveWith(resolvedExports)), Eval.now(Chain.empty) ) diff --git a/model/src/main/scala/aqua/model/func/FuncModel.scala b/model/src/main/scala/aqua/model/func/FuncModel.scala index a8eb9f6d..f8719d5e 100644 --- a/model/src/main/scala/aqua/model/func/FuncModel.scala +++ b/model/src/main/scala/aqua/model/func/FuncModel.scala @@ -1,6 +1,6 @@ package aqua.model.func -import aqua.model.func.body.FuncOp +import aqua.model.func.raw.FuncOp import aqua.model.{Model, ValueModel} import aqua.types.Type @@ -15,6 +15,6 @@ case class FuncModel( arrows: Map[String, FuncCallable], constants: Map[String, ValueModel] ): FuncCallable = - FuncCallable(name, body, args, ret, arrows, constants) + FuncCallable(name, body.fixXorPar, args, ret, arrows, constants) } diff --git a/model/src/main/scala/aqua/model/func/body/FuncOp.scala b/model/src/main/scala/aqua/model/func/raw/FuncOp.scala similarity index 71% rename from model/src/main/scala/aqua/model/func/body/FuncOp.scala rename to model/src/main/scala/aqua/model/func/raw/FuncOp.scala index 4baf24a5..0fc53225 100644 --- a/model/src/main/scala/aqua/model/func/body/FuncOp.scala +++ b/model/src/main/scala/aqua/model/func/raw/FuncOp.scala @@ -1,4 +1,4 @@ -package aqua.model.func.body +package aqua.model.func.raw import aqua.model.func.Call import aqua.model.{Model, ValueModel, VarModel} @@ -9,8 +9,8 @@ import cats.kernel.Semigroup import cats.syntax.apply._ import cats.syntax.functor._ -case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { - def head: OpTag = tree.head +case class FuncOp(tree: Cofree[Chain, RawTag]) extends Model { + def head: RawTag = tree.head lazy val isRightAssoc: Boolean = head match { case XorTag | ParTag => true @@ -18,13 +18,13 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { case _ => false } - def cata[T](folder: (OpTag, Chain[T]) => Eval[T]): Eval[T] = + def cata[T](folder: (RawTag, Chain[T]) => Eval[T]): Eval[T] = Cofree.cata(tree)(folder) def definesVarNames: Eval[Set[String]] = cata[Set[String]] { case (CallArrowTag(_, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) - case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) => + case (CallServiceTag(_, _, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) case (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) @@ -33,25 +33,30 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { def exportsVarNames: Eval[Set[String]] = cata[Set[String]] { case (CallArrowTag(_, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) - case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) => + case (CallServiceTag(_, _, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) } + // TODO: as it is used for checking of intersection, make it a lazy traverse with fail-fast def usesVarNames: Eval[Set[String]] = cata[Set[String]] { case (CallArrowTag(_, call), acc) => Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _)) - case (CallServiceTag(_, _, call, _), acc) => + case (CallServiceTag(_, _, call), acc) => Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _)) + case (MatchMismatchTag(a, b, _), acc) => + Eval.later(acc.foldLeft(ValueModel.varName(a).toSet ++ ValueModel.varName(b))(_ ++ _)) + case (ForTag(_, VarModel(name, _, _)), acc) => + Eval.later(acc.foldLeft(Set(name))(_ ++ _)) case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) } def resolveValues(vals: Map[String, ValueModel]): FuncOp = - FuncOp(tree.map[OpTag](_.mapValues(_.resolveWith(vals)))) + FuncOp(tree.map[RawTag](_.mapValues(_.resolveWith(vals)))) def rename(vals: Map[String, String]): FuncOp = FuncOp( - tree.map[OpTag](op => + tree.map[RawTag](op => op.mapValues { case v: VarModel if vals.contains(v.name) => v.copy(name = vals(v.name)) case v => v @@ -68,13 +73,29 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { def :+:(prev: FuncOp): FuncOp = FuncOp.RightAssocSemi.combine(prev, this) + + // Function body must be fixed before function gets resolved + def fixXorPar: FuncOp = + FuncOp(cata[Cofree[Chain, RawTag]] { + case (XorParTag(left, right), _) => + Eval.now( + FuncOps + .par( + FuncOp.wrap(XorTag, left), + right + ) + .tree + ) + + case (head, tail) => Eval.now(Cofree(head, Eval.now(tail))) + }.value) } object FuncOp { - type Tree = Cofree[Chain, OpTag] + type Tree = Cofree[Chain, RawTag] def traverseA[A](cf: Tree, init: A)( - f: (A, OpTag) => (A, Tree) + f: (A, RawTag) => (A, Tree) ): Eval[(A, Tree)] = { val (headA, head) = f(init, cf.head) // TODO: it should be in standard library, with some other types @@ -95,7 +116,7 @@ object FuncOp { FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _))) case (XorTag.LeftBiased, XorTag) => wrap(SeqTag, FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _)))) - case (XorTag, ParTag) => FuncOp(Cofree[Chain, OpTag](XorParTag(x, y), Eval.now(Chain.empty))) + case (XorTag, ParTag) => FuncOp(Cofree[Chain, RawTag](XorParTag(x, y), Eval.now(Chain.empty))) case (_, ParTag | XorTag) => wrap(SeqTag, FuncOp(y.tree.copy(tail = y.tree.tail.map(_.prepend(x.tree))))) case (_, XorParTag(xor, par)) => @@ -116,10 +137,10 @@ object FuncOp { } } - def leaf(tag: OpTag): FuncOp = FuncOp(Cofree[Chain, OpTag](tag, Eval.now(Chain.empty))) + def leaf(tag: RawTag): FuncOp = FuncOp(Cofree[Chain, RawTag](tag, Eval.now(Chain.empty))) - def wrap(tag: OpTag, child: FuncOp): FuncOp = node(tag, Chain.one(child)) + def wrap(tag: RawTag, child: FuncOp): FuncOp = node(tag, Chain.one(child)) - def node(tag: OpTag, children: Chain[FuncOp]): FuncOp = - FuncOp(Cofree[Chain, OpTag](tag, Eval.later(children.map(_.tree)))) + def node(tag: RawTag, children: Chain[FuncOp]): FuncOp = + FuncOp(Cofree[Chain, RawTag](tag, Eval.later(children.map(_.tree)))) } diff --git a/model/src/main/scala/aqua/model/func/body/FuncOps.scala b/model/src/main/scala/aqua/model/func/raw/FuncOps.scala similarity index 77% rename from model/src/main/scala/aqua/model/func/body/FuncOps.scala rename to model/src/main/scala/aqua/model/func/raw/FuncOps.scala index 8018948b..2063eefb 100644 --- a/model/src/main/scala/aqua/model/func/body/FuncOps.scala +++ b/model/src/main/scala/aqua/model/func/raw/FuncOps.scala @@ -1,4 +1,4 @@ -package aqua.model.func.body +package aqua.model.func.raw import aqua.model.func.Call import aqua.model.{LiteralModel, ValueModel} @@ -7,12 +7,12 @@ import cats.free.Cofree object FuncOps { - def noop(peerId: ValueModel): FuncOp = - FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None), Some(peerId))) + def noop: FuncOp = + FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None))) def identity(what: ValueModel, to: Call.Export): FuncOp = FuncOp.leaf( - CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to)), None) + CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to))) ) def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp = @@ -51,6 +51,19 @@ object FuncOps { .map(FuncOp(_)) ) + def par(ops: FuncOp*): FuncOp = + if (ops.length == 1) ops.head + else + FuncOp.node( + ParTag, + Chain + .fromSeq(ops.flatMap { + case FuncOp(Cofree(ParTag, subOps)) => subOps.value.toList + case FuncOp(cof) => cof :: Nil + }) + .map(FuncOp(_)) + ) + def xor(left: FuncOp, right: FuncOp): FuncOp = FuncOp.node(XorTag, Chain(left, right)) @@ -62,16 +75,4 @@ object FuncOps { def next(item: String): FuncOp = FuncOp.leaf(NextTag(item)) - - def meta(op: FuncOp, skipTopology: Boolean = false, comment: String = null): FuncOp = - FuncOp( - op.tree.copy( - MetaTag( - skipTopology = skipTopology, - comment = Option(comment), - op.head - ), - op.tree.tail - ) - ) } diff --git a/model/src/main/scala/aqua/model/func/raw/PathFinder.scala b/model/src/main/scala/aqua/model/func/raw/PathFinder.scala new file mode 100644 index 00000000..acaec064 --- /dev/null +++ b/model/src/main/scala/aqua/model/func/raw/PathFinder.scala @@ -0,0 +1,98 @@ +package aqua.model.func.raw + +import aqua.model.ValueModel +import cats.data.Chain +import cats.data.Chain.{:==, ==:, nil} +import wvlet.log.LogSupport + +import scala.annotation.tailrec + +object PathFinder extends LogSupport { + + def find(from: RawCursor, to: RawCursor, isExit: Boolean = false): Chain[ValueModel] = { + + val fromOn = Chain.fromSeq(from.pathOn).reverse + val toOn = Chain.fromSeq(to.pathOn).reverse + + val wasHandled = + !isExit && + to.leftSiblings.isEmpty && + to.moveUp.exists(_.pathOn == to.pathOn) && + !to.parentTag.contains(ParTag) + + if (wasHandled) { + debug("Was handled") + debug(" :: " + from) + debug(" -> " + to) + Chain.empty + } else { + debug("Find path") + debug(" :: " + from) + debug(" -> " + to) + findPath( + fromOn, + toOn, + Chain.fromOption(from.currentPeerId), + Chain.fromOption(to.currentPeerId) + ) + } + } + + def optimizePath( + peerIds: Chain[ValueModel], + prefix: Chain[ValueModel], + suffix: Chain[ValueModel] + ): Chain[ValueModel] = { + val optimized = peerIds + .foldLeft(Chain.empty[ValueModel]) { + case (acc, p) if acc.lastOption.contains(p) => acc + case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p + case (acc, p) => acc :+ p + } + val noPrefix = skipPrefix(optimized, prefix, optimized) + skipSuffix(noPrefix, suffix, noPrefix) + } + + def findPath( + fromOn: Chain[OnTag], + toOn: Chain[OnTag], + fromPeer: Chain[ValueModel], + toPeer: Chain[ValueModel] + ): Chain[ValueModel] = { + val (from, to) = skipCommonPrefix(fromOn, toOn) + val fromFix = + if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from + val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to + val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via) + val optimized = optimizePath(fromPeer ++ fromTo ++ toPeer, fromPeer, toPeer) + + trace("FIND PATH " + fromFix) + trace(" -> " + toFix) + trace(s"$fromPeer $toPeer") + trace(" Optimized: " + optimized) + optimized + } + + @tailrec + def skipPrefix[T](chain: Chain[T], prefix: Chain[T], init: Chain[T]): Chain[T] = + (chain, prefix) match { + case (c ==: ctail, p ==: ptail) if c == p => skipPrefix(ctail, ptail, init) + case (_, `nil`) => chain + case (_, _) => init + } + + @tailrec + def skipCommonPrefix[T](chain1: Chain[T], chain2: Chain[T]): (Chain[T], Chain[T]) = + (chain1, chain2) match { + case (c ==: ctail, p ==: ptail) if c == p => skipCommonPrefix(ctail, ptail) + case _ => chain1 -> chain2 + } + + @tailrec + def skipSuffix[T](chain: Chain[T], suffix: Chain[T], init: Chain[T]): Chain[T] = + (chain, suffix) match { + case (cinit :== c, pinit :== p) if c == p => skipSuffix(cinit, pinit, init) + case (_, `nil`) => chain + case (_, _) => init + } +} diff --git a/model/src/main/scala/aqua/model/func/raw/RawCursor.scala b/model/src/main/scala/aqua/model/func/raw/RawCursor.scala new file mode 100644 index 00000000..25813a4e --- /dev/null +++ b/model/src/main/scala/aqua/model/func/raw/RawCursor.scala @@ -0,0 +1,155 @@ +package aqua.model.func.raw + +import aqua.model.ValueModel +import aqua.model.cursor.{ChainCursor, ChainZipper} +import cats.Eval +import cats.data.{Chain, NonEmptyList, OptionT} +import cats.free.Cofree +import cats.syntax.traverse._ +import wvlet.log.LogSupport + +// Can be heavily optimized by caching parent cursors, not just list of zippers +case class RawCursor(tree: NonEmptyList[ChainZipper[FuncOp.Tree]]) + extends ChainCursor[RawCursor, FuncOp.Tree](RawCursor) with LogSupport { + def tag: RawTag = current.head + def parentTag: Option[RawTag] = parent.map(_.head) + + def hasChildren: Boolean = + current.tailForced.nonEmpty + + lazy val toFirstChild: Option[RawCursor] = + ChainZipper.first(current.tail.value).map(moveDown) + + lazy val toLastChild: Option[RawCursor] = + ChainZipper.last(current.tail.value).map(moveDown) + + lazy val tagsPath: NonEmptyList[RawTag] = path.map(_.head) + + lazy val pathOn: List[OnTag] = tagsPath.collect { case o: OnTag => + o + } + + lazy val rootOn: Option[RawCursor] = moveUp + .flatMap(_.rootOn) + .orElse(tag match { + case _: OnTag => + Some(this) + case _ => None + }) + + lazy val currentPeerId: Option[ValueModel] = + pathOn.headOption.map(_.peerId) + + // Cursor to the last sequentially executed operation, if any + lazy val lastExecuted: Option[RawCursor] = tag match { + case XorTag => toFirstChild.flatMap(_.lastExecuted) + case _: SeqGroupTag => toLastChild.flatMap(_.lastExecuted) + case ParTag => None + case _: NoExecTag => None + case _ => Some(this) + } + + lazy val firstExecuted: Option[RawCursor] = tag match { + case _: SeqGroupTag => toLastChild.flatMap(_.lastExecuted) + case ParTag => None + case _: NoExecTag => None + case _ => Some(this) + } + + /** + * Sequentially previous cursor + * @return + */ + lazy val seqPrev: Option[RawCursor] = + parentTag.flatMap { + case p: SeqGroupTag if leftSiblings.nonEmpty => + toPrevSibling.flatMap(c => c.lastExecuted orElse c.seqPrev) + case _ => + moveUp.flatMap(_.seqPrev) + } + + lazy val seqNext: Option[RawCursor] = + parentTag.flatMap { + case _: SeqGroupTag if rightSiblings.nonEmpty => + toNextSibling.flatMap(c => c.firstExecuted orElse c.seqNext) + case _ => moveUp.flatMap(_.seqNext) + } + + // TODO write a test + def checkNamesUsedLater(names: Set[String]): Boolean = + allToRight + .map(_.current) + .map(FuncOp(_)) + .exists(_.usesVarNames.value.intersect(names).nonEmpty) + + lazy val pathFromPrev: Chain[ValueModel] = pathFromPrevD() + + def pathFromPrevD(forExit: Boolean = false): Chain[ValueModel] = + parentTag.fold(Chain.empty[ValueModel]) { + case _: GroupTag => + seqPrev + .orElse(rootOn) + .fold(Chain.empty[ValueModel])(PathFinder.find(_, this, isExit = forExit)) + case _ => + Chain.empty + } + + lazy val pathToNext: Chain[ValueModel] = parentTag.fold(Chain.empty[ValueModel]) { + case ParTag => + val exports = FuncOp(current).exportsVarNames.value + if (exports.nonEmpty && checkNamesUsedLater(exports)) + seqNext.fold(Chain.empty[ValueModel])(nxt => + PathFinder.find(this, nxt) ++ + // we need to "wake" the target peer to enable join behaviour + Chain.fromOption(nxt.currentPeerId) + ) + else Chain.empty + case XorTag if leftSiblings.nonEmpty => + lastExecuted + .flatMap(le => + seqNext + .map(nxt => PathFinder.find(le, nxt, isExit = true) -> nxt) + .flatMap { + case (path, nxt) if path.isEmpty && currentPeerId == nxt.currentPeerId => + nxt.pathFromPrevD(true).reverse.initLast.map(_._1) + case (path, nxt) => + path.initLast.map { + case (init, last) + if nxt.pathFromPrevD(forExit = true).headOption.contains(last) => + init + case (init, last) => init :+ last + } + } + ) + .getOrElse(Chain.empty) + case _ => + Chain.empty + } + + def cata[A](wrap: ChainZipper[Cofree[Chain, A]] => Chain[Cofree[Chain, A]])( + f: RawCursor => OptionT[Eval, ChainZipper[Cofree[Chain, A]]] + ): Eval[Chain[Cofree[Chain, A]]] = + f(this).map { case ChainZipper(prev, curr, next) => + val inner = curr.copy(tail = + Eval + .later( + Chain.fromSeq( + toFirstChild + .map(fc => + LazyList + .unfold(fc) { _.toNextSibling.map(c => c -> c) } + .prepended(fc) + ) + .getOrElse(LazyList.empty) + ) + ) + // TODO: this can be parallelized + .flatMap(_.traverse(_.cata(wrap)(f))) + .map(_.flatMap(identity)) + .flatMap(addition => curr.tail.map(_ ++ addition)) + ) + wrap(ChainZipper(prev, inner, next)) + }.getOrElse(Chain.empty).memoize + + override def toString: String = s"$tag /: ${moveUp.getOrElse("(|)")}" +} diff --git a/model/src/main/scala/aqua/model/func/body/OpTag.scala b/model/src/main/scala/aqua/model/func/raw/RawTag.scala similarity index 65% rename from model/src/main/scala/aqua/model/func/body/OpTag.scala rename to model/src/main/scala/aqua/model/func/raw/RawTag.scala index 9dc54c66..4f4d973b 100644 --- a/model/src/main/scala/aqua/model/func/body/OpTag.scala +++ b/model/src/main/scala/aqua/model/func/raw/RawTag.scala @@ -1,12 +1,12 @@ -package aqua.model.func.body +package aqua.model.func.raw import aqua.model.ValueModel import aqua.model.func.Call import cats.data.Chain -sealed trait OpTag { +sealed trait RawTag { - def mapValues(f: ValueModel => ValueModel): OpTag = this match { + def mapValues(f: ValueModel => ValueModel): RawTag = this match { case OnTag(peerId, via) => OnTag(f(peerId), via.map(f)) case MatchMismatchTag(left, right, shouldMatch) => MatchMismatchTag(f(left), f(right), shouldMatch) @@ -16,12 +16,11 @@ sealed trait OpTag { funcName, call.mapValues(f) ) - case CallServiceTag(serviceId, funcName, call, pid) => + case CallServiceTag(serviceId, funcName, call) => CallServiceTag( f(serviceId), funcName, - call.mapValues(f), - pid.map(f) + call.mapValues(f) ) case AssignmentTag(value, assignTo) => AssignmentTag(f(value), assignTo) @@ -32,49 +31,49 @@ sealed trait OpTag { } -sealed trait NoAirTag extends OpTag +sealed trait NoExecTag extends RawTag -sealed trait GroupTag extends OpTag +sealed trait GroupTag extends RawTag sealed trait SeqGroupTag extends GroupTag case object SeqTag extends SeqGroupTag case object ParTag extends GroupTag -case object XorTag extends GroupTag { +case object XorTag extends SeqGroupTag { case object LeftBiased extends GroupTag } -case class XorParTag(xor: FuncOp, par: FuncOp) extends OpTag -case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupTag -case class NextTag(item: String) extends OpTag +case class XorParTag(xor: FuncOp, par: FuncOp) extends RawTag + +case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupTag { + + override def toString: String = + s"(on $peerId${if (via.nonEmpty) " via " + via.toList.mkString(" via ") else ""})" +} +case class NextTag(item: String) extends RawTag case class MatchMismatchTag(left: ValueModel, right: ValueModel, shouldMatch: Boolean) extends SeqGroupTag case class ForTag(item: String, iterable: ValueModel) extends SeqGroupTag -case class MetaTag( - skipTopology: Boolean = false, - comment: Option[String] = None, - op: OpTag -) extends OpTag - case class CallArrowTag( funcName: String, call: Call -) extends OpTag +) extends RawTag case class AssignmentTag( value: ValueModel, assignTo: String -) extends NoAirTag +) extends NoExecTag case class AbilityIdTag( value: ValueModel, service: String -) extends NoAirTag +) extends NoExecTag case class CallServiceTag( serviceId: ValueModel, funcName: String, - call: Call, - peerId: Option[ValueModel] = None -) extends OpTag + call: Call +) extends RawTag { + override def toString: String = s"(call _ ($serviceId $funcName) $call)" +} diff --git a/model/src/main/scala/aqua/model/func/body/ShowFuncOp.scala b/model/src/main/scala/aqua/model/func/raw/ShowFuncOp.scala similarity index 94% rename from model/src/main/scala/aqua/model/func/body/ShowFuncOp.scala rename to model/src/main/scala/aqua/model/func/raw/ShowFuncOp.scala index 64b8efe4..1801e3a4 100644 --- a/model/src/main/scala/aqua/model/func/body/ShowFuncOp.scala +++ b/model/src/main/scala/aqua/model/func/raw/ShowFuncOp.scala @@ -1,4 +1,4 @@ -package aqua.model.func.body +package aqua.model.func.raw import cats.Show import cats.free.Cofree diff --git a/model/src/main/scala/aqua/model/func/resolved/MakeRes.scala b/model/src/main/scala/aqua/model/func/resolved/MakeRes.scala new file mode 100644 index 00000000..1b6b38f5 --- /dev/null +++ b/model/src/main/scala/aqua/model/func/resolved/MakeRes.scala @@ -0,0 +1,31 @@ +package aqua.model.func.resolved + +import aqua.model.func.Call +import aqua.model.{LiteralModel, ValueModel} +import cats.Eval +import cats.data.Chain +import cats.free.Cofree + +object MakeRes { + val nilTail: Eval[Chain[Cofree[Chain, ResolvedOp]]] = Eval.now(Chain.empty) + type Cof = Cofree[Chain, ResolvedOp] + def leaf(op: ResolvedOp): Cof = Cofree[Chain, ResolvedOp](op, nilTail) + + def next(item: String): Cof = + leaf(NextRes(item)) + + def seq(first: Cof, second: Cof, more: Cof*): Cof = + Cofree[Chain, ResolvedOp](SeqRes, Eval.later(first +: second +: Chain.fromSeq(more))) + + def par(first: Cof, second: Cof, more: Cof*): Cof = + Cofree[Chain, ResolvedOp](ParRes, Eval.later(first +: second +: Chain.fromSeq(more))) + + def xor(first: Cof, second: Cof): Cof = + Cofree[Chain, ResolvedOp](XorRes, Eval.later(Chain(first, second))) + + def fold(item: String, iter: ValueModel, body: Cof): Cof = + Cofree[Chain, ResolvedOp](FoldRes(item, iter), Eval.now(Chain.one(body))) + + def noop(onPeer: ValueModel): Cof = + leaf(CallServiceRes(LiteralModel.quote("op"), "noop", Call(Nil, None), onPeer)) +} diff --git a/model/src/main/scala/aqua/model/func/resolved/ResolvedOp.scala b/model/src/main/scala/aqua/model/func/resolved/ResolvedOp.scala new file mode 100644 index 00000000..4e16b539 --- /dev/null +++ b/model/src/main/scala/aqua/model/func/resolved/ResolvedOp.scala @@ -0,0 +1,34 @@ +package aqua.model.func.resolved + +import aqua.model.ValueModel +import aqua.model.func.Call + +sealed trait ResolvedOp + +sealed trait NoAir extends ResolvedOp + +case object SeqRes extends ResolvedOp +case object ParRes extends ResolvedOp +case object XorRes extends ResolvedOp + +case class NextRes(item: String) extends ResolvedOp + +case class MatchMismatchRes(left: ValueModel, right: ValueModel, shouldMatch: Boolean) + extends ResolvedOp { + override def toString: String = s"(${if (shouldMatch) "match" else "mismatch"} $left $right)" +} +case class FoldRes(item: String, iterable: ValueModel) extends ResolvedOp + +case class AbilityIdRes( + value: ValueModel, + service: String +) extends NoAir + +case class CallServiceRes( + serviceId: ValueModel, + funcName: String, + call: Call, + peerId: ValueModel +) extends ResolvedOp { + override def toString: String = s"(call $peerId ($serviceId $funcName) $call)" +} diff --git a/model/src/main/scala/aqua/model/topology/Cursor.scala b/model/src/main/scala/aqua/model/topology/Cursor.scala deleted file mode 100644 index c97e41ce..00000000 --- a/model/src/main/scala/aqua/model/topology/Cursor.scala +++ /dev/null @@ -1,128 +0,0 @@ -package aqua.model.topology - -import Topology.Tree -import aqua.model.func.body.{MetaTag, OnTag, OpTag, ParTag, SeqTag, XorTag} -import cats.Eval -import cats.data.Chain -import cats.free.Cofree -import cats.syntax.functor._ -import wvlet.log.LogSupport - -case class Cursor(point: ChainZipper[Tree], loc: Location) extends LogSupport { - - def downLoc(tree: Tree): Location = - loc.down(point.copy(current = tree)) - - def moveUp: Option[Cursor] = - loc.path match { - case cz :: tail => - Some(Cursor(cz.replaceInjecting(point), Location(tail))) - case _ => - None - } - - def pathToRoot: LazyList[Cursor] = - moveUp.to(LazyList).flatMap(c => c #:: c.pathToRoot) - - def mapParent(f: Tree => Tree): Cursor = - copy(loc = - Location( - loc.path match { - case parent :: tail => parent.copy(current = f(parent.current)) :: tail - case path => path - } - ) - ) - - def prevOnTags: Chain[OnTag] = - Chain - .fromOption( - point.prev.lastOption - .map(t => loc.pathOn -> t) - .orElse(loc.lastLeftSeq.map(_.map(_.pathOn).swap.map(_.current))) - ) - .flatMap(pt => pt._1.widen[OpTag] ++ Cursor.rightBoundary(pt._2)) - .takeWhile { - case ParTag => false - case MetaTag(_, _, ParTag) => false - case _ => true - } - .collect { - case o: OnTag => - o - case MetaTag(false, _, o: OnTag) => - o - } - - def nextOnTags: Chain[OnTag] = - Chain - .fromOption( - loc.lastRightSeq - .map(_.map(_.pathOn).swap.map(_.current)) - ) - .flatMap(pt => pt._1 ++ Cursor.leftBoundary(pt._2)) - .takeWhile { - case ParTag => false - case MetaTag(_, _, ParTag) => false - case _ => true - } - .collect { - case o: OnTag => - o - case MetaTag(false, _, o: OnTag) => - o - } -} - -object Cursor { - - def rightBoundary(root: Tree): Chain[OpTag] = - Chain.fromSeq(rightBoundaryLazy(root)) - - def rightBoundaryLazy(root: Tree): LazyList[OpTag] = - root.head #:: (root.head match { - case XorTag => - root.tailForced.reverse.toList match { - case _ :: v :: _ => - // Go through the main branch of xor - rightBoundaryLazy(v) - case v :: Nil => rightBoundaryLazy(v) - case _ => LazyList.empty - } - case _ => - root.tailForced.lastOption.to(LazyList).flatMap(rightBoundaryLazy) - }) - - def leftBoundary(root: Tree): Chain[OpTag] = - Chain - .fromSeq( - root.head #:: LazyList.unfold(root.tail)(_.value.headOption.map(lo => lo.head -> lo.tail)) - ) - - def transform(root: Tree)(f: Cursor => List[Tree]): Option[Tree] = { - def step(cursor: Cursor): Option[Tree] = - f(cursor) match { - case Nil => None - case h :: Nil => - val np = cursor.downLoc(h) - Some(h.copy(tail = h.tail.map(ChainZipper.fromChainMap(_)(cz => step(Cursor(cz, np)))))) - case hs => - ChainZipper - .fromChain(Chain.fromSeq(hs)) - .map(cursor.point.replaceInjecting) - .map { cfh => - val np = cursor.loc.down(cfh) - cfh.current.copy(tail = - cfh.current.tail.map(ChainZipper.fromChainMap(_)(cz => step(Cursor(cz, np)))) - ) - } - .uncons - .map { - case (h, t) if t.isEmpty => h - case (h, t) => Cofree(SeqTag, Eval.later(h +: t)) - } - } - step(Cursor(ChainZipper.one(root), Location())) - } - -} diff --git a/model/src/main/scala/aqua/model/topology/Location.scala b/model/src/main/scala/aqua/model/topology/Location.scala deleted file mode 100644 index 642de250..00000000 --- a/model/src/main/scala/aqua/model/topology/Location.scala +++ /dev/null @@ -1,62 +0,0 @@ -package aqua.model.topology - -import aqua.model.func.body.{MetaTag, OnTag, SeqGroupTag} -import cats.data.Chain -import cats.free.Cofree -import wvlet.log.LogSupport - -case class Location(path: List[ChainZipper[Topology.Tree]] = Nil) extends LogSupport { - def down(h: ChainZipper[Topology.Tree]): Location = copy(h :: path) - - def lastOn: Option[OnTag] = pathOn.lastOption - - def firstOn: Option[OnTag] = pathOn.headOption - - lazy val pathOn: Chain[OnTag] = Chain - .fromSeq(path.map(_.current.head).collect { - case o: OnTag => - o - case MetaTag(false, _, o: OnTag) => o - }) - .reverse - - def lastLeftSeq: Option[(ChainZipper[Topology.Tree], Location)] = - path match { - case (cz @ ChainZipper( - prev, - Cofree(_: SeqGroupTag | MetaTag(false, _, _: SeqGroupTag), _), - _ - )) :: tail if prev.nonEmpty => - cz.moveLeft.map(_ -> Location(tail)) - case _ :: tail => - Location(tail).lastLeftSeq - case Nil => None - } - - def lastRightSeq: Option[(ChainZipper[Topology.Tree], Location)] = - path match { - case (cz @ ChainZipper( - _, - Cofree(_: SeqGroupTag | MetaTag(false, _, _: SeqGroupTag), _), - next - )) :: tail if next.nonEmpty => - cz.moveRight.map(_ -> Location(tail)) - case _ :: tail => Location(tail).lastRightSeq - case Nil => None - } -} - -object Location { - - object Matchers { - - object /: { - - def unapply(loc: Location): Option[(ChainZipper[Topology.Tree], Location)] = - loc.path match { - case h :: tail => Some(h -> Location(tail)) - case _ => None - } - } - } -} diff --git a/model/src/main/scala/aqua/model/topology/Topology.scala b/model/src/main/scala/aqua/model/topology/Topology.scala index a29a67f0..f6bdbe5a 100644 --- a/model/src/main/scala/aqua/model/topology/Topology.scala +++ b/model/src/main/scala/aqua/model/topology/Topology.scala @@ -1,225 +1,129 @@ package aqua.model.topology -import aqua.model.{ValueModel, VarModel} -import aqua.model.func.body._ +import aqua.model.{LiteralModel, ValueModel, VarModel} +import aqua.model.func.raw._ import cats.Eval -import cats.data.Chain -import cats.data.Chain.{:==, ==:, nil} +import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT} +import cats.data.Chain.nil import cats.free.Cofree -import ChainZipper.Matchers._ -import Location.Matchers._ +import aqua.model.cursor.ChainZipper +import aqua.model.func.resolved._ import aqua.types.{BoxType, ScalarType} import wvlet.log.LogSupport - -import scala.annotation.tailrec +import cats.syntax.traverse._ object Topology extends LogSupport { - type Tree = Cofree[Chain, OpTag] + type Tree = Cofree[Chain, RawTag] + type Res = Cofree[Chain, ResolvedOp] - def resolve(op: Tree): Tree = + def resolve(op: Tree): Res = Cofree - .cata[Chain, OpTag, Tree](resolveOnMoves(op)) { - case (SeqTag | _: OnTag | MetaTag(false, _, SeqTag | _: OnTag), children) => + .cata[Chain, ResolvedOp, Res](resolveOnMoves(op).value) { + case (SeqRes, children) => Eval.later( - Cofree( - SeqTag, - Eval.now(children.flatMap { - case Cofree(SeqTag, ch) => ch.value - case cf => Chain.one(cf) - }) - ) + children.uncons + .filter(_._2.isEmpty) + .map(_._1) + .getOrElse( + Cofree( + SeqRes, + Eval.now(children.flatMap { + case Cofree(SeqRes, ch) => ch.value + case cf => Chain.one(cf) + }) + ) + ) ) case (head, children) => Eval.later(Cofree(head, Eval.now(children))) } .value - def resolveOnMoves(op: Tree): Tree = - Cursor - .transform(op)(transformWalker) - .getOrElse(op) - - @tailrec - private def transformWalker(c: Cursor): List[Tree] = - c match { - case Cursor(_, `head`(parent: MetaTag) /: _) if !parent.skipTopology => - transformWalker(c.mapParent(p => p.copy(parent.op, p.tail))) - - case Cursor( - `current`(cf), - loc @ `head`(parent: GroupTag) /: _ - ) => - // Set the service call IDs - val cfu = cf.copy(setServiceCallPeerId(cf.head, loc)) - - // We need to get there, finally - val currentPeerId = Chain.fromOption(loc.lastOn.map(_.peerId)) - - debug("Going to handle: " + cf.head) - - val fromPrevToCurrentPath = fromPrevToCurrent(c, currentPeerId) - if (fromPrevToCurrentPath.nonEmpty) debug("BEFORE = " + fromPrevToCurrentPath) - - val fromCurrentToNextPath = fromCurrentToNext(parent, c, currentPeerId) - if (fromCurrentToNextPath.nonEmpty) debug("NEXT = " + fromCurrentToNextPath) - - (through(fromPrevToCurrentPath) - .append(cfu) ++ through(fromCurrentToNextPath, reversed = true)).toList - - case Cursor(ChainZipper(_, cf, _), loc) => - cf.copy(setServiceCallPeerId(cf.head, loc)) :: Nil - } - - def fromPrevToCurrent(c: Cursor, currentPeerId: Chain[ValueModel]): Chain[ValueModel] = { - val prevOn = c.prevOnTags - val currentOn = c.loc.pathOn - - val wasHandled = c.pathToRoot.collectFirst { case cc @ Cursor(_, `head`(_: GroupTag) /: _) => - cc.loc.pathOn - }.exists(cclp => - cclp == currentOn && { - val (c1, _) = skipCommonPrefix(prevOn, cclp) - c1.isEmpty - } + def wrap(cz: ChainZipper[Res]): Chain[Res] = + Chain.one( + if (cz.prev.nonEmpty || cz.next.nonEmpty) Cofree(SeqRes, Eval.now(cz.chain)) + else cz.current ) - // Need to get from there - val prevPeerId = - Chain.fromOption(prevOn.lastOption.map(_.peerId) orElse c.loc.firstOn.map(_.peerId)) + def resolveOnMoves(op: Tree): Eval[Res] = + RawCursor(NonEmptyList.one(ChainZipper.one(op))) + .cata(wrap) { rc => + debug(s"<:> $rc") + OptionT[Eval, ChainZipper[Res]]( + ({ + case SeqTag => SeqRes + case _: OnTag => SeqRes + case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s) + case ForTag(item, iter) => FoldRes(item, iter) + case ParTag => ParRes + case XorTag | XorTag.LeftBiased => XorRes + case NextTag(item) => NextRes(item) + case CallServiceTag(serviceId, funcName, call) => + CallServiceRes( + serviceId, + funcName, + call, + rc.currentPeerId + .getOrElse(LiteralModel.initPeerId) + ) + }: PartialFunction[RawTag, ResolvedOp]).lift + .apply(rc.tag) + .map(MakeRes.leaf) + .traverse(c => + Eval.later { + val cz = ChainZipper( + through(rc.pathFromPrev), + c, + through(rc.pathToNext) + ) + if (cz.next.nonEmpty || cz.prev.nonEmpty) { + debug(s"Resolved $rc -> $c") + if (cz.prev.nonEmpty) + trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> ")) + if (cz.next.nonEmpty) + trace("To next: " + cz.next.map(_.head).toList.mkString(" -> ")) + } else debug(s"EMPTY $rc -> $c") + cz + } + ) + ) - if (wasHandled) Chain.empty[ValueModel] - else - findPath(prevOn, currentOn, prevPeerId, currentPeerId) - } - - def fromCurrentToNext( - parent: OpTag, - c: Cursor, - currentPeerId: Chain[ValueModel] - ): Chain[ValueModel] = { - // Usually we don't need to go next - val nextOn = parent match { - case ParTag => - val exports = FuncOp(c.point.current).exportsVarNames.value - if (exports.isEmpty) Chain.empty[OnTag] - else { - val isUsed = c.pathToRoot.tail.collect { - case Cursor(cz, `head`(gt: GroupTag) /: _) if gt != ParTag => - cz.next.map(FuncOp(_)).map(_.usesVarNames) - }.exists(_.exists(_.value.intersect(exports).nonEmpty)) - if (isUsed) c.nextOnTags else Chain.empty[OnTag] - } - case XorTag if c.point.prev.nonEmpty => c.nextOnTags - case _ => Chain.empty[OnTag] - } - val nextPeerId = - if (nextOn.nonEmpty) Chain.fromOption(nextOn.lastOption.map(_.peerId)) else currentPeerId - - val targetOn: Option[OnTag] = c.point.current.head match { - case o: OnTag => Option(o) - case _ => None - } - val currentOn = c.loc.pathOn - val currentOnInside = targetOn.fold(currentOn)(currentOn :+ _) - findPath( - currentOnInside, - nextOn, - currentPeerId, - nextPeerId - ) - } - - def optimizePath( - peerIds: Chain[ValueModel], - prefix: Chain[ValueModel], - suffix: Chain[ValueModel] - ): Chain[ValueModel] = { - val optimized = peerIds - .foldLeft(Chain.empty[ValueModel]) { - case (acc, p) if acc.lastOption.contains(p) => acc - case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p - case (acc, p) => acc :+ p } - val noPrefix = skipPrefix(optimized, prefix, optimized) - skipSuffix(noPrefix, suffix, noPrefix) - } - - def findPath( - fromOn: Chain[OnTag], - toOn: Chain[OnTag], - fromPeer: Chain[ValueModel], - toPeer: Chain[ValueModel] - ): Chain[ValueModel] = { - val (from, to) = skipCommonPrefix(fromOn, toOn) - val fromFix = - if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from - val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to - val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via) - val optimized = optimizePath(fromPeer ++ fromTo ++ toPeer, fromPeer, toPeer) - - debug("FIND PATH " + fromFix) - debug(" -> " + toFix) - debug(" Optimized: " + optimized) - optimized - } - - @tailrec - def skipPrefix[T](chain: Chain[T], prefix: Chain[T], init: Chain[T]): Chain[T] = - (chain, prefix) match { - case (c ==: ctail, p ==: ptail) if c == p => skipPrefix(ctail, ptail, init) - case (_, `nil`) => chain - case (_, _) => init - } - - @tailrec - def skipCommonPrefix[T](chain1: Chain[T], chain2: Chain[T]): (Chain[T], Chain[T]) = - (chain1, chain2) match { - case (c ==: ctail, p ==: ptail) if c == p => skipCommonPrefix(ctail, ptail) - case _ => chain1 -> chain2 - } - - @tailrec - def skipSuffix[T](chain: Chain[T], suffix: Chain[T], init: Chain[T]): Chain[T] = - (chain, suffix) match { - case (cinit :== c, pinit :== p) if c == p => skipSuffix(cinit, pinit, init) - case (_, `nil`) => chain - case (_, _) => init - } + .map(NonEmptyChain.fromChain(_).map(_.uncons)) + .map { + case None => + error("Topology emitted nothing") + Cofree(SeqRes, MakeRes.nilTail) + case Some((el, `nil`)) => el + case Some((el, tail)) => + warn("Topology emitted many nodes, that's unusual") + Cofree(SeqRes, Eval.now(el +: tail)) + } // Walks through peer IDs, doing a noop function on each // If same IDs are found in a row, does noop only once // if there's a chain like a -> b -> c -> ... -> b -> g, remove everything between b and b - def through(peerIds: Chain[ValueModel], reversed: Boolean = false): Chain[Tree] = + def through(peerIds: Chain[ValueModel], reversed: Boolean = false): Chain[Res] = peerIds.map { v => v.lastType match { case _: BoxType => val itemName = "-via-peer-" - FuncOps.meta( - FuncOps.fold( - itemName, - v, - if (reversed) - FuncOps.seq( - FuncOps.next(itemName), - FuncOps.noop(VarModel(itemName, ScalarType.string)) - ) - else - FuncOps.seq( - FuncOps.noop(VarModel(itemName, ScalarType.string)), - FuncOps.next(itemName) - ) - ), - skipTopology = true + MakeRes.fold( + itemName, + v, + if (reversed) + MakeRes.seq( + MakeRes.next(itemName), + MakeRes.noop(VarModel(itemName, ScalarType.string)) + ) + else + MakeRes.seq( + MakeRes.noop(VarModel(itemName, ScalarType.string)), + MakeRes.next(itemName) + ) ) case _ => - FuncOps.noop(v) + MakeRes.noop(v) } } - .map(_.tree) - - def setServiceCallPeerId(tag: OpTag, loc: Location): OpTag = tag match { - case c: CallServiceTag if c.peerId.isEmpty => - c.copy(peerId = loc.lastOn.map(_.peerId)) - case t => t - } } diff --git a/model/src/main/scala/aqua/model/transform/ArgsProvider.scala b/model/src/main/scala/aqua/model/transform/ArgsProvider.scala index 0253e093..2283ae01 100644 --- a/model/src/main/scala/aqua/model/transform/ArgsProvider.scala +++ b/model/src/main/scala/aqua/model/transform/ArgsProvider.scala @@ -2,8 +2,8 @@ package aqua.model.transform import aqua.model.{ValueModel, VarModel} import aqua.model.func.Call -import aqua.model.func.body.{FuncOp, FuncOps} -import aqua.types.{ArrayType, DataType, OptionType, StreamType, Type} +import aqua.model.func.raw.{FuncOp, FuncOps} +import aqua.types.{ArrayType, DataType, StreamType} import cats.data.Chain trait ArgsProvider { @@ -26,7 +26,6 @@ case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataT item, VarModel(iter, ArrayType(t.element), Chain.empty), FuncOps.seq( - // TODO: currently this does not work, as identity wraps everything with an array FuncOps.identity(VarModel(item, t.element), Call.Export(name, t)), FuncOps.next(item) ) diff --git a/model/src/main/scala/aqua/model/transform/ErrorsCatcher.scala b/model/src/main/scala/aqua/model/transform/ErrorsCatcher.scala index 11b3bf19..7920cc21 100644 --- a/model/src/main/scala/aqua/model/transform/ErrorsCatcher.scala +++ b/model/src/main/scala/aqua/model/transform/ErrorsCatcher.scala @@ -2,7 +2,7 @@ package aqua.model.transform import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.model.func.Call -import aqua.model.func.body.{FuncOp, FuncOps, MatchMismatchTag, OnTag, OpTag, XorTag} +import aqua.model.func.raw.{FuncOp, FuncOps, MatchMismatchTag, OnTag, RawTag, XorTag} import aqua.types.LiteralType import cats.Eval import cats.data.Chain @@ -19,7 +19,7 @@ case class ErrorsCatcher( if (enabled) { var i = 0 op - .cata[Cofree[Chain, OpTag]] { + .cata[Cofree[Chain, RawTag]] { case (tag, children) if children.length == 1 && children.headOption.exists( _.head == XorTag.LeftBiased diff --git a/model/src/main/scala/aqua/model/transform/InitPeerCallable.scala b/model/src/main/scala/aqua/model/transform/InitPeerCallable.scala index 6325d916..8f8c7d9b 100644 --- a/model/src/main/scala/aqua/model/transform/InitPeerCallable.scala +++ b/model/src/main/scala/aqua/model/transform/InitPeerCallable.scala @@ -2,7 +2,7 @@ package aqua.model.transform import aqua.model.{LiteralModel, ValueModel} import aqua.model.func.Call -import aqua.model.func.body.{FuncOp, FuncOps} +import aqua.model.func.raw.{FuncOp, FuncOps} import cats.data.Chain sealed trait InitPeerCallable { diff --git a/model/src/main/scala/aqua/model/transform/ResolveFunc.scala b/model/src/main/scala/aqua/model/transform/ResolveFunc.scala index 0b9b763b..4d50bafb 100644 --- a/model/src/main/scala/aqua/model/transform/ResolveFunc.scala +++ b/model/src/main/scala/aqua/model/transform/ResolveFunc.scala @@ -2,7 +2,7 @@ package aqua.model.transform import aqua.model.{ValueModel, VarModel} import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable} -import aqua.model.func.body.{FuncOp, FuncOps} +import aqua.model.func.raw.{FuncOp, FuncOps} import aqua.types.ArrowType import cats.Eval import cats.syntax.apply._ diff --git a/model/src/main/scala/aqua/model/transform/Transform.scala b/model/src/main/scala/aqua/model/transform/Transform.scala index 8626e589..ca2e4b5d 100644 --- a/model/src/main/scala/aqua/model/transform/Transform.scala +++ b/model/src/main/scala/aqua/model/transform/Transform.scala @@ -1,27 +1,28 @@ package aqua.model.transform -import aqua.model.func.body._ import aqua.model.func.FuncCallable import aqua.model.VarModel +import aqua.model.func.resolved.{NoAir, ResolvedOp} import aqua.model.topology.Topology import aqua.types.ScalarType import cats.data.Chain import cats.free.Cofree +import wvlet.log.LogSupport -object Transform { +object Transform extends LogSupport { - def defaultFilter(t: OpTag): Boolean = t match { - case _: NoAirTag => false + def defaultFilter(t: ResolvedOp): Boolean = t match { + case _: NoAir => false case _ => true } def clear( - tree: Cofree[Chain, OpTag], - filter: OpTag => Boolean = defaultFilter - ): Cofree[Chain, OpTag] = + tree: Cofree[Chain, ResolvedOp], + filter: ResolvedOp => Boolean = defaultFilter + ): Cofree[Chain, ResolvedOp] = tree.copy(tail = tree.tail.map(_.filter(t => filter(t.head)).map(clear(_, filter)))) - def forClient(func: FuncCallable, conf: BodyConfig): Cofree[Chain, OpTag] = { + def forClient(func: FuncCallable, conf: BodyConfig): Cofree[Chain, ResolvedOp] = { val initCallable: InitPeerCallable = InitViaRelayCallable( Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string)) ) diff --git a/parser/src/main/scala/aqua/parser/Expr.scala b/parser/src/main/scala/aqua/parser/Expr.scala index e137dfc6..92857761 100644 --- a/parser/src/main/scala/aqua/parser/Expr.scala +++ b/parser/src/main/scala/aqua/parser/Expr.scala @@ -130,8 +130,8 @@ object Expr { case block if block.isBlock => acc.copy(block = Some(indent -> currentExpr)) // create leaf if token is on current level - case e => - acc.copy(currentChildren = acc.currentChildren.append(leaf(e))) + case _ => + acc.copy(currentChildren = acc.currentChildren.append(currentExpr)) } // if we have root companion, gather all expressions that have indent > than current case r @ Some((_, block)) => @@ -163,10 +163,10 @@ object Expr { window = Chain.empty ) // create leaf if token is on current level - case e => + case _ => acc.copy( block = None, - currentChildren = withTree.append(leaf(e)), + currentChildren = withTree.append(currentExpr), window = Chain.empty ) } diff --git a/parser/src/test/scala/aqua/parser/FuncExprSpec.scala b/parser/src/test/scala/aqua/parser/FuncExprSpec.scala index 51ca4b19..62e064cc 100644 --- a/parser/src/test/scala/aqua/parser/FuncExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/FuncExprSpec.scala @@ -267,4 +267,14 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec { qGenComplex.d() shouldBe elseCall qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None)*/ } + + "function with par" should "be parsed correctly" in { + val script = + """func topologyTest(): + | on "friend": + | str2 <- LocalPrint.print("in on") + | par LocalPrint.print("in par")""".stripMargin + + val tree = parser[Id]().parseAll(script).value.toEither.value + } } diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index b88d0870..b6ecc2e1 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -1,7 +1,7 @@ package aqua.semantics import aqua.model.{AquaContext, Model, ScriptModel} -import aqua.model.func.body.FuncOp +import aqua.model.func.raw.FuncOp import aqua.parser.lexer.Token import aqua.parser.{Ast, Expr} import aqua.semantics.rules.ReportError @@ -23,8 +23,9 @@ import monocle.Lens import monocle.macros.GenLens import cats.syntax.apply._ import cats.syntax.semigroup._ +import wvlet.log.LogSupport -object Semantics { +object Semantics extends LogSupport { def folder[F[_], G[_]](implicit A: AbilitiesAlgebra[F, G], @@ -91,7 +92,7 @@ object Semantics { .run(CompilerState.init[F](init)) .map { case (state, gen: ScriptModel) => - val ctx = AquaContext.fromScriptModel(gen, aqum.empty) + val ctx = AquaContext.fromScriptModel(gen, init) NonEmptyChain .fromChain(state.errors) .fold[ValidatedNec[SemanticError[F], AquaContext]](Valid(ctx))(Invalid(_)) diff --git a/semantics/src/main/scala/aqua/semantics/expr/AbilityIdSem.scala b/semantics/src/main/scala/aqua/semantics/expr/AbilityIdSem.scala index e25612a1..60093995 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/AbilityIdSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/AbilityIdSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.Model -import aqua.model.func.body.{AbilityIdTag, FuncOp} +import aqua.model.func.raw.{AbilityIdTag, FuncOp} import aqua.parser.expr.AbilityIdExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/AssignmentSem.scala b/semantics/src/main/scala/aqua/semantics/expr/AssignmentSem.scala index 5b2e18f6..d460ea19 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/AssignmentSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/AssignmentSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.Model -import aqua.model.func.body.{AssignmentTag, FuncOp, FuncOps} +import aqua.model.func.raw.{AssignmentTag, FuncOp, FuncOps} import aqua.parser.expr.{AbilityIdExpr, AssignmentExpr} import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/CallArrowSem.scala b/semantics/src/main/scala/aqua/semantics/expr/CallArrowSem.scala index 896e636d..37d5fa36 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/CallArrowSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/CallArrowSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.func.Call -import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp} +import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp} import aqua.model.{Model, ValueModel} import aqua.parser.expr.CallArrowExpr import aqua.semantics.Prog diff --git a/semantics/src/main/scala/aqua/semantics/expr/CatchSem.scala b/semantics/src/main/scala/aqua/semantics/expr/CatchSem.scala index e5d4001c..b717f252 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/CatchSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/CatchSem.scala @@ -1,6 +1,6 @@ package aqua.semantics.expr -import aqua.model.func.body.{AssignmentTag, FuncOp, FuncOps, XorTag} +import aqua.model.func.raw.{AssignmentTag, FuncOp, FuncOps, XorTag} import aqua.model.{Model, VarModel} import aqua.parser.expr.CatchExpr import aqua.semantics.Prog diff --git a/semantics/src/main/scala/aqua/semantics/expr/ElseOtherwiseSem.scala b/semantics/src/main/scala/aqua/semantics/expr/ElseOtherwiseSem.scala index f9faa2bf..cbe56526 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/ElseOtherwiseSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/ElseOtherwiseSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.Model -import aqua.model.func.body.{FuncOp, XorTag} +import aqua.model.func.raw.{FuncOp, XorTag} import aqua.parser.expr.ElseOtherwiseExpr import aqua.semantics.Prog import aqua.semantics.rules.abilities.AbilitiesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/ForSem.scala b/semantics/src/main/scala/aqua/semantics/expr/ForSem.scala index ac2ec0b5..69718cfc 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/ForSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/ForSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.{Model, ValueModel} -import aqua.model.func.body._ +import aqua.model.func.raw._ import aqua.parser.expr.ForExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra @@ -41,7 +41,7 @@ class ForSem[F[_]](val expr: ForExpr[F]) extends AnyVal { FuncOp.wrap( ForTag(expr.item.value, vm), FuncOp.node( - expr.mode.map(_._2).fold[OpTag](SeqTag) { + expr.mode.map(_._2).fold[RawTag](SeqTag) { case ForExpr.ParMode => ParTag case ForExpr.TryMode => XorTag }, diff --git a/semantics/src/main/scala/aqua/semantics/expr/FuncSem.scala b/semantics/src/main/scala/aqua/semantics/expr/FuncSem.scala index c5600b1c..605c9ded 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/FuncSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/FuncSem.scala @@ -1,6 +1,6 @@ package aqua.semantics.expr -import aqua.model.func.body.FuncOp +import aqua.model.func.raw.FuncOp import aqua.model.{Model, ValueModel} import aqua.model.func.{ArgDef, ArgsDef, FuncModel} import aqua.parser.expr.FuncExpr diff --git a/semantics/src/main/scala/aqua/semantics/expr/IfSem.scala b/semantics/src/main/scala/aqua/semantics/expr/IfSem.scala index 3f652d8e..4d7b44c8 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/IfSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/IfSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.{Model, ValueModel} -import aqua.model.func.body.{FuncOp, MatchMismatchTag, XorTag} +import aqua.model.func.raw.{FuncOp, MatchMismatchTag, XorTag} import aqua.parser.expr.IfExpr import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.types.TypesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/OnSem.scala b/semantics/src/main/scala/aqua/semantics/expr/OnSem.scala index 7becf634..6ac17029 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/OnSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/OnSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.{Model, ValueModel} -import aqua.model.func.body.{FuncOp, OnTag} +import aqua.model.func.raw.{FuncOp, OnTag} import aqua.parser.expr.OnExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/ParSem.scala b/semantics/src/main/scala/aqua/semantics/expr/ParSem.scala index a0c6b25b..b038c4f1 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/ParSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/ParSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.Model -import aqua.model.func.body.{FuncOp, ParTag} +import aqua.model.func.raw.{FuncOp, ParTag} import aqua.parser.expr.ParExpr import aqua.semantics.Prog import cats.free.Free diff --git a/semantics/src/main/scala/aqua/semantics/expr/TrySem.scala b/semantics/src/main/scala/aqua/semantics/expr/TrySem.scala index e7e64343..0784c4ac 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/TrySem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/TrySem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr import aqua.model.Model -import aqua.model.func.body.{FuncOp, XorTag} +import aqua.model.func.raw.{FuncOp, XorTag} import aqua.parser.expr.TryExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra diff --git a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala index 7b4b207b..f4d1917b 100644 --- a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala @@ -2,6 +2,7 @@ package aqua.semantics import aqua.Node import aqua.Node._ +import aqua.model.func.raw.{FuncOp, FuncOps, SeqTag} import aqua.model.transform._ import aqua.model.{AquaContext, LiteralModel} import aqua.parser.Ast @@ -13,7 +14,7 @@ import org.scalatest.matchers.should.Matchers class SemanticsSpec extends AnyFlatSpec with Matchers { // use it to fix https://github.com/fluencelabs/aqua/issues/90 - ignore should "create right model" in { + "sem" should "create right model" in { implicit val fileLift: LiftParser[Span.F] = Span.spanLiftParser val script = @@ -37,15 +38,16 @@ class SemanticsSpec extends AnyFlatSpec with Matchers { val proc = Node.cofToNode(func.body.tree) - val expected = - seq( - par( - on(LiteralModel("\"other-peer\"", LiteralType.string), Nil, callLiteral(1)), - callLiteral(1) + val expected: Node.Raw = + FuncOp.wrap( + SeqTag, + FuncOps.par( + on(LiteralModel("\"other-peer\"", LiteralType.string), Nil, callLiteralRaw(1)), + callLiteralRaw(1) ) ) -// proc.equalsOrPrintDiff(expected) should be(true) + proc.equalsOrPrintDiff(expected) should be(true) } } diff --git a/test-kit/src/main/scala/aqua/Node.scala b/test-kit/src/main/scala/aqua/Node.scala index e85df39f..47b66163 100644 --- a/test-kit/src/main/scala/aqua/Node.scala +++ b/test-kit/src/main/scala/aqua/Node.scala @@ -1,7 +1,8 @@ package aqua import aqua.model.func.Call -import aqua.model.func.body._ +import aqua.model.func.raw._ +import aqua.model.func.resolved.{CallServiceRes, MakeRes, MatchMismatchRes, ResolvedOp} import aqua.model.transform.{BodyConfig, ErrorsCatcher} import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.types.{ArrayType, LiteralType, ScalarType} @@ -12,15 +13,143 @@ import cats.free.Cofree import scala.language.implicitConversions // Helper to simplify building and visualizing Cofree structures -case class Node(tag: OpTag, ops: List[Node] = Nil) { +case class Node[+T](label: T, children: List[Node[T]] = Nil) { + + def cof[TT >: T]: Cofree[Chain, TT] = Node.nodeToCof(this) override def toString: String = - tag.toString + (if (ops.isEmpty) "\n" else s"{\n${ops.mkString}\n}\n") + label.toString + (if (children.isEmpty) "\n" else s"{\n${children.mkString}\n}\n") - private def equalOrNot[T](left: T, right: T): String = (if (left == right) - Console.GREEN + left + Console.RESET - else - Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right) + def equalsOrPrintDiff[TT](other: Node[TT]): Boolean = + if (this == other) true + else { + println(Console.CYAN + "Given: " + this) + println(Console.YELLOW + "Other: " + other + Console.RESET) + false + } +} + +object Node { + type Res = Node[ResolvedOp] + type Raw = Node[RawTag] + + implicit def cofToNode[T](cof: Cofree[Chain, T]): Node[T] = + Node[T](cof.head, cof.tailForced.toList.map(cofToNode[T])) + + implicit def nodeToCof[T](tree: Node[T]): Cofree[Chain, T] = + Cofree(tree.label, Eval.later(Chain.fromSeq(tree.children.map(nodeToCof)))) + + implicit def rawToFuncOp(tree: Raw): FuncOp = + FuncOp(tree.cof) + + implicit def funcOpToRaw(op: FuncOp): Raw = + op.tree + + val relay = LiteralModel("-relay-", ScalarType.string) + val relayV = VarModel("-relay-", ScalarType.string) + val initPeer = LiteralModel.initPeerId + val emptyCall = Call(Nil, None) + val otherPeer = LiteralModel("other-peer", ScalarType.string) + val otherPeerL = LiteralModel("\"other-peer\"", LiteralType.string) + val otherRelay = LiteralModel("other-relay", ScalarType.string) + val otherPeer2 = LiteralModel("other-peer-2", ScalarType.string) + val otherRelay2 = LiteralModel("other-relay-2", ScalarType.string) + val varNode = VarModel("node-id", ScalarType.string) + val viaList = VarModel("other-relay-2", ArrayType(ScalarType.string)) + + def callRes( + i: Int, + on: ValueModel, + exportTo: Option[Call.Export] = None, + args: List[ValueModel] = Nil + ): Res = Node( + CallServiceRes(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo), on) + ) + + def callTag(i: Int, exportTo: Option[Call.Export] = None, args: List[ValueModel] = Nil): Raw = + Node( + CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo)) + ) + + def callLiteralRes(i: Int, on: ValueModel, exportTo: Option[Call.Export] = None): Res = Node( + CallServiceRes( + LiteralModel("\"srv" + i + "\"", LiteralType.string), + s"fn$i", + Call(Nil, exportTo), + on + ) + ) + + def callLiteralRaw(i: Int, exportTo: Option[Call.Export] = None): Raw = Node( + CallServiceTag( + LiteralModel("\"srv" + i + "\"", LiteralType.string), + s"fn$i", + Call(Nil, exportTo) + ) + ) + + def errorCall(bc: BodyConfig, i: Int, on: ValueModel = initPeer): Res = Node[ResolvedOp]( + CallServiceRes( + bc.errorHandlingCallback, + bc.errorFuncName, + Call( + ErrorsCatcher.lastErrorArg :: LiteralModel( + i.toString, + LiteralType.number + ) :: Nil, + None + ), + on + ) + ) + + def respCall(bc: BodyConfig, value: ValueModel, on: ValueModel = initPeer): Res = + Node[ResolvedOp]( + CallServiceRes( + bc.callbackSrvId, + bc.respFuncName, + Call(value :: Nil, None), + on + ) + ) + + def dataCall(bc: BodyConfig, name: String, on: ValueModel = initPeer): Res = Node[ResolvedOp]( + CallServiceRes( + bc.dataSrvId, + name, + Call(Nil, Some(Call.Export(name, ScalarType.string))), + on + ) + ) + + def on(peer: ValueModel, via: List[ValueModel], body: Raw*) = + Node( + OnTag(peer, Chain.fromSeq(via)), + body.toList + ) + + def `try`(body: Raw*) = + Node(XorTag.LeftBiased, body.toList) + + def matchRes(l: ValueModel, r: ValueModel, body: Res): Res = + Node( + MatchMismatchRes(l, r, shouldMatch = true), + body :: Nil + ) + + def matchRaw(l: ValueModel, r: ValueModel, body: Raw): Raw = + Node( + MatchMismatchTag(l, r, shouldMatch = true), + body :: Nil + ) + + def through(peer: ValueModel): Node[ResolvedOp] = + cofToNode(MakeRes.noop(peer)) + + private def equalOrNot[TT](left: TT, right: TT): String = (if (left == right) + Console.GREEN + left + Console.RESET + else + Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right) private def diffArg(left: ValueModel, right: ValueModel): String = Console.GREEN + "(" + @@ -36,129 +165,53 @@ case class Node(tag: OpTag, ops: List[Node] = Nil) { .mkString("::") + Console.GREEN + ", " + equalOrNot(left.exportTo, right.exportTo) + Console.GREEN + ")" - private def diffServiceCall(left: CallServiceTag, right: CallServiceTag): String = - Console.GREEN + "CallServiceTag(" + - equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + ", " + - equalOrNot(left.funcName, right.funcName) + Console.GREEN + ", " + - diffCall(left.call, right.call) + Console.GREEN + ", " + - equalOrNot(left.peerId, right.peerId) + + private def diffServiceCall(left: CallServiceRes, right: CallServiceRes): String = + Console.GREEN + "(call" + + equalOrNot(left.peerId, right.peerId) + Console.GREEN + " (" + + equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + " " + + equalOrNot(left.funcName, right.funcName) + Console.GREEN + ") " + + diffCall(left.call, right.call) + Console.GREEN + Console.GREEN + ")" + Console.RESET - private def diffTags(left: OpTag, right: OpTag): String = (left, right) match { - case (l: CallServiceTag, r: CallServiceTag) => diffServiceCall(l, r) + private def diffRes(left: ResolvedOp, right: ResolvedOp): String = (left, right) match { + case (l: CallServiceRes, r: CallServiceRes) => diffServiceCall(l, r) case _ => Console.BLUE + s" $left ${Console.RED}\n != ${Console.YELLOW}${right}${Console.RED}" } - def diffToString(other: Node): String = - (if (tag == other.tag) Console.GREEN + tag - else diffTags(tag, other.tag)) + (if (ops.isEmpty && other.ops.isEmpty) "\n" - else - "{\n") + Console.RESET + - (if (ops.length != other.ops.length) - Console.RED + s"number of ops: ${ops.length} != ${other.ops.length}\n" + Console.RESET + def diffToString(current: Node.Res, other: Node.Res): String = + (if (current.label == other.label) Console.GREEN + current.label + else + diffRes(current.label, other.label)) + (if ( + current.children.isEmpty && other.children.isEmpty + ) "\n" + else + "{\n") + Console.RESET + + (if (current.children.length != other.children.length) + Console.RED + s"number of ops: ${current.children.length} != ${other.children.length}\n" + Console.RESET else "") + - ops - .zip(other.ops) - .map { case (a, b) => - a.diffToString(b) + current.children + .map(Option(_)) + .zipAll(other.children.map(Option(_)), None, None) + .map { + case (Some(a), Some(b)) => + diffToString(a, b) + case (Some(a), _) => + Console.BLUE + a + Console.RESET + case (_, Some(b)) => + Console.YELLOW + b + Console.RESET + case _ => + Console.RED + "???" + Console.RESET } - .mkString + (if (ops.isEmpty && other.ops.isEmpty) "" + .mkString + (if (current.children.isEmpty && other.children.isEmpty) "" else - ((if (tag == other.tag) Console.GREEN + ((if (current.label == other.label) Console.GREEN else Console.RED) + "}\n" + Console.RESET)) - def equalsOrPrintDiff(other: Node): Boolean = - if (this == other) true + def equalsOrPrintDiff(current: Node.Res, other: Node.Res): Boolean = + if (current == other) true else { - println(diffToString(other)) + println(diffToString(current, other)) false } } - -object Node { - type Cof = Cofree[Chain, OpTag] - - implicit def cofToNode(cof: Cof): Node = - Node(cof.head, cof.tailForced.toList.map(cofToNode)) - - implicit def nodeToCof(tree: Node): Cof = - Cofree(tree.tag, Eval.later(Chain.fromSeq(tree.ops.map(nodeToCof)))) - - val relay = LiteralModel("-relay-", ScalarType.string) - val relayV = VarModel("-relay-", ScalarType.string) - val initPeer = LiteralModel.initPeerId - val emptyCall = Call(Nil, None) - val otherPeer = LiteralModel("other-peer", ScalarType.string) - val otherRelay = LiteralModel("other-relay", ScalarType.string) - val otherPeer2 = LiteralModel("other-peer-2", ScalarType.string) - val otherRelay2 = LiteralModel("other-relay-2", ScalarType.string) - val varNode = VarModel("node-id", ScalarType.string) - val viaList = VarModel("other-relay-2", ArrayType(ScalarType.string)) - - def call(i: Int, on: ValueModel = null) = Node( - CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(Nil, None), Option(on)) - ) - - def callLiteral(i: Int, on: ValueModel = null) = Node( - CallServiceTag( - LiteralModel("\"srv" + i + "\"", LiteralType.string), - s"fn$i", - Call(Nil, None), - Option(on) - ) - ) - - def errorCall(bc: BodyConfig, i: Int, on: ValueModel = null) = Node( - CallServiceTag( - bc.errorHandlingCallback, - bc.errorFuncName, - Call( - ErrorsCatcher.lastErrorArg :: LiteralModel( - i.toString, - LiteralType.number - ) :: Nil, - None - ), - Option(on) - ) - ) - - def respCall(bc: BodyConfig, value: ValueModel, on: ValueModel = null) = Node( - CallServiceTag( - bc.callbackSrvId, - bc.respFuncName, - Call(value :: Nil, None), - Option(on) - ) - ) - - def dataCall(bc: BodyConfig, name: String, on: ValueModel = null) = Node( - CallServiceTag( - bc.dataSrvId, - name, - Call(Nil, Some(Call.Export(name, ScalarType.string))), - Option(on) - ) - ) - - def seq(nodes: Node*) = Node(SeqTag, nodes.toList) - def xor(left: Node, right: Node) = Node(XorTag, left :: right :: Nil) - - def par(left: Node, right: Node) = Node(ParTag, left :: right :: Nil) - - def on(peer: ValueModel, via: List[ValueModel], body: Node*) = - Node( - OnTag(peer, Chain.fromSeq(via)), - body.toList - ) - - def _match(l: ValueModel, r: ValueModel, body: Node) = - Node( - MatchMismatchTag(l, r, shouldMatch = true), - body :: Nil - ) - - def through(peer: ValueModel): Node = - FuncOps.noop(peer).tree -} diff --git a/test-kit/src/test/scala/aqua/model/topology/LocationSpec.scala b/test-kit/src/test/scala/aqua/model/topology/LocationSpec.scala deleted file mode 100644 index 40eb6e49..00000000 --- a/test-kit/src/test/scala/aqua/model/topology/LocationSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package aqua.model.topology - -import aqua.model.func.body.SeqTag -import aqua.model.topology.ChainZipper.Matchers.`head` -import aqua.model.topology.Location.Matchers._ -import cats.Eval -import cats.data.Chain -import cats.free.Cofree -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class LocationSpec extends AnyFlatSpec with Matchers { - - "matchers" should "unapply correctly" in { - val loc = - Location(ChainZipper.one(Cofree(SeqTag, Eval.later(Chain.empty[Topology.Tree]))) :: Nil) - - Option(loc).collect { case `head`(SeqTag) /: _ => - true - } should be('defined) - } - -} diff --git a/test-kit/src/test/scala/aqua/model/topology/TopologySpec.scala b/test-kit/src/test/scala/aqua/model/topology/TopologySpec.scala index b7f39c86..b9dd31df 100644 --- a/test-kit/src/test/scala/aqua/model/topology/TopologySpec.scala +++ b/test-kit/src/test/scala/aqua/model/topology/TopologySpec.scala @@ -1,6 +1,14 @@ package aqua.model.topology import aqua.Node +import aqua.model.VarModel +import aqua.model.func.Call +import aqua.model.func.raw.FuncOps +import aqua.model.func.resolved.{MakeRes, ResolvedOp, SeqRes, XorRes} +import aqua.types.ScalarType +import cats.Eval +import cats.data.Chain +import cats.free.Cofree import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers @@ -9,21 +17,21 @@ class TopologySpec extends AnyFlatSpec with Matchers { "topology resolver" should "do nothing on init peer" in { - val init = on( + val init: Node.Raw = on( initPeer, relay :: Nil, - seq( - call(1), - call(2) + FuncOps.seq( + callTag(1), + callTag(2) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq( - call(1, initPeer), - call(2, initPeer) + val expected: Node.Res = + MakeRes.seq( + callRes(1, initPeer), + callRes(2, initPeer) ) proc should be(expected) @@ -32,23 +40,23 @@ class TopologySpec extends AnyFlatSpec with Matchers { "topology resolver" should "go through relay to any other node, directly" in { - val init = on( + val init: Node.Raw = on( initPeer, relay :: Nil, on( otherPeer, Nil, - seq( - call(1), - call(2) + FuncOps.seq( + callTag(1), + callTag(2) ) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq(through(relay), call(1, otherPeer), call(2, otherPeer)) + val expected: Node.Res = + MakeRes.seq(through(relay), callRes(1, otherPeer), callRes(2, otherPeer)) proc should be(expected) } @@ -61,21 +69,139 @@ class TopologySpec extends AnyFlatSpec with Matchers { on( otherPeer, otherRelay :: Nil, - seq( - call(1), - call(2) + FuncOps.seq( + callTag(1), + callTag(2) ) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq( + val expected: Node.Res = + MakeRes.seq( through(relay), through(otherRelay), - call(1, otherPeer), - call(2, otherPeer) + callRes(1, otherPeer), + callRes(2, otherPeer) + ) + + proc.equalsOrPrintDiff(expected) should be(true) + } + + "topology resolver" should "build return path in par if there are exported variables" in { + val export = Some(Call.Export("result", ScalarType.string)) + val result = VarModel("result", ScalarType.string) + + val init = on( + initPeer, + relay :: Nil, + FuncOps.seq( + FuncOps.par( + on( + otherPeer, + otherRelay :: Nil, + callTag(1, export) + ), + callTag(2) + ), + callTag(3, None, result :: Nil) + ) + ) + + val proc: Node.Res = Topology.resolve(init) + + val expected: Node.Res = + MakeRes.seq( + MakeRes.par( + MakeRes.seq( + through(relay), + through(otherRelay), + callRes(1, otherPeer, export), + through(otherRelay), + through(relay), + // we should return to a caller to continue execution + through(initPeer) + ), + callRes(2, initPeer) + ), + callRes(3, initPeer, None, result :: Nil) + ) + + Node.equalsOrPrintDiff(proc, expected) should be(true) + } + + "topology resolver" should "work fine with par" in { + val init = on( + initPeer, + relay :: Nil, + FuncOps.par( + on( + otherPeer, + otherRelay :: Nil, + callTag(1) + ), + callTag(2) + ) + ) + + val proc = Topology.resolve(init) + + val expected: Node.Res = + MakeRes.par( + MakeRes.seq( + through(relay), + through(otherRelay), + callRes(1, otherPeer) + ), + callRes(2, initPeer) + ) + + proc.equalsOrPrintDiff(expected) should be(true) + } + + "topology resolver" should "create correct calls in try" in { + val init = Node.`try`(callTag(1)) + + val proc = Topology.resolve(init) + + proc.equalsOrPrintDiff( + Cofree[Chain, ResolvedOp](XorRes, Eval.now(Chain.one(callRes(1, initPeer)))) + ) should be(true) + } + + "topology resolver" should "work fine with par with on" in { + val init: Node.Raw = on( + initPeer, + relay :: Nil, + FuncOps.par( + on( + otherPeer, + otherRelay :: Nil, + callTag(1) + ), + on( + otherPeer2, + otherRelay2 :: Nil, + callTag(2) + ) + ) + ) + + val proc: Node.Res = Topology.resolve(init) + + val expected: Node.Res = + MakeRes.par( + MakeRes.seq( + through(relay), + through(otherRelay), + callRes(1, otherPeer) + ), + MakeRes.seq( + through(relay), + through(otherRelay2), + callRes(2, otherPeer2) + ) ) proc.equalsOrPrintDiff(expected) should be(true) @@ -89,28 +215,28 @@ class TopologySpec extends AnyFlatSpec with Matchers { on( otherPeer, otherRelay :: Nil, - xor( - seq( - call(1), - call(2) + FuncOps.xor( + FuncOps.seq( + callTag(1), + callTag(2) ), - call(3) + callTag(3) ) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq( + val expected: Node.Res = + MakeRes.seq( through(relay), through(otherRelay), - xor( - seq( - call(1, otherPeer), - call(2, otherPeer) + MakeRes.xor( + MakeRes.seq( + callRes(1, otherPeer), + callRes(2, otherPeer) ), - call(3, otherPeer) + callRes(3, otherPeer) ) ) @@ -118,25 +244,25 @@ class TopologySpec extends AnyFlatSpec with Matchers { } "topology resolver" should "simplify a route with init_peer_id" in { - val init = on( + val init: Node.Raw = on( initPeer, relay :: Nil, - seq( + FuncOps.seq( on( initPeer, relay :: Nil, - call(1) + callTag(1) ), - call(2) + callTag(2) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) val expected = - seq( - call(1, initPeer), - call(2, initPeer) + MakeRes.seq( + callRes(1, initPeer), + callRes(2, initPeer) ) proc.equalsOrPrintDiff(expected) should be(true) @@ -144,29 +270,29 @@ class TopologySpec extends AnyFlatSpec with Matchers { "topology resolver" should "get back to init peer" in { - val init = on( + val init: Node.Raw = on( initPeer, relay :: Nil, - seq( + FuncOps.seq( on( otherPeer, otherRelay :: Nil, - call(1) + callTag(1) ), - call(2) + callTag(2) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq( + val expected: Node.Res = + MakeRes.seq( through(relay), through(otherRelay), - call(1, otherPeer), + callRes(1, otherPeer), through(otherRelay), through(relay), - call(2, initPeer) + callRes(2, initPeer) ) // println(Console.BLUE + init) @@ -197,19 +323,19 @@ class TopologySpec extends AnyFlatSpec with Matchers { val init = on( initPeer, relay :: Nil, - seq( - call(1), - call(2), - call(3), + FuncOps.seq( + callTag(1), + callTag(2), + callTag(3), on( varNode, viaList :: Nil, - call(4) + callTag(4) ), on( initPeer, relay :: Nil, - call(5) + callTag(5) ) ) ) @@ -222,50 +348,50 @@ class TopologySpec extends AnyFlatSpec with Matchers { val init = on( initPeer, relay :: Nil, - seq( + FuncOps.seq( on( otherPeer, otherRelay :: Nil, - call(0), + callTag(0), on( otherPeer2, otherRelay :: Nil, - call(1), - _match( + callTag(1), + matchRaw( otherPeer, otherRelay, on( otherPeer, otherRelay :: Nil, - call(2) + callTag(2) ) ) ) ), - call(3) + callTag(3) ) ) - val proc: Node = Topology.resolve(init) + val proc: Node.Res = Topology.resolve(init) - val expected = - seq( + val expected: Node.Res = + MakeRes.seq( through(relay), through(otherRelay), - call(0, otherPeer), + callRes(0, otherPeer), through(otherRelay), - call(1, otherPeer2), - _match( + callRes(1, otherPeer2), + matchRes( otherPeer, otherRelay, - seq( + MakeRes.seq( through(otherRelay), - call(2, otherPeer) + callRes(2, otherPeer) ) ), through(otherRelay), through(relay), - call(3, initPeer) + callRes(3, initPeer) ) // println(Console.BLUE + init) @@ -276,4 +402,63 @@ class TopologySpec extends AnyFlatSpec with Matchers { proc.equalsOrPrintDiff(expected) should be(true) } + "topology resolver" should "resolve xor path" in { + + val init = on( + initPeer, + relay :: Nil, + FuncOps.seq( + FuncOps.xor( + on( + otherPeer, + otherRelay :: Nil, + callTag(0) + ), + on( + initPeer, + relay :: Nil, + callTag(1) + ) + ), + on( + otherPeer, + otherRelay :: Nil, + callTag(3) + ), + callTag(4) + ) + ) + + val proc: Node.Res = Topology.resolve(init) + + val expected: Node.Res = + MakeRes.seq( + MakeRes.xor( + MakeRes.seq( + through(relay), + through(otherRelay), + callRes(0, otherPeer) + ), + MakeRes.seq( + through(otherRelay), + through(relay), + callRes(1, initPeer), + through(relay), + through(otherRelay) + ) + ), + callRes(3, otherPeer), + through(otherRelay), + through(relay), + callRes(4, initPeer) + ) + +// println(Console.BLUE + init) + println(Console.YELLOW + proc) + println(Console.MAGENTA + expected) + println(Console.RESET) + + Node.equalsOrPrintDiff(proc, expected) should be(true) + } + } diff --git a/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala b/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala index b7a9309a..0b49c7c2 100644 --- a/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala +++ b/test-kit/src/test/scala/aqua/model/transform/TransformSpec.scala @@ -1,7 +1,8 @@ package aqua.model.transform import aqua.Node -import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp} +import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp, FuncOps} +import aqua.model.func.resolved.{CallServiceRes, MakeRes} import aqua.model.func.{ArgsDef, Call, FuncCallable} import aqua.model.{LiteralModel, VarModel} import aqua.types.ScalarType @@ -11,14 +12,14 @@ import org.scalatest.matchers.should.Matchers class TransformSpec extends AnyFlatSpec with Matchers { import Node._ - "transform.forClient" should "work well with function 1 (no calls before on)" in { + "transform.forClient" should "work well with function 1 (no calls before on), generate correct error handling" in { val ret = LiteralModel.quote("return this") val func: FuncCallable = FuncCallable( "ret", - FuncOp(on(otherPeer, otherRelay :: Nil, call(1))), + on(otherPeer, otherRelay :: Nil, callTag(1)), ArgsDef.empty, Some((ret, ScalarType.string)), Map.empty, @@ -29,17 +30,17 @@ class TransformSpec extends AnyFlatSpec with Matchers { val fc = Transform.forClient(func, bc) - val procFC: Node = fc + val procFC: Node.Res = fc - val expectedFC = seq( - xor( - seq( + val expectedFC: Node.Res = + MakeRes.xor( + MakeRes.seq( dataCall(bc, "-relay-", initPeer), through(relayV), through(otherRelay), - xor( - call(1, otherPeer), - seq( + MakeRes.xor( + callRes(1, otherPeer), + MakeRes.seq( through(otherRelay), through(relayV), errorCall(bc, 1, initPeer), @@ -48,22 +49,17 @@ class TransformSpec extends AnyFlatSpec with Matchers { ), through(otherRelay), through(relayV), - xor( + MakeRes.xor( respCall(bc, ret, initPeer), - seq( - errorCall(bc, 2, initPeer) - ) + errorCall(bc, 2, initPeer) ) ), - seq( - errorCall(bc, 3, initPeer) - ) + errorCall(bc, 3, initPeer) ) - ) - println(procFC) + //println(procFC) - procFC.equalsOrPrintDiff(expectedFC) should be(true) + Node.equalsOrPrintDiff(procFC, expectedFC) should be(true) } @@ -73,7 +69,7 @@ class TransformSpec extends AnyFlatSpec with Matchers { val func: FuncCallable = FuncCallable( "ret", - FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))), + FuncOps.seq(callTag(0), on(otherPeer, Nil, callTag(1))), ArgsDef.empty, Some((ret, ScalarType.string)), Map.empty, @@ -84,14 +80,14 @@ class TransformSpec extends AnyFlatSpec with Matchers { val fc = Transform.forClient(func, bc) - val procFC: Node = fc + val procFC: Res = fc - val expectedFC = - seq( + val expectedFC: Res = + MakeRes.seq( dataCall(bc, "-relay-", initPeer), - call(0, initPeer), + callRes(0, initPeer), through(relayV), - call(1, otherPeer), + callRes(1, otherPeer), through(relayV), respCall(bc, ret, initPeer) ) @@ -119,10 +115,9 @@ class TransformSpec extends AnyFlatSpec with Matchers { CallServiceTag( LiteralModel.quote("srv1"), "foo", - Call(Nil, Some(Call.Export("v", ScalarType.string))), - None + Call(Nil, Some(Call.Export("v", ScalarType.string))) ) - ) + ).cof ), ArgsDef.empty, Some((VarModel("v", ScalarType.string), ScalarType.string)), @@ -134,7 +129,7 @@ class TransformSpec extends AnyFlatSpec with Matchers { FuncCallable( "f2", FuncOp( - Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string))))) + Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string))))).cof ), ArgsDef.empty, Some((VarModel("v", ScalarType.string), ScalarType.string)), @@ -144,17 +139,17 @@ class TransformSpec extends AnyFlatSpec with Matchers { val bc = BodyConfig(wrapWithXor = false) - val res = Transform.forClient(f2, bc): Node + val res = Transform.forClient(f2, bc): Node.Res res.equalsOrPrintDiff( - seq( + MakeRes.seq( dataCall(bc, "-relay-", initPeer), Node( - CallServiceTag( + CallServiceRes( LiteralModel.quote("srv1"), "foo", Call(Nil, Some(Call.Export("v", ScalarType.string))), - Some(initPeer) + initPeer ) ), respCall(bc, VarModel("v", ScalarType.string), initPeer) diff --git a/types/src/main/scala/aqua/types/Type.scala b/types/src/main/scala/aqua/types/Type.scala index d81822f7..685d89d4 100644 --- a/types/src/main/scala/aqua/types/Type.scala +++ b/types/src/main/scala/aqua/types/Type.scala @@ -16,7 +16,7 @@ sealed trait Type { sealed trait DataType extends Type case class ScalarType private (name: String) extends DataType { - override def toString: String = s"ScalarType($name)" + override def toString: String = name } object ScalarType { @@ -72,7 +72,7 @@ object ScalarType { } case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType { - override def toString: String = s"Literal($name)" + override def toString: String = s"literal($name)" } object LiteralType {