Fixes missing par (#177)

* Fixes missing par

* test for par

* Par topology bug fixed

* test `on` on every par branch

* Topology refactoring

* Tests compilation wip

* Tests compilation wip

* Tests compile

* Test fix

* Non-par tests fixed

* The last test remains

* Topology tests fixed

* SemanticsSpec compiles

* transformspec wip

* fix diff

* TransformSpec with diff

* test for error handling

* topology resolver spec wip

* delete test, rename test

* fixed

* par with export variable test

* test for try without catch

* Handle try without catch

* XorParTag fix

* Wake up target peer after par

* Increment version

* Fix xor par during func model resolution

* test with import and fold

* Linker bug fixed

Co-authored-by: DieMyst <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
Dmitry Kurinskiy 2021-06-22 11:03:45 +03:00 committed by GitHub
parent f71de81cb4
commit 985309d4eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1140 additions and 963 deletions

View File

@ -1,22 +1,5 @@
import "builtin.aqua" import "print.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
func iterateAndPrint(strings: []string):
for s <- strings:
print(s)

5
aqua-src/print.aqua Normal file
View File

@ -0,0 +1,5 @@
service Println("println-service-id"):
print: string -> ()
func print(str: string):
Println.print(str)

View File

@ -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()

View File

@ -2,22 +2,19 @@ package aqua.backend.air
import aqua.model._ import aqua.model._
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.func.body._ import aqua.model.func.resolved._
import aqua.types.StreamType import aqua.types.StreamType
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
import wvlet.log.Logger import wvlet.log.LogSupport
sealed trait AirGen { sealed trait AirGen {
def generate: Air def generate: Air
} }
object AirGen { object AirGen extends LogSupport {
private val logger = Logger.of[AirGen.type]
import logger._
def lambdaToString(ls: List[LambdaModel]): String = ls match { def lambdaToString(ls: List[LambdaModel]): String = ls match {
case Nil => "" case Nil => ""
@ -46,22 +43,27 @@ object AirGen {
case list => list.reduceLeft(SeqGen) 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 { op match {
case mt: MetaTag => // case mt: MetaTag =>
folder(mt.op, ops).map(ag => mt.comment.fold(ag)(CommentGen(_, ag))) // folder(mt.op, ops).map(ag => mt.comment.fold(ag)(CommentGen(_, ag)))
case SeqTag => case SeqRes =>
Eval later ops.toList.reduceLeftOption(SeqGen).getOrElse(NullGen) Eval later ops.toList.reduceLeftOption(SeqGen).getOrElse(NullGen)
case ParTag => case ParRes =>
Eval later ops.toList.reduceLeftOption(ParGen).getOrElse(NullGen) Eval later ops.toList.reduceLeftOption(ParGen).getOrElse(NullGen)
case XorTag => case XorRes =>
Eval later ops.toList.reduceLeftOption(XorGen).getOrElse(NullGen) Eval later (ops.toList match {
case XorTag.LeftBiased => case o :: Nil => XorGen(o, NullGen)
Eval later XorGen(opsToSingle(ops), 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) Eval later NextGen(item)
case MatchMismatchTag(left, right, shouldMatch) => case MatchMismatchRes(left, right, shouldMatch) =>
Eval later MatchMismatchGen( Eval later MatchMismatchGen(
valueToData(left), valueToData(left),
valueToData(right), valueToData(right),
@ -69,12 +71,12 @@ object AirGen {
opsToSingle(ops) opsToSingle(ops)
) )
case ForTag(item, iterable) => case FoldRes(item, iterable) =>
Eval later ForGen(valueToData(iterable), item, opsToSingle(ops)) 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( Eval.later(
ServiceCallGen( ServiceCallGen(
peerId.map(valueToData).getOrElse(DataView.InitPeerId), valueToData(peerId),
valueToData(serviceId), valueToData(serviceId),
funcName, funcName,
args.map(valueToData), args.map(valueToData),
@ -85,35 +87,14 @@ object AirGen {
) )
) )
case CallArrowTag(funcName, _) => case _: NoAir =>
// TODO: should be already resolved & removed from tree
error(
s"Unresolved arrow in AirGen: $funcName"
)
Eval later NullGen 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 Cofree
.cata[Chain, OpTag, AirGen](op)(folder) .cata[Chain, ResolvedOp, AirGen](op)(folder)
.value .value
} }

View File

@ -24,7 +24,7 @@ val airframeLog = "org.wvlet.airframe" %% "airframe-log" % airframeLogV
name := "aqua-hll" name := "aqua-hll"
val commons = Seq( val commons = Seq(
baseAquaVersion := "0.1.6", baseAquaVersion := "0.1.7",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion, scalaVersion := dottyVersion,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(

View File

@ -1,6 +1,6 @@
package aqua.model 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.model.func.{ArgsCall, FuncCallable, FuncModel}
import aqua.types.{ProductType, Type} import aqua.types.{ProductType, Type}
import cats.Monoid import cats.Monoid
@ -8,6 +8,7 @@ import cats.data.NonEmptyMap
import cats.syntax.apply._ import cats.syntax.apply._
import cats.syntax.functor._ import cats.syntax.functor._
import cats.syntax.monoid._ import cats.syntax.monoid._
import wvlet.log.LogSupport
import scala.collection.immutable.SortedMap import scala.collection.immutable.SortedMap
@ -64,7 +65,7 @@ case class AquaContext(
.map(ProductType(name, _)) .map(ProductType(name, _))
} }
object AquaContext { object AquaContext extends LogSupport {
trait Implicits { trait Implicits {
implicit val aquaContextMonoid: Monoid[AquaContext] implicit val aquaContextMonoid: Monoid[AquaContext]
@ -99,7 +100,7 @@ object AquaContext {
FuncCallable( FuncCallable(
fnName, fnName,
// TODO: capture ability resolution, get ID from the call context // TODO: capture ability resolution, get ID from the call context
FuncOp.leaf(CallServiceTag(serviceId, fnName, call, None)), FuncOp.leaf(CallServiceTag(serviceId, fnName, call)),
args, args,
(ret.map(_.model), arrowType.res).mapN(_ -> _), (ret.map(_.model), arrowType.res).mapN(_ -> _),
Map.empty, Map.empty,

View File

@ -1,6 +1,6 @@
package aqua.model package aqua.model
import aqua.model.func.body.FuncOp import aqua.model.func.raw.FuncOp
import cats.kernel.Semigroup import cats.kernel.Semigroup
trait Model trait Model

View File

@ -18,10 +18,18 @@ object ValueModel {
implicit object ValueModelEq extends Eq[ValueModel] { implicit object ValueModelEq extends Eq[ValueModel] {
override def eqv(x: ValueModel, y: ValueModel): Boolean = x == y 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 { case class LiteralModel(value: String, `type`: Type) extends ValueModel {
override def lastType: Type = `type` override def lastType: Type = `type`
override def toString: String = s"{$value: ${`type`}}"
} }
object LiteralModel { object LiteralModel {

View File

@ -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))
}

View File

@ -1,4 +1,4 @@
package aqua.model.topology package aqua.model.cursor
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
@ -24,17 +24,36 @@ case class ChainZipper[T](prev: Chain[T], current: T, next: Chain[T]) {
object ChainZipper { object ChainZipper {
def one[T](el: T): ChainZipper[T] = ChainZipper(Chain.empty, el, Chain.empty) 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]] = def first[T](chain: Chain[T]): Option[ChainZipper[T]] =
chain.uncons.fold(Chain.empty[ChainZipper[T]]) { case (t, next) => chain.uncons.map { case (current, next) =>
ChainZipper(prev, t, next) +: fromChain(next, prev :+ t) 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] f: ChainZipper[T] => Option[T]
): Chain[T] = ): Chain[T] =
chain.uncons.fold(Chain.empty[T]) { case (t, next) => chain.uncons.fold(Chain.empty[T]) { case (t, next) =>
f(ChainZipper(prev, t, next)).fold(fromChainMap(next, prev)(f))(r => f(ChainZipper(prev, t, next)).fold(traverseChainMap(next, prev)(f))(r =>
r +: fromChainMap(next, prev :+ r)(f) r +: traverseChainMap(next, prev :+ r)(f)
) )
} }

View File

@ -16,6 +16,9 @@ case class Call(args: List[ValueModel], exportTo: Option[Call.Export]) {
def argVarNames: Set[String] = args.collect { case VarModel(name, _, _) => def argVarNames: Set[String] = args.collect { case VarModel(name, _, _) =>
name name
}.toSet }.toSet
override def toString: String =
s"[${args.mkString(" ")}]${exportTo.map(_.model).map(" " + _).getOrElse("")}"
} }
object Call { object Call {

View File

@ -1,6 +1,6 @@
package aqua.model.func 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.model.{Model, ValueModel, VarModel}
import aqua.types.{ArrowType, Type} import aqua.types.{ArrowType, Type}
import cats.Eval import cats.Eval
@ -86,7 +86,7 @@ case class FuncCallable(
( (
noNames, noNames,
resolvedExports + (assignTo -> value.resolveWith(resolvedExports)) resolvedExports + (assignTo -> value.resolveWith(resolvedExports))
) -> Cofree[Chain, OpTag]( ) -> Cofree[Chain, RawTag](
tag.mapValues(_.resolveWith(resolvedExports)), tag.mapValues(_.resolveWith(resolvedExports)),
Eval.now(Chain.empty) Eval.now(Chain.empty)
) )
@ -119,7 +119,7 @@ case class FuncCallable(
} }
// All the other tags are already resolved and need no substitution // All the other tags are already resolved and need no substitution
acc -> Cofree[Chain, OpTag]( acc -> Cofree[Chain, RawTag](
tag.mapValues(_.resolveWith(resolvedExports)), tag.mapValues(_.resolveWith(resolvedExports)),
Eval.now(Chain.empty) Eval.now(Chain.empty)
) )

View File

@ -1,6 +1,6 @@
package aqua.model.func package aqua.model.func
import aqua.model.func.body.FuncOp import aqua.model.func.raw.FuncOp
import aqua.model.{Model, ValueModel} import aqua.model.{Model, ValueModel}
import aqua.types.Type import aqua.types.Type
@ -15,6 +15,6 @@ case class FuncModel(
arrows: Map[String, FuncCallable], arrows: Map[String, FuncCallable],
constants: Map[String, ValueModel] constants: Map[String, ValueModel]
): FuncCallable = ): FuncCallable =
FuncCallable(name, body, args, ret, arrows, constants) FuncCallable(name, body.fixXorPar, args, ret, arrows, constants)
} }

View File

@ -1,4 +1,4 @@
package aqua.model.func.body package aqua.model.func.raw
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.{Model, ValueModel, VarModel} import aqua.model.{Model, ValueModel, VarModel}
@ -9,8 +9,8 @@ import cats.kernel.Semigroup
import cats.syntax.apply._ import cats.syntax.apply._
import cats.syntax.functor._ import cats.syntax.functor._
case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { case class FuncOp(tree: Cofree[Chain, RawTag]) extends Model {
def head: OpTag = tree.head def head: RawTag = tree.head
lazy val isRightAssoc: Boolean = head match { lazy val isRightAssoc: Boolean = head match {
case XorTag | ParTag => true case XorTag | ParTag => true
@ -18,13 +18,13 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
case _ => false 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) Cofree.cata(tree)(folder)
def definesVarNames: Eval[Set[String]] = cata[Set[String]] { def definesVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, Call(_, Some(export))), acc) => case (CallArrowTag(_, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) 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))(_ ++ _)) Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) case (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) 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]] { def exportsVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, Call(_, Some(export))), acc) => case (CallArrowTag(_, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _)) 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))(_ ++ _)) Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) 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]] { def usesVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, call), acc) => case (CallArrowTag(_, call), acc) =>
Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _)) Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _))
case (CallServiceTag(_, _, call, _), acc) => case (CallServiceTag(_, _, call), acc) =>
Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _)) 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])(_ ++ _)) case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
} }
def resolveValues(vals: Map[String, ValueModel]): FuncOp = 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 = def rename(vals: Map[String, String]): FuncOp =
FuncOp( FuncOp(
tree.map[OpTag](op => tree.map[RawTag](op =>
op.mapValues { op.mapValues {
case v: VarModel if vals.contains(v.name) => v.copy(name = vals(v.name)) case v: VarModel if vals.contains(v.name) => v.copy(name = vals(v.name))
case v => v case v => v
@ -68,13 +73,29 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
def :+:(prev: FuncOp): FuncOp = def :+:(prev: FuncOp): FuncOp =
FuncOp.RightAssocSemi.combine(prev, this) 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 { object FuncOp {
type Tree = Cofree[Chain, OpTag] type Tree = Cofree[Chain, RawTag]
def traverseA[A](cf: Tree, init: A)( def traverseA[A](cf: Tree, init: A)(
f: (A, OpTag) => (A, Tree) f: (A, RawTag) => (A, Tree)
): Eval[(A, Tree)] = { ): Eval[(A, Tree)] = {
val (headA, head) = f(init, cf.head) val (headA, head) = f(init, cf.head)
// TODO: it should be in standard library, with some other types // 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(_ ++ _))) FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _)))
case (XorTag.LeftBiased, XorTag) => case (XorTag.LeftBiased, XorTag) =>
wrap(SeqTag, FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _)))) 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) => case (_, ParTag | XorTag) =>
wrap(SeqTag, FuncOp(y.tree.copy(tail = y.tree.tail.map(_.prepend(x.tree))))) wrap(SeqTag, FuncOp(y.tree.copy(tail = y.tree.tail.map(_.prepend(x.tree)))))
case (_, XorParTag(xor, par)) => 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 = def node(tag: RawTag, children: Chain[FuncOp]): FuncOp =
FuncOp(Cofree[Chain, OpTag](tag, Eval.later(children.map(_.tree)))) FuncOp(Cofree[Chain, RawTag](tag, Eval.later(children.map(_.tree))))
} }

View File

@ -1,4 +1,4 @@
package aqua.model.func.body package aqua.model.func.raw
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.{LiteralModel, ValueModel} import aqua.model.{LiteralModel, ValueModel}
@ -7,12 +7,12 @@ import cats.free.Cofree
object FuncOps { object FuncOps {
def noop(peerId: ValueModel): FuncOp = def noop: FuncOp =
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None), Some(peerId))) FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None)))
def identity(what: ValueModel, to: Call.Export): FuncOp = def identity(what: ValueModel, to: Call.Export): FuncOp =
FuncOp.leaf( 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 = def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
@ -51,6 +51,19 @@ object FuncOps {
.map(FuncOp(_)) .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 = def xor(left: FuncOp, right: FuncOp): FuncOp =
FuncOp.node(XorTag, Chain(left, right)) FuncOp.node(XorTag, Chain(left, right))
@ -62,16 +75,4 @@ object FuncOps {
def next(item: String): FuncOp = def next(item: String): FuncOp =
FuncOp.leaf(NextTag(item)) 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
)
)
} }

View File

@ -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
}
}

View File

@ -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("(|)")}"
}

View File

@ -1,12 +1,12 @@
package aqua.model.func.body package aqua.model.func.raw
import aqua.model.ValueModel import aqua.model.ValueModel
import aqua.model.func.Call import aqua.model.func.Call
import cats.data.Chain 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 OnTag(peerId, via) => OnTag(f(peerId), via.map(f))
case MatchMismatchTag(left, right, shouldMatch) => case MatchMismatchTag(left, right, shouldMatch) =>
MatchMismatchTag(f(left), f(right), shouldMatch) MatchMismatchTag(f(left), f(right), shouldMatch)
@ -16,12 +16,11 @@ sealed trait OpTag {
funcName, funcName,
call.mapValues(f) call.mapValues(f)
) )
case CallServiceTag(serviceId, funcName, call, pid) => case CallServiceTag(serviceId, funcName, call) =>
CallServiceTag( CallServiceTag(
f(serviceId), f(serviceId),
funcName, funcName,
call.mapValues(f), call.mapValues(f)
pid.map(f)
) )
case AssignmentTag(value, assignTo) => case AssignmentTag(value, assignTo) =>
AssignmentTag(f(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 sealed trait SeqGroupTag extends GroupTag
case object SeqTag extends SeqGroupTag case object SeqTag extends SeqGroupTag
case object ParTag extends GroupTag case object ParTag extends GroupTag
case object XorTag extends GroupTag { case object XorTag extends SeqGroupTag {
case object LeftBiased extends GroupTag case object LeftBiased extends GroupTag
} }
case class XorParTag(xor: FuncOp, par: FuncOp) extends OpTag case class XorParTag(xor: FuncOp, par: FuncOp) extends RawTag
case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupTag
case class NextTag(item: String) extends OpTag 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) case class MatchMismatchTag(left: ValueModel, right: ValueModel, shouldMatch: Boolean)
extends SeqGroupTag extends SeqGroupTag
case class ForTag(item: String, iterable: ValueModel) 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( case class CallArrowTag(
funcName: String, funcName: String,
call: Call call: Call
) extends OpTag ) extends RawTag
case class AssignmentTag( case class AssignmentTag(
value: ValueModel, value: ValueModel,
assignTo: String assignTo: String
) extends NoAirTag ) extends NoExecTag
case class AbilityIdTag( case class AbilityIdTag(
value: ValueModel, value: ValueModel,
service: String service: String
) extends NoAirTag ) extends NoExecTag
case class CallServiceTag( case class CallServiceTag(
serviceId: ValueModel, serviceId: ValueModel,
funcName: String, funcName: String,
call: Call, call: Call
peerId: Option[ValueModel] = None ) extends RawTag {
) extends OpTag override def toString: String = s"(call _ ($serviceId $funcName) $call)"
}

View File

@ -1,4 +1,4 @@
package aqua.model.func.body package aqua.model.func.raw
import cats.Show import cats.Show
import cats.free.Cofree import cats.free.Cofree

View File

@ -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))
}

View File

@ -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)"
}

View File

@ -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()))
}
}

View File

@ -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
}
}
}
}

View File

@ -1,225 +1,129 @@
package aqua.model.topology package aqua.model.topology
import aqua.model.{ValueModel, VarModel} import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.func.body._ import aqua.model.func.raw._
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
import cats.data.Chain.{:==, ==:, nil} import cats.data.Chain.nil
import cats.free.Cofree import cats.free.Cofree
import ChainZipper.Matchers._ import aqua.model.cursor.ChainZipper
import Location.Matchers._ import aqua.model.func.resolved._
import aqua.types.{BoxType, ScalarType} import aqua.types.{BoxType, ScalarType}
import wvlet.log.LogSupport import wvlet.log.LogSupport
import cats.syntax.traverse._
import scala.annotation.tailrec
object Topology extends LogSupport { 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 Cofree
.cata[Chain, OpTag, Tree](resolveOnMoves(op)) { .cata[Chain, ResolvedOp, Res](resolveOnMoves(op).value) {
case (SeqTag | _: OnTag | MetaTag(false, _, SeqTag | _: OnTag), children) => case (SeqRes, children) =>
Eval.later( Eval.later(
Cofree( children.uncons
SeqTag, .filter(_._2.isEmpty)
Eval.now(children.flatMap { .map(_._1)
case Cofree(SeqTag, ch) => ch.value .getOrElse(
case cf => Chain.one(cf) 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))) case (head, children) => Eval.later(Cofree(head, Eval.now(children)))
} }
.value .value
def resolveOnMoves(op: Tree): Tree = def wrap(cz: ChainZipper[Res]): Chain[Res] =
Cursor Chain.one(
.transform(op)(transformWalker) if (cz.prev.nonEmpty || cz.next.nonEmpty) Cofree(SeqRes, Eval.now(cz.chain))
.getOrElse(op) else cz.current
@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
}
) )
// Need to get from there def resolveOnMoves(op: Tree): Eval[Res] =
val prevPeerId = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
Chain.fromOption(prevOn.lastOption.map(_.peerId) orElse c.loc.firstOn.map(_.peerId)) .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) .map(NonEmptyChain.fromChain(_).map(_.uncons))
skipSuffix(noPrefix, suffix, noPrefix) .map {
} case None =>
error("Topology emitted nothing")
def findPath( Cofree(SeqRes, MakeRes.nilTail)
fromOn: Chain[OnTag], case Some((el, `nil`)) => el
toOn: Chain[OnTag], case Some((el, tail)) =>
fromPeer: Chain[ValueModel], warn("Topology emitted many nodes, that's unusual")
toPeer: Chain[ValueModel] Cofree(SeqRes, Eval.now(el +: tail))
): 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
}
// Walks through peer IDs, doing a noop function on each // Walks through peer IDs, doing a noop function on each
// If same IDs are found in a row, does noop only once // 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 // 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 => peerIds.map { v =>
v.lastType match { v.lastType match {
case _: BoxType => case _: BoxType =>
val itemName = "-via-peer-" val itemName = "-via-peer-"
FuncOps.meta( MakeRes.fold(
FuncOps.fold( itemName,
itemName, v,
v, if (reversed)
if (reversed) MakeRes.seq(
FuncOps.seq( MakeRes.next(itemName),
FuncOps.next(itemName), MakeRes.noop(VarModel(itemName, ScalarType.string))
FuncOps.noop(VarModel(itemName, ScalarType.string)) )
) else
else MakeRes.seq(
FuncOps.seq( MakeRes.noop(VarModel(itemName, ScalarType.string)),
FuncOps.noop(VarModel(itemName, ScalarType.string)), MakeRes.next(itemName)
FuncOps.next(itemName) )
)
),
skipTopology = true
) )
case _ => 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
}
} }

View File

@ -2,8 +2,8 @@ package aqua.model.transform
import aqua.model.{ValueModel, VarModel} import aqua.model.{ValueModel, VarModel}
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.func.body.{FuncOp, FuncOps} import aqua.model.func.raw.{FuncOp, FuncOps}
import aqua.types.{ArrayType, DataType, OptionType, StreamType, Type} import aqua.types.{ArrayType, DataType, StreamType}
import cats.data.Chain import cats.data.Chain
trait ArgsProvider { trait ArgsProvider {
@ -26,7 +26,6 @@ case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataT
item, item,
VarModel(iter, ArrayType(t.element), Chain.empty), VarModel(iter, ArrayType(t.element), Chain.empty),
FuncOps.seq( 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.identity(VarModel(item, t.element), Call.Export(name, t)),
FuncOps.next(item) FuncOps.next(item)
) )

View File

@ -2,7 +2,7 @@ package aqua.model.transform
import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.func.Call 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 aqua.types.LiteralType
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
@ -19,7 +19,7 @@ case class ErrorsCatcher(
if (enabled) { if (enabled) {
var i = 0 var i = 0
op op
.cata[Cofree[Chain, OpTag]] { .cata[Cofree[Chain, RawTag]] {
case (tag, children) case (tag, children)
if children.length == 1 && children.headOption.exists( if children.length == 1 && children.headOption.exists(
_.head == XorTag.LeftBiased _.head == XorTag.LeftBiased

View File

@ -2,7 +2,7 @@ package aqua.model.transform
import aqua.model.{LiteralModel, ValueModel} import aqua.model.{LiteralModel, ValueModel}
import aqua.model.func.Call import aqua.model.func.Call
import aqua.model.func.body.{FuncOp, FuncOps} import aqua.model.func.raw.{FuncOp, FuncOps}
import cats.data.Chain import cats.data.Chain
sealed trait InitPeerCallable { sealed trait InitPeerCallable {

View File

@ -2,7 +2,7 @@ package aqua.model.transform
import aqua.model.{ValueModel, VarModel} import aqua.model.{ValueModel, VarModel}
import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable} 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 aqua.types.ArrowType
import cats.Eval import cats.Eval
import cats.syntax.apply._ import cats.syntax.apply._

View File

@ -1,27 +1,28 @@
package aqua.model.transform package aqua.model.transform
import aqua.model.func.body._
import aqua.model.func.FuncCallable import aqua.model.func.FuncCallable
import aqua.model.VarModel import aqua.model.VarModel
import aqua.model.func.resolved.{NoAir, ResolvedOp}
import aqua.model.topology.Topology import aqua.model.topology.Topology
import aqua.types.ScalarType import aqua.types.ScalarType
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
import wvlet.log.LogSupport
object Transform { object Transform extends LogSupport {
def defaultFilter(t: OpTag): Boolean = t match { def defaultFilter(t: ResolvedOp): Boolean = t match {
case _: NoAirTag => false case _: NoAir => false
case _ => true case _ => true
} }
def clear( def clear(
tree: Cofree[Chain, OpTag], tree: Cofree[Chain, ResolvedOp],
filter: OpTag => Boolean = defaultFilter filter: ResolvedOp => Boolean = defaultFilter
): Cofree[Chain, OpTag] = ): Cofree[Chain, ResolvedOp] =
tree.copy(tail = tree.tail.map(_.filter(t => filter(t.head)).map(clear(_, filter)))) 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( val initCallable: InitPeerCallable = InitViaRelayCallable(
Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string)) Chain.fromOption(conf.relayVarName).map(VarModel(_, ScalarType.string))
) )

View File

@ -130,8 +130,8 @@ object Expr {
case block if block.isBlock => case block if block.isBlock =>
acc.copy(block = Some(indent -> currentExpr)) acc.copy(block = Some(indent -> currentExpr))
// create leaf if token is on current level // create leaf if token is on current level
case e => case _ =>
acc.copy(currentChildren = acc.currentChildren.append(leaf(e))) acc.copy(currentChildren = acc.currentChildren.append(currentExpr))
} }
// if we have root companion, gather all expressions that have indent > than current // if we have root companion, gather all expressions that have indent > than current
case r @ Some((_, block)) => case r @ Some((_, block)) =>
@ -163,10 +163,10 @@ object Expr {
window = Chain.empty window = Chain.empty
) )
// create leaf if token is on current level // create leaf if token is on current level
case e => case _ =>
acc.copy( acc.copy(
block = None, block = None,
currentChildren = withTree.append(leaf(e)), currentChildren = withTree.append(currentExpr),
window = Chain.empty window = Chain.empty
) )
} }

View File

@ -267,4 +267,14 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
qGenComplex.d() shouldBe elseCall qGenComplex.d() shouldBe elseCall
qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None)*/ 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
}
} }

View File

@ -1,7 +1,7 @@
package aqua.semantics package aqua.semantics
import aqua.model.{AquaContext, Model, ScriptModel} 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.lexer.Token
import aqua.parser.{Ast, Expr} import aqua.parser.{Ast, Expr}
import aqua.semantics.rules.ReportError import aqua.semantics.rules.ReportError
@ -23,8 +23,9 @@ import monocle.Lens
import monocle.macros.GenLens import monocle.macros.GenLens
import cats.syntax.apply._ import cats.syntax.apply._
import cats.syntax.semigroup._ import cats.syntax.semigroup._
import wvlet.log.LogSupport
object Semantics { object Semantics extends LogSupport {
def folder[F[_], G[_]](implicit def folder[F[_], G[_]](implicit
A: AbilitiesAlgebra[F, G], A: AbilitiesAlgebra[F, G],
@ -91,7 +92,7 @@ object Semantics {
.run(CompilerState.init[F](init)) .run(CompilerState.init[F](init))
.map { .map {
case (state, gen: ScriptModel) => case (state, gen: ScriptModel) =>
val ctx = AquaContext.fromScriptModel(gen, aqum.empty) val ctx = AquaContext.fromScriptModel(gen, init)
NonEmptyChain NonEmptyChain
.fromChain(state.errors) .fromChain(state.errors)
.fold[ValidatedNec[SemanticError[F], AquaContext]](Valid(ctx))(Invalid(_)) .fold[ValidatedNec[SemanticError[F], AquaContext]](Valid(ctx))(Invalid(_))

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.Model 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.parser.expr.AbilityIdExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.Model 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.parser.expr.{AbilityIdExpr, AssignmentExpr}
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.func.Call 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.model.{Model, ValueModel}
import aqua.parser.expr.CallArrowExpr import aqua.parser.expr.CallArrowExpr
import aqua.semantics.Prog import aqua.semantics.Prog

View File

@ -1,6 +1,6 @@
package aqua.semantics.expr 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.model.{Model, VarModel}
import aqua.parser.expr.CatchExpr import aqua.parser.expr.CatchExpr
import aqua.semantics.Prog import aqua.semantics.Prog

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.Model 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.parser.expr.ElseOtherwiseExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.{Model, ValueModel} import aqua.model.{Model, ValueModel}
import aqua.model.func.body._ import aqua.model.func.raw._
import aqua.parser.expr.ForExpr import aqua.parser.expr.ForExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
@ -41,7 +41,7 @@ class ForSem[F[_]](val expr: ForExpr[F]) extends AnyVal {
FuncOp.wrap( FuncOp.wrap(
ForTag(expr.item.value, vm), ForTag(expr.item.value, vm),
FuncOp.node( FuncOp.node(
expr.mode.map(_._2).fold[OpTag](SeqTag) { expr.mode.map(_._2).fold[RawTag](SeqTag) {
case ForExpr.ParMode => ParTag case ForExpr.ParMode => ParTag
case ForExpr.TryMode => XorTag case ForExpr.TryMode => XorTag
}, },

View File

@ -1,6 +1,6 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.func.body.FuncOp import aqua.model.func.raw.FuncOp
import aqua.model.{Model, ValueModel} import aqua.model.{Model, ValueModel}
import aqua.model.func.{ArgDef, ArgsDef, FuncModel} import aqua.model.func.{ArgDef, ArgsDef, FuncModel}
import aqua.parser.expr.FuncExpr import aqua.parser.expr.FuncExpr

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.{Model, ValueModel} 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.parser.expr.IfExpr
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.{Model, ValueModel} 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.parser.expr.OnExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.Model 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.parser.expr.ParExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import cats.free.Free import cats.free.Free

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.model.Model 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.parser.expr.TryExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra

View File

@ -2,6 +2,7 @@ package aqua.semantics
import aqua.Node import aqua.Node
import aqua.Node._ import aqua.Node._
import aqua.model.func.raw.{FuncOp, FuncOps, SeqTag}
import aqua.model.transform._ import aqua.model.transform._
import aqua.model.{AquaContext, LiteralModel} import aqua.model.{AquaContext, LiteralModel}
import aqua.parser.Ast import aqua.parser.Ast
@ -13,7 +14,7 @@ import org.scalatest.matchers.should.Matchers
class SemanticsSpec extends AnyFlatSpec with Matchers { class SemanticsSpec extends AnyFlatSpec with Matchers {
// use it to fix https://github.com/fluencelabs/aqua/issues/90 // 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 implicit val fileLift: LiftParser[Span.F] = Span.spanLiftParser
val script = val script =
@ -37,15 +38,16 @@ class SemanticsSpec extends AnyFlatSpec with Matchers {
val proc = Node.cofToNode(func.body.tree) val proc = Node.cofToNode(func.body.tree)
val expected = val expected: Node.Raw =
seq( FuncOp.wrap(
par( SeqTag,
on(LiteralModel("\"other-peer\"", LiteralType.string), Nil, callLiteral(1)), FuncOps.par(
callLiteral(1) on(LiteralModel("\"other-peer\"", LiteralType.string), Nil, callLiteralRaw(1)),
callLiteralRaw(1)
) )
) )
// proc.equalsOrPrintDiff(expected) should be(true) proc.equalsOrPrintDiff(expected) should be(true)
} }
} }

View File

@ -1,7 +1,8 @@
package aqua package aqua
import aqua.model.func.Call 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.transform.{BodyConfig, ErrorsCatcher}
import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.types.{ArrayType, LiteralType, ScalarType} import aqua.types.{ArrayType, LiteralType, ScalarType}
@ -12,15 +13,143 @@ import cats.free.Cofree
import scala.language.implicitConversions import scala.language.implicitConversions
// Helper to simplify building and visualizing Cofree structures // 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 = 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) def equalsOrPrintDiff[TT](other: Node[TT]): Boolean =
Console.GREEN + left + Console.RESET if (this == other) true
else else {
Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right) 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 = private def diffArg(left: ValueModel, right: ValueModel): String =
Console.GREEN + "(" + Console.GREEN + "(" +
@ -36,129 +165,53 @@ case class Node(tag: OpTag, ops: List[Node] = Nil) {
.mkString("::") + Console.GREEN + ", " + .mkString("::") + Console.GREEN + ", " +
equalOrNot(left.exportTo, right.exportTo) + Console.GREEN + ")" equalOrNot(left.exportTo, right.exportTo) + Console.GREEN + ")"
private def diffServiceCall(left: CallServiceTag, right: CallServiceTag): String = private def diffServiceCall(left: CallServiceRes, right: CallServiceRes): String =
Console.GREEN + "CallServiceTag(" + Console.GREEN + "(call" +
equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + ", " + equalOrNot(left.peerId, right.peerId) + Console.GREEN + " (" +
equalOrNot(left.funcName, right.funcName) + Console.GREEN + ", " + equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + " " +
diffCall(left.call, right.call) + Console.GREEN + ", " + equalOrNot(left.funcName, right.funcName) + Console.GREEN + ") " +
equalOrNot(left.peerId, right.peerId) + diffCall(left.call, right.call) + Console.GREEN +
Console.GREEN + ")" + Console.RESET Console.GREEN + ")" + Console.RESET
private def diffTags(left: OpTag, right: OpTag): String = (left, right) match { private def diffRes(left: ResolvedOp, right: ResolvedOp): String = (left, right) match {
case (l: CallServiceTag, r: CallServiceTag) => diffServiceCall(l, r) case (l: CallServiceRes, r: CallServiceRes) => diffServiceCall(l, r)
case _ => case _ =>
Console.BLUE + s" $left ${Console.RED}\n != ${Console.YELLOW}${right}${Console.RED}" Console.BLUE + s" $left ${Console.RED}\n != ${Console.YELLOW}${right}${Console.RED}"
} }
def diffToString(other: Node): String = def diffToString(current: Node.Res, other: Node.Res): String =
(if (tag == other.tag) Console.GREEN + tag (if (current.label == other.label) Console.GREEN + current.label
else diffTags(tag, other.tag)) + (if (ops.isEmpty && other.ops.isEmpty) "\n" else
else diffRes(current.label, other.label)) + (if (
"{\n") + Console.RESET + current.children.isEmpty && other.children.isEmpty
(if (ops.length != other.ops.length) ) "\n"
Console.RED + s"number of ops: ${ops.length} != ${other.ops.length}\n" + Console.RESET 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 "") + else "") +
ops current.children
.zip(other.ops) .map(Option(_))
.map { case (a, b) => .zipAll(other.children.map(Option(_)), None, None)
a.diffToString(b) .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 else
((if (tag == other.tag) Console.GREEN ((if (current.label == other.label) Console.GREEN
else Console.RED) + "}\n" + Console.RESET)) else Console.RED) + "}\n" + Console.RESET))
def equalsOrPrintDiff(other: Node): Boolean = def equalsOrPrintDiff(current: Node.Res, other: Node.Res): Boolean =
if (this == other) true if (current == other) true
else { else {
println(diffToString(other)) println(diffToString(current, other))
false 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
}

View File

@ -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)
}
}

View File

@ -1,6 +1,14 @@
package aqua.model.topology package aqua.model.topology
import aqua.Node 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.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers 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 { "topology resolver" should "do nothing on init peer" in {
val init = on( val init: Node.Raw = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
seq( FuncOps.seq(
call(1), callTag(1),
call(2) callTag(2)
) )
) )
val proc: Node = Topology.resolve(init) val proc: Node.Res = Topology.resolve(init)
val expected = val expected: Node.Res =
seq( MakeRes.seq(
call(1, initPeer), callRes(1, initPeer),
call(2, initPeer) callRes(2, initPeer)
) )
proc should be(expected) 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 { "topology resolver" should "go through relay to any other node, directly" in {
val init = on( val init: Node.Raw = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
on( on(
otherPeer, otherPeer,
Nil, Nil,
seq( FuncOps.seq(
call(1), callTag(1),
call(2) callTag(2)
) )
) )
) )
val proc: Node = Topology.resolve(init) val proc: Node.Res = Topology.resolve(init)
val expected = val expected: Node.Res =
seq(through(relay), call(1, otherPeer), call(2, otherPeer)) MakeRes.seq(through(relay), callRes(1, otherPeer), callRes(2, otherPeer))
proc should be(expected) proc should be(expected)
} }
@ -61,21 +69,139 @@ class TopologySpec extends AnyFlatSpec with Matchers {
on( on(
otherPeer, otherPeer,
otherRelay :: Nil, otherRelay :: Nil,
seq( FuncOps.seq(
call(1), callTag(1),
call(2) callTag(2)
) )
) )
) )
val proc: Node = Topology.resolve(init) val proc: Node.Res = Topology.resolve(init)
val expected = val expected: Node.Res =
seq( MakeRes.seq(
through(relay), through(relay),
through(otherRelay), through(otherRelay),
call(1, otherPeer), callRes(1, otherPeer),
call(2, 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) proc.equalsOrPrintDiff(expected) should be(true)
@ -89,28 +215,28 @@ class TopologySpec extends AnyFlatSpec with Matchers {
on( on(
otherPeer, otherPeer,
otherRelay :: Nil, otherRelay :: Nil,
xor( FuncOps.xor(
seq( FuncOps.seq(
call(1), callTag(1),
call(2) callTag(2)
), ),
call(3) callTag(3)
) )
) )
) )
val proc: Node = Topology.resolve(init) val proc: Node.Res = Topology.resolve(init)
val expected = val expected: Node.Res =
seq( MakeRes.seq(
through(relay), through(relay),
through(otherRelay), through(otherRelay),
xor( MakeRes.xor(
seq( MakeRes.seq(
call(1, otherPeer), callRes(1, otherPeer),
call(2, 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 { "topology resolver" should "simplify a route with init_peer_id" in {
val init = on( val init: Node.Raw = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
seq( FuncOps.seq(
on( on(
initPeer, initPeer,
relay :: Nil, 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 = val expected =
seq( MakeRes.seq(
call(1, initPeer), callRes(1, initPeer),
call(2, initPeer) callRes(2, initPeer)
) )
proc.equalsOrPrintDiff(expected) should be(true) 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 { "topology resolver" should "get back to init peer" in {
val init = on( val init: Node.Raw = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
seq( FuncOps.seq(
on( on(
otherPeer, otherPeer,
otherRelay :: Nil, 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 = val expected: Node.Res =
seq( MakeRes.seq(
through(relay), through(relay),
through(otherRelay), through(otherRelay),
call(1, otherPeer), callRes(1, otherPeer),
through(otherRelay), through(otherRelay),
through(relay), through(relay),
call(2, initPeer) callRes(2, initPeer)
) )
// println(Console.BLUE + init) // println(Console.BLUE + init)
@ -197,19 +323,19 @@ class TopologySpec extends AnyFlatSpec with Matchers {
val init = on( val init = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
seq( FuncOps.seq(
call(1), callTag(1),
call(2), callTag(2),
call(3), callTag(3),
on( on(
varNode, varNode,
viaList :: Nil, viaList :: Nil,
call(4) callTag(4)
), ),
on( on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
call(5) callTag(5)
) )
) )
) )
@ -222,50 +348,50 @@ class TopologySpec extends AnyFlatSpec with Matchers {
val init = on( val init = on(
initPeer, initPeer,
relay :: Nil, relay :: Nil,
seq( FuncOps.seq(
on( on(
otherPeer, otherPeer,
otherRelay :: Nil, otherRelay :: Nil,
call(0), callTag(0),
on( on(
otherPeer2, otherPeer2,
otherRelay :: Nil, otherRelay :: Nil,
call(1), callTag(1),
_match( matchRaw(
otherPeer, otherPeer,
otherRelay, otherRelay,
on( on(
otherPeer, otherPeer,
otherRelay :: Nil, 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 = val expected: Node.Res =
seq( MakeRes.seq(
through(relay), through(relay),
through(otherRelay), through(otherRelay),
call(0, otherPeer), callRes(0, otherPeer),
through(otherRelay), through(otherRelay),
call(1, otherPeer2), callRes(1, otherPeer2),
_match( matchRes(
otherPeer, otherPeer,
otherRelay, otherRelay,
seq( MakeRes.seq(
through(otherRelay), through(otherRelay),
call(2, otherPeer) callRes(2, otherPeer)
) )
), ),
through(otherRelay), through(otherRelay),
through(relay), through(relay),
call(3, initPeer) callRes(3, initPeer)
) )
// println(Console.BLUE + init) // println(Console.BLUE + init)
@ -276,4 +402,63 @@ class TopologySpec extends AnyFlatSpec with Matchers {
proc.equalsOrPrintDiff(expected) should be(true) 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)
}
} }

View File

@ -1,7 +1,8 @@
package aqua.model.transform package aqua.model.transform
import aqua.Node 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.func.{ArgsDef, Call, FuncCallable}
import aqua.model.{LiteralModel, VarModel} import aqua.model.{LiteralModel, VarModel}
import aqua.types.ScalarType import aqua.types.ScalarType
@ -11,14 +12,14 @@ import org.scalatest.matchers.should.Matchers
class TransformSpec extends AnyFlatSpec with Matchers { class TransformSpec extends AnyFlatSpec with Matchers {
import Node._ 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 ret = LiteralModel.quote("return this")
val func: FuncCallable = val func: FuncCallable =
FuncCallable( FuncCallable(
"ret", "ret",
FuncOp(on(otherPeer, otherRelay :: Nil, call(1))), on(otherPeer, otherRelay :: Nil, callTag(1)),
ArgsDef.empty, ArgsDef.empty,
Some((ret, ScalarType.string)), Some((ret, ScalarType.string)),
Map.empty, Map.empty,
@ -29,17 +30,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val fc = Transform.forClient(func, bc) val fc = Transform.forClient(func, bc)
val procFC: Node = fc val procFC: Node.Res = fc
val expectedFC = seq( val expectedFC: Node.Res =
xor( MakeRes.xor(
seq( MakeRes.seq(
dataCall(bc, "-relay-", initPeer), dataCall(bc, "-relay-", initPeer),
through(relayV), through(relayV),
through(otherRelay), through(otherRelay),
xor( MakeRes.xor(
call(1, otherPeer), callRes(1, otherPeer),
seq( MakeRes.seq(
through(otherRelay), through(otherRelay),
through(relayV), through(relayV),
errorCall(bc, 1, initPeer), errorCall(bc, 1, initPeer),
@ -48,22 +49,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
), ),
through(otherRelay), through(otherRelay),
through(relayV), through(relayV),
xor( MakeRes.xor(
respCall(bc, ret, initPeer), 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( val func: FuncCallable = FuncCallable(
"ret", "ret",
FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))), FuncOps.seq(callTag(0), on(otherPeer, Nil, callTag(1))),
ArgsDef.empty, ArgsDef.empty,
Some((ret, ScalarType.string)), Some((ret, ScalarType.string)),
Map.empty, Map.empty,
@ -84,14 +80,14 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val fc = Transform.forClient(func, bc) val fc = Transform.forClient(func, bc)
val procFC: Node = fc val procFC: Res = fc
val expectedFC = val expectedFC: Res =
seq( MakeRes.seq(
dataCall(bc, "-relay-", initPeer), dataCall(bc, "-relay-", initPeer),
call(0, initPeer), callRes(0, initPeer),
through(relayV), through(relayV),
call(1, otherPeer), callRes(1, otherPeer),
through(relayV), through(relayV),
respCall(bc, ret, initPeer) respCall(bc, ret, initPeer)
) )
@ -119,10 +115,9 @@ class TransformSpec extends AnyFlatSpec with Matchers {
CallServiceTag( CallServiceTag(
LiteralModel.quote("srv1"), LiteralModel.quote("srv1"),
"foo", "foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))), Call(Nil, Some(Call.Export("v", ScalarType.string)))
None
) )
) ).cof
), ),
ArgsDef.empty, ArgsDef.empty,
Some((VarModel("v", ScalarType.string), ScalarType.string)), Some((VarModel("v", ScalarType.string), ScalarType.string)),
@ -134,7 +129,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
FuncCallable( FuncCallable(
"f2", "f2",
FuncOp( 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, ArgsDef.empty,
Some((VarModel("v", ScalarType.string), ScalarType.string)), Some((VarModel("v", ScalarType.string), ScalarType.string)),
@ -144,17 +139,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val bc = BodyConfig(wrapWithXor = false) val bc = BodyConfig(wrapWithXor = false)
val res = Transform.forClient(f2, bc): Node val res = Transform.forClient(f2, bc): Node.Res
res.equalsOrPrintDiff( res.equalsOrPrintDiff(
seq( MakeRes.seq(
dataCall(bc, "-relay-", initPeer), dataCall(bc, "-relay-", initPeer),
Node( Node(
CallServiceTag( CallServiceRes(
LiteralModel.quote("srv1"), LiteralModel.quote("srv1"),
"foo", "foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))), Call(Nil, Some(Call.Export("v", ScalarType.string))),
Some(initPeer) initPeer
) )
), ),
respCall(bc, VarModel("v", ScalarType.string), initPeer) respCall(bc, VarModel("v", ScalarType.string), initPeer)

View File

@ -16,7 +16,7 @@ sealed trait Type {
sealed trait DataType extends Type sealed trait DataType extends Type
case class ScalarType private (name: String) extends DataType { case class ScalarType private (name: String) extends DataType {
override def toString: String = s"ScalarType($name)" override def toString: String = name
} }
object ScalarType { object ScalarType {
@ -72,7 +72,7 @@ object ScalarType {
} }
case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType { 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 { object LiteralType {