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"
service OpH("op"):
puk(s: string) -> string
pek(s: string, -- trgtr
c: string) -> string
func a( -- ferkjn
b: string, -- fr
c: string, -- asdf
g: string
) -> string: -- rgtr
try:
f = "world"
OpH "planet"
OpH.pek("TRY THIS", -- gtrg
c)
catch err:
OpH.puk(err.msg)
<- f
import "print.aqua"
func iterateAndPrint(strings: []string):
for s <- strings:
print(s)

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

View File

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

View File

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

View File

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

View File

@ -18,10 +18,18 @@ object ValueModel {
implicit object ValueModelEq extends Eq[ValueModel] {
override def eqv(x: ValueModel, y: ValueModel): Boolean = x == y
}
def varName(vm: ValueModel): Option[String] =
vm match {
case VarModel(name, _, _) => Some(name)
case _ => None
}
}
case class LiteralModel(value: String, `type`: Type) extends ValueModel {
override def lastType: Type = `type`
override def toString: String = s"{$value: ${`type`}}"
}
object LiteralModel {

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.free.Cofree
@ -24,17 +24,36 @@ case class ChainZipper[T](prev: Chain[T], current: T, next: Chain[T]) {
object ChainZipper {
def one[T](el: T): ChainZipper[T] = ChainZipper(Chain.empty, el, Chain.empty)
def fromChain[T](chain: Chain[T], prev: Chain[T] = Chain.empty): Chain[ChainZipper[T]] =
chain.uncons.fold(Chain.empty[ChainZipper[T]]) { case (t, next) =>
ChainZipper(prev, t, next) +: fromChain(next, prev :+ t)
def first[T](chain: Chain[T]): Option[ChainZipper[T]] =
chain.uncons.map { case (current, next) =>
ChainZipper(Chain.empty, current, next)
}
def fromChainMap[T](chain: Chain[T], prev: Chain[T] = Chain.empty)(
def last[T](chain: Chain[T]): Option[ChainZipper[T]] =
chain.initLast.map { case (prev, current) =>
ChainZipper(prev, current, Chain.empty)
}
def traverseChain[T](chain: Chain[T], prev: Chain[T] = Chain.empty): Chain[ChainZipper[T]] =
chain.uncons.fold(Chain.empty[ChainZipper[T]]) { case (t, next) =>
ChainZipper(prev, t, next) +: traverseChain(next, prev :+ t)
}
/**
* Walks through the given chain as a zipper, applying the given function to it
*
* @param chain Chain to traverse and map
* @param prev Accumulator: previous steps
* @param f Function that converts a zipper (meaning an element with its horizontal context) to element
* @tparam T Type
* @return Resulting chain
*/
def traverseChainMap[T](chain: Chain[T], prev: Chain[T] = Chain.empty)(
f: ChainZipper[T] => Option[T]
): Chain[T] =
chain.uncons.fold(Chain.empty[T]) { case (t, next) =>
f(ChainZipper(prev, t, next)).fold(fromChainMap(next, prev)(f))(r =>
r +: fromChainMap(next, prev :+ r)(f)
f(ChainZipper(prev, t, next)).fold(traverseChainMap(next, prev)(f))(r =>
r +: traverseChainMap(next, prev :+ r)(f)
)
}

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, _, _) =>
name
}.toSet
override def toString: String =
s"[${args.mkString(" ")}]${exportTo.map(_.model).map(" " + _).getOrElse("")}"
}
object Call {

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package aqua.model.func.body
package aqua.model.func.raw
import aqua.model.func.Call
import aqua.model.{Model, ValueModel, VarModel}
@ -9,8 +9,8 @@ import cats.kernel.Semigroup
import cats.syntax.apply._
import cats.syntax.functor._
case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
def head: OpTag = tree.head
case class FuncOp(tree: Cofree[Chain, RawTag]) extends Model {
def head: RawTag = tree.head
lazy val isRightAssoc: Boolean = head match {
case XorTag | ParTag => true
@ -18,13 +18,13 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
case _ => false
}
def cata[T](folder: (OpTag, Chain[T]) => Eval[T]): Eval[T] =
def cata[T](folder: (RawTag, Chain[T]) => Eval[T]): Eval[T] =
Cofree.cata(tree)(folder)
def definesVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) =>
case (CallServiceTag(_, _, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
@ -33,25 +33,30 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
def exportsVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) =>
case (CallServiceTag(_, _, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
}
// TODO: as it is used for checking of intersection, make it a lazy traverse with fail-fast
def usesVarNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, call), acc) =>
Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _))
case (CallServiceTag(_, _, call, _), acc) =>
case (CallServiceTag(_, _, call), acc) =>
Eval.later(acc.foldLeft(call.argVarNames)(_ ++ _))
case (MatchMismatchTag(a, b, _), acc) =>
Eval.later(acc.foldLeft(ValueModel.varName(a).toSet ++ ValueModel.varName(b))(_ ++ _))
case (ForTag(_, VarModel(name, _, _)), acc) =>
Eval.later(acc.foldLeft(Set(name))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
}
def resolveValues(vals: Map[String, ValueModel]): FuncOp =
FuncOp(tree.map[OpTag](_.mapValues(_.resolveWith(vals))))
FuncOp(tree.map[RawTag](_.mapValues(_.resolveWith(vals))))
def rename(vals: Map[String, String]): FuncOp =
FuncOp(
tree.map[OpTag](op =>
tree.map[RawTag](op =>
op.mapValues {
case v: VarModel if vals.contains(v.name) => v.copy(name = vals(v.name))
case v => v
@ -68,13 +73,29 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
def :+:(prev: FuncOp): FuncOp =
FuncOp.RightAssocSemi.combine(prev, this)
// Function body must be fixed before function gets resolved
def fixXorPar: FuncOp =
FuncOp(cata[Cofree[Chain, RawTag]] {
case (XorParTag(left, right), _) =>
Eval.now(
FuncOps
.par(
FuncOp.wrap(XorTag, left),
right
)
.tree
)
case (head, tail) => Eval.now(Cofree(head, Eval.now(tail)))
}.value)
}
object FuncOp {
type Tree = Cofree[Chain, OpTag]
type Tree = Cofree[Chain, RawTag]
def traverseA[A](cf: Tree, init: A)(
f: (A, OpTag) => (A, Tree)
f: (A, RawTag) => (A, Tree)
): Eval[(A, Tree)] = {
val (headA, head) = f(init, cf.head)
// TODO: it should be in standard library, with some other types
@ -95,7 +116,7 @@ object FuncOp {
FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _)))
case (XorTag.LeftBiased, XorTag) =>
wrap(SeqTag, FuncOp(y.tree.copy(tail = (x.tree.tail, y.tree.tail).mapN(_ ++ _))))
case (XorTag, ParTag) => FuncOp(Cofree[Chain, OpTag](XorParTag(x, y), Eval.now(Chain.empty)))
case (XorTag, ParTag) => FuncOp(Cofree[Chain, RawTag](XorParTag(x, y), Eval.now(Chain.empty)))
case (_, ParTag | XorTag) =>
wrap(SeqTag, FuncOp(y.tree.copy(tail = y.tree.tail.map(_.prepend(x.tree)))))
case (_, XorParTag(xor, par)) =>
@ -116,10 +137,10 @@ object FuncOp {
}
}
def leaf(tag: OpTag): FuncOp = FuncOp(Cofree[Chain, OpTag](tag, Eval.now(Chain.empty)))
def leaf(tag: RawTag): FuncOp = FuncOp(Cofree[Chain, RawTag](tag, Eval.now(Chain.empty)))
def wrap(tag: OpTag, child: FuncOp): FuncOp = node(tag, Chain.one(child))
def wrap(tag: RawTag, child: FuncOp): FuncOp = node(tag, Chain.one(child))
def node(tag: OpTag, children: Chain[FuncOp]): FuncOp =
FuncOp(Cofree[Chain, OpTag](tag, Eval.later(children.map(_.tree))))
def node(tag: RawTag, children: Chain[FuncOp]): FuncOp =
FuncOp(Cofree[Chain, RawTag](tag, Eval.later(children.map(_.tree))))
}

View File

@ -1,4 +1,4 @@
package aqua.model.func.body
package aqua.model.func.raw
import aqua.model.func.Call
import aqua.model.{LiteralModel, ValueModel}
@ -7,12 +7,12 @@ import cats.free.Cofree
object FuncOps {
def noop(peerId: ValueModel): FuncOp =
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None), Some(peerId)))
def noop: FuncOp =
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None)))
def identity(what: ValueModel, to: Call.Export): FuncOp =
FuncOp.leaf(
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to)), None)
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to)))
)
def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
@ -51,6 +51,19 @@ object FuncOps {
.map(FuncOp(_))
)
def par(ops: FuncOp*): FuncOp =
if (ops.length == 1) ops.head
else
FuncOp.node(
ParTag,
Chain
.fromSeq(ops.flatMap {
case FuncOp(Cofree(ParTag, subOps)) => subOps.value.toList
case FuncOp(cof) => cof :: Nil
})
.map(FuncOp(_))
)
def xor(left: FuncOp, right: FuncOp): FuncOp =
FuncOp.node(XorTag, Chain(left, right))
@ -62,16 +75,4 @@ object FuncOps {
def next(item: String): FuncOp =
FuncOp.leaf(NextTag(item))
def meta(op: FuncOp, skipTopology: Boolean = false, comment: String = null): FuncOp =
FuncOp(
op.tree.copy(
MetaTag(
skipTopology = skipTopology,
comment = Option(comment),
op.head
),
op.tree.tail
)
)
}

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.func.Call
import cats.data.Chain
sealed trait OpTag {
sealed trait RawTag {
def mapValues(f: ValueModel => ValueModel): OpTag = this match {
def mapValues(f: ValueModel => ValueModel): RawTag = this match {
case OnTag(peerId, via) => OnTag(f(peerId), via.map(f))
case MatchMismatchTag(left, right, shouldMatch) =>
MatchMismatchTag(f(left), f(right), shouldMatch)
@ -16,12 +16,11 @@ sealed trait OpTag {
funcName,
call.mapValues(f)
)
case CallServiceTag(serviceId, funcName, call, pid) =>
case CallServiceTag(serviceId, funcName, call) =>
CallServiceTag(
f(serviceId),
funcName,
call.mapValues(f),
pid.map(f)
call.mapValues(f)
)
case AssignmentTag(value, assignTo) =>
AssignmentTag(f(value), assignTo)
@ -32,49 +31,49 @@ sealed trait OpTag {
}
sealed trait NoAirTag extends OpTag
sealed trait NoExecTag extends RawTag
sealed trait GroupTag extends OpTag
sealed trait GroupTag extends RawTag
sealed trait SeqGroupTag extends GroupTag
case object SeqTag extends SeqGroupTag
case object ParTag extends GroupTag
case object XorTag extends GroupTag {
case object XorTag extends SeqGroupTag {
case object LeftBiased extends GroupTag
}
case class XorParTag(xor: FuncOp, par: FuncOp) extends OpTag
case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupTag
case class NextTag(item: String) extends OpTag
case class XorParTag(xor: FuncOp, par: FuncOp) extends RawTag
case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupTag {
override def toString: String =
s"(on $peerId${if (via.nonEmpty) " via " + via.toList.mkString(" via ") else ""})"
}
case class NextTag(item: String) extends RawTag
case class MatchMismatchTag(left: ValueModel, right: ValueModel, shouldMatch: Boolean)
extends SeqGroupTag
case class ForTag(item: String, iterable: ValueModel) extends SeqGroupTag
case class MetaTag(
skipTopology: Boolean = false,
comment: Option[String] = None,
op: OpTag
) extends OpTag
case class CallArrowTag(
funcName: String,
call: Call
) extends OpTag
) extends RawTag
case class AssignmentTag(
value: ValueModel,
assignTo: String
) extends NoAirTag
) extends NoExecTag
case class AbilityIdTag(
value: ValueModel,
service: String
) extends NoAirTag
) extends NoExecTag
case class CallServiceTag(
serviceId: ValueModel,
funcName: String,
call: Call,
peerId: Option[ValueModel] = None
) extends OpTag
call: Call
) extends RawTag {
override def toString: String = s"(call _ ($serviceId $funcName) $call)"
}

View File

@ -1,4 +1,4 @@
package aqua.model.func.body
package aqua.model.func.raw
import cats.Show
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
import aqua.model.{ValueModel, VarModel}
import aqua.model.func.body._
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.func.raw._
import cats.Eval
import cats.data.Chain
import cats.data.Chain.{:==, ==:, nil}
import cats.data.{Chain, NonEmptyChain, NonEmptyList, OptionT}
import cats.data.Chain.nil
import cats.free.Cofree
import ChainZipper.Matchers._
import Location.Matchers._
import aqua.model.cursor.ChainZipper
import aqua.model.func.resolved._
import aqua.types.{BoxType, ScalarType}
import wvlet.log.LogSupport
import scala.annotation.tailrec
import cats.syntax.traverse._
object Topology extends LogSupport {
type Tree = Cofree[Chain, OpTag]
type Tree = Cofree[Chain, RawTag]
type Res = Cofree[Chain, ResolvedOp]
def resolve(op: Tree): Tree =
def resolve(op: Tree): Res =
Cofree
.cata[Chain, OpTag, Tree](resolveOnMoves(op)) {
case (SeqTag | _: OnTag | MetaTag(false, _, SeqTag | _: OnTag), children) =>
.cata[Chain, ResolvedOp, Res](resolveOnMoves(op).value) {
case (SeqRes, children) =>
Eval.later(
children.uncons
.filter(_._2.isEmpty)
.map(_._1)
.getOrElse(
Cofree(
SeqTag,
SeqRes,
Eval.now(children.flatMap {
case Cofree(SeqTag, ch) => ch.value
case Cofree(SeqRes, ch) => ch.value
case cf => Chain.one(cf)
})
)
)
)
case (head, children) => Eval.later(Cofree(head, Eval.now(children)))
}
.value
def resolveOnMoves(op: Tree): Tree =
Cursor
.transform(op)(transformWalker)
.getOrElse(op)
@tailrec
private def transformWalker(c: Cursor): List[Tree] =
c match {
case Cursor(_, `head`(parent: MetaTag) /: _) if !parent.skipTopology =>
transformWalker(c.mapParent(p => p.copy(parent.op, p.tail)))
case Cursor(
`current`(cf),
loc @ `head`(parent: GroupTag) /: _
) =>
// Set the service call IDs
val cfu = cf.copy(setServiceCallPeerId(cf.head, loc))
// We need to get there, finally
val currentPeerId = Chain.fromOption(loc.lastOn.map(_.peerId))
debug("Going to handle: " + cf.head)
val fromPrevToCurrentPath = fromPrevToCurrent(c, currentPeerId)
if (fromPrevToCurrentPath.nonEmpty) debug("BEFORE = " + fromPrevToCurrentPath)
val fromCurrentToNextPath = fromCurrentToNext(parent, c, currentPeerId)
if (fromCurrentToNextPath.nonEmpty) debug("NEXT = " + fromCurrentToNextPath)
(through(fromPrevToCurrentPath)
.append(cfu) ++ through(fromCurrentToNextPath, reversed = true)).toList
case Cursor(ChainZipper(_, cf, _), loc) =>
cf.copy(setServiceCallPeerId(cf.head, loc)) :: Nil
}
def fromPrevToCurrent(c: Cursor, currentPeerId: Chain[ValueModel]): Chain[ValueModel] = {
val prevOn = c.prevOnTags
val currentOn = c.loc.pathOn
val wasHandled = c.pathToRoot.collectFirst { case cc @ Cursor(_, `head`(_: GroupTag) /: _) =>
cc.loc.pathOn
}.exists(cclp =>
cclp == currentOn && {
val (c1, _) = skipCommonPrefix(prevOn, cclp)
c1.isEmpty
}
def wrap(cz: ChainZipper[Res]): Chain[Res] =
Chain.one(
if (cz.prev.nonEmpty || cz.next.nonEmpty) Cofree(SeqRes, Eval.now(cz.chain))
else cz.current
)
// Need to get from there
val prevPeerId =
Chain.fromOption(prevOn.lastOption.map(_.peerId) orElse c.loc.firstOn.map(_.peerId))
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 resolveOnMoves(op: Tree): Eval[Res] =
RawCursor(NonEmptyList.one(ChainZipper.one(op)))
.cata(wrap) { rc =>
debug(s"<:> $rc")
OptionT[Eval, ChainZipper[Res]](
({
case SeqTag => SeqRes
case _: OnTag => SeqRes
case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s)
case ForTag(item, iter) => FoldRes(item, iter)
case ParTag => ParRes
case XorTag | XorTag.LeftBiased => XorRes
case NextTag(item) => NextRes(item)
case CallServiceTag(serviceId, funcName, call) =>
CallServiceRes(
serviceId,
funcName,
call,
rc.currentPeerId
.getOrElse(LiteralModel.initPeerId)
)
}: PartialFunction[RawTag, ResolvedOp]).lift
.apply(rc.tag)
.map(MakeRes.leaf)
.traverse(c =>
Eval.later {
val cz = ChainZipper(
through(rc.pathFromPrev),
c,
through(rc.pathToNext)
)
if (cz.next.nonEmpty || cz.prev.nonEmpty) {
debug(s"Resolved $rc -> $c")
if (cz.prev.nonEmpty)
trace("From prev: " + cz.prev.map(_.head).toList.mkString(" -> "))
if (cz.next.nonEmpty)
trace("To next: " + cz.next.map(_.head).toList.mkString(" -> "))
} else debug(s"EMPTY $rc -> $c")
cz
}
)
)
def optimizePath(
peerIds: Chain[ValueModel],
prefix: Chain[ValueModel],
suffix: Chain[ValueModel]
): Chain[ValueModel] = {
val optimized = peerIds
.foldLeft(Chain.empty[ValueModel]) {
case (acc, p) if acc.lastOption.contains(p) => acc
case (acc, p) if acc.contains(p) => acc.takeWhile(_ != p) :+ p
case (acc, p) => acc :+ p
}
val noPrefix = skipPrefix(optimized, prefix, optimized)
skipSuffix(noPrefix, suffix, noPrefix)
}
def findPath(
fromOn: Chain[OnTag],
toOn: Chain[OnTag],
fromPeer: Chain[ValueModel],
toPeer: Chain[ValueModel]
): Chain[ValueModel] = {
val (from, to) = skipCommonPrefix(fromOn, toOn)
val fromFix =
if (from.isEmpty && fromPeer != toPeer) Chain.fromOption(fromOn.lastOption) else from
val toFix = if (to.isEmpty && fromPeer != toPeer) Chain.fromOption(toOn.lastOption) else to
val fromTo = fromFix.reverse.flatMap(_.via.reverse) ++ toFix.flatMap(_.via)
val optimized = optimizePath(fromPeer ++ fromTo ++ toPeer, fromPeer, toPeer)
debug("FIND PATH " + fromFix)
debug(" -> " + toFix)
debug(" Optimized: " + optimized)
optimized
}
@tailrec
def skipPrefix[T](chain: Chain[T], prefix: Chain[T], init: Chain[T]): Chain[T] =
(chain, prefix) match {
case (c ==: ctail, p ==: ptail) if c == p => skipPrefix(ctail, ptail, init)
case (_, `nil`) => chain
case (_, _) => init
}
@tailrec
def skipCommonPrefix[T](chain1: Chain[T], chain2: Chain[T]): (Chain[T], Chain[T]) =
(chain1, chain2) match {
case (c ==: ctail, p ==: ptail) if c == p => skipCommonPrefix(ctail, ptail)
case _ => chain1 -> chain2
}
@tailrec
def skipSuffix[T](chain: Chain[T], suffix: Chain[T], init: Chain[T]): Chain[T] =
(chain, suffix) match {
case (cinit :== c, pinit :== p) if c == p => skipSuffix(cinit, pinit, init)
case (_, `nil`) => chain
case (_, _) => init
.map(NonEmptyChain.fromChain(_).map(_.uncons))
.map {
case None =>
error("Topology emitted nothing")
Cofree(SeqRes, MakeRes.nilTail)
case Some((el, `nil`)) => el
case Some((el, tail)) =>
warn("Topology emitted many nodes, that's unusual")
Cofree(SeqRes, Eval.now(el +: tail))
}
// Walks through peer IDs, doing a noop function on each
// If same IDs are found in a row, does noop only once
// if there's a chain like a -> b -> c -> ... -> b -> g, remove everything between b and b
def through(peerIds: Chain[ValueModel], reversed: Boolean = false): Chain[Tree] =
def through(peerIds: Chain[ValueModel], reversed: Boolean = false): Chain[Res] =
peerIds.map { v =>
v.lastType match {
case _: BoxType =>
val itemName = "-via-peer-"
FuncOps.meta(
FuncOps.fold(
MakeRes.fold(
itemName,
v,
if (reversed)
FuncOps.seq(
FuncOps.next(itemName),
FuncOps.noop(VarModel(itemName, ScalarType.string))
MakeRes.seq(
MakeRes.next(itemName),
MakeRes.noop(VarModel(itemName, ScalarType.string))
)
else
FuncOps.seq(
FuncOps.noop(VarModel(itemName, ScalarType.string)),
FuncOps.next(itemName)
MakeRes.seq(
MakeRes.noop(VarModel(itemName, ScalarType.string)),
MakeRes.next(itemName)
)
),
skipTopology = true
)
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.func.Call
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.types.{ArrayType, DataType, OptionType, StreamType, Type}
import aqua.model.func.raw.{FuncOp, FuncOps}
import aqua.types.{ArrayType, DataType, StreamType}
import cats.data.Chain
trait ArgsProvider {
@ -26,7 +26,6 @@ case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataT
item,
VarModel(iter, ArrayType(t.element), Chain.empty),
FuncOps.seq(
// TODO: currently this does not work, as identity wraps everything with an array
FuncOps.identity(VarModel(item, t.element), Call.Export(name, t)),
FuncOps.next(item)
)

View File

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

View File

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

View File

@ -2,7 +2,7 @@ package aqua.model.transform
import aqua.model.{ValueModel, VarModel}
import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable}
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.model.func.raw.{FuncOp, FuncOps}
import aqua.types.ArrowType
import cats.Eval
import cats.syntax.apply._

View File

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

View File

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

View File

@ -267,4 +267,14 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
qGenComplex.d() shouldBe elseCall
qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None)*/
}
"function with par" should "be parsed correctly" in {
val script =
"""func topologyTest():
| on "friend":
| str2 <- LocalPrint.print("in on")
| par LocalPrint.print("in par")""".stripMargin
val tree = parser[Id]().parseAll(script).value.toEither.value
}
}

View File

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

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{AbilityIdTag, FuncOp}
import aqua.model.func.raw.{AbilityIdTag, FuncOp}
import aqua.parser.expr.AbilityIdExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{AssignmentTag, FuncOp, FuncOps}
import aqua.model.func.raw.{AssignmentTag, FuncOp, FuncOps}
import aqua.parser.expr.{AbilityIdExpr, AssignmentExpr}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.func.Call
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp}
import aqua.model.{Model, ValueModel}
import aqua.parser.expr.CallArrowExpr
import aqua.semantics.Prog

View File

@ -1,6 +1,6 @@
package aqua.semantics.expr
import aqua.model.func.body.{AssignmentTag, FuncOp, FuncOps, XorTag}
import aqua.model.func.raw.{AssignmentTag, FuncOp, FuncOps, XorTag}
import aqua.model.{Model, VarModel}
import aqua.parser.expr.CatchExpr
import aqua.semantics.Prog

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{FuncOp, XorTag}
import aqua.model.func.raw.{FuncOp, XorTag}
import aqua.parser.expr.ElseOtherwiseExpr
import aqua.semantics.Prog
import aqua.semantics.rules.abilities.AbilitiesAlgebra

View File

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

View File

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

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.{Model, ValueModel}
import aqua.model.func.body.{FuncOp, MatchMismatchTag, XorTag}
import aqua.model.func.raw.{FuncOp, MatchMismatchTag, XorTag}
import aqua.parser.expr.IfExpr
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.types.TypesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.{Model, ValueModel}
import aqua.model.func.body.{FuncOp, OnTag}
import aqua.model.func.raw.{FuncOp, OnTag}
import aqua.parser.expr.OnExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{FuncOp, ParTag}
import aqua.model.func.raw.{FuncOp, ParTag}
import aqua.parser.expr.ParExpr
import aqua.semantics.Prog
import cats.free.Free

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.func.body.{FuncOp, XorTag}
import aqua.model.func.raw.{FuncOp, XorTag}
import aqua.parser.expr.TryExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra

View File

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

View File

@ -1,7 +1,8 @@
package aqua
import aqua.model.func.Call
import aqua.model.func.body._
import aqua.model.func.raw._
import aqua.model.func.resolved.{CallServiceRes, MakeRes, MatchMismatchRes, ResolvedOp}
import aqua.model.transform.{BodyConfig, ErrorsCatcher}
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.types.{ArrayType, LiteralType, ScalarType}
@ -12,12 +13,140 @@ import cats.free.Cofree
import scala.language.implicitConversions
// Helper to simplify building and visualizing Cofree structures
case class Node(tag: OpTag, ops: List[Node] = Nil) {
case class Node[+T](label: T, children: List[Node[T]] = Nil) {
def cof[TT >: T]: Cofree[Chain, TT] = Node.nodeToCof(this)
override def toString: String =
tag.toString + (if (ops.isEmpty) "\n" else s"{\n${ops.mkString}\n}\n")
label.toString + (if (children.isEmpty) "\n" else s"{\n${children.mkString}\n}\n")
private def equalOrNot[T](left: T, right: T): String = (if (left == right)
def equalsOrPrintDiff[TT](other: Node[TT]): Boolean =
if (this == other) true
else {
println(Console.CYAN + "Given: " + this)
println(Console.YELLOW + "Other: " + other + Console.RESET)
false
}
}
object Node {
type Res = Node[ResolvedOp]
type Raw = Node[RawTag]
implicit def cofToNode[T](cof: Cofree[Chain, T]): Node[T] =
Node[T](cof.head, cof.tailForced.toList.map(cofToNode[T]))
implicit def nodeToCof[T](tree: Node[T]): Cofree[Chain, T] =
Cofree(tree.label, Eval.later(Chain.fromSeq(tree.children.map(nodeToCof))))
implicit def rawToFuncOp(tree: Raw): FuncOp =
FuncOp(tree.cof)
implicit def funcOpToRaw(op: FuncOp): Raw =
op.tree
val relay = LiteralModel("-relay-", ScalarType.string)
val relayV = VarModel("-relay-", ScalarType.string)
val initPeer = LiteralModel.initPeerId
val emptyCall = Call(Nil, None)
val otherPeer = LiteralModel("other-peer", ScalarType.string)
val otherPeerL = LiteralModel("\"other-peer\"", LiteralType.string)
val otherRelay = LiteralModel("other-relay", ScalarType.string)
val otherPeer2 = LiteralModel("other-peer-2", ScalarType.string)
val otherRelay2 = LiteralModel("other-relay-2", ScalarType.string)
val varNode = VarModel("node-id", ScalarType.string)
val viaList = VarModel("other-relay-2", ArrayType(ScalarType.string))
def callRes(
i: Int,
on: ValueModel,
exportTo: Option[Call.Export] = None,
args: List[ValueModel] = Nil
): Res = Node(
CallServiceRes(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo), on)
)
def callTag(i: Int, exportTo: Option[Call.Export] = None, args: List[ValueModel] = Nil): Raw =
Node(
CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo))
)
def callLiteralRes(i: Int, on: ValueModel, exportTo: Option[Call.Export] = None): Res = Node(
CallServiceRes(
LiteralModel("\"srv" + i + "\"", LiteralType.string),
s"fn$i",
Call(Nil, exportTo),
on
)
)
def callLiteralRaw(i: Int, exportTo: Option[Call.Export] = None): Raw = Node(
CallServiceTag(
LiteralModel("\"srv" + i + "\"", LiteralType.string),
s"fn$i",
Call(Nil, exportTo)
)
)
def errorCall(bc: BodyConfig, i: Int, on: ValueModel = initPeer): Res = Node[ResolvedOp](
CallServiceRes(
bc.errorHandlingCallback,
bc.errorFuncName,
Call(
ErrorsCatcher.lastErrorArg :: LiteralModel(
i.toString,
LiteralType.number
) :: Nil,
None
),
on
)
)
def respCall(bc: BodyConfig, value: ValueModel, on: ValueModel = initPeer): Res =
Node[ResolvedOp](
CallServiceRes(
bc.callbackSrvId,
bc.respFuncName,
Call(value :: Nil, None),
on
)
)
def dataCall(bc: BodyConfig, name: String, on: ValueModel = initPeer): Res = Node[ResolvedOp](
CallServiceRes(
bc.dataSrvId,
name,
Call(Nil, Some(Call.Export(name, ScalarType.string))),
on
)
)
def on(peer: ValueModel, via: List[ValueModel], body: Raw*) =
Node(
OnTag(peer, Chain.fromSeq(via)),
body.toList
)
def `try`(body: Raw*) =
Node(XorTag.LeftBiased, body.toList)
def matchRes(l: ValueModel, r: ValueModel, body: Res): Res =
Node(
MatchMismatchRes(l, r, shouldMatch = true),
body :: Nil
)
def matchRaw(l: ValueModel, r: ValueModel, body: Raw): Raw =
Node(
MatchMismatchTag(l, r, shouldMatch = true),
body :: Nil
)
def through(peer: ValueModel): Node[ResolvedOp] =
cofToNode(MakeRes.noop(peer))
private def equalOrNot[TT](left: TT, right: TT): String = (if (left == right)
Console.GREEN + left + Console.RESET
else
Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right)
@ -36,129 +165,53 @@ case class Node(tag: OpTag, ops: List[Node] = Nil) {
.mkString("::") + Console.GREEN + ", " +
equalOrNot(left.exportTo, right.exportTo) + Console.GREEN + ")"
private def diffServiceCall(left: CallServiceTag, right: CallServiceTag): String =
Console.GREEN + "CallServiceTag(" +
equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + ", " +
equalOrNot(left.funcName, right.funcName) + Console.GREEN + ", " +
diffCall(left.call, right.call) + Console.GREEN + ", " +
equalOrNot(left.peerId, right.peerId) +
private def diffServiceCall(left: CallServiceRes, right: CallServiceRes): String =
Console.GREEN + "(call" +
equalOrNot(left.peerId, right.peerId) + Console.GREEN + " (" +
equalOrNot(left.serviceId, right.serviceId) + Console.GREEN + " " +
equalOrNot(left.funcName, right.funcName) + Console.GREEN + ") " +
diffCall(left.call, right.call) + Console.GREEN +
Console.GREEN + ")" + Console.RESET
private def diffTags(left: OpTag, right: OpTag): String = (left, right) match {
case (l: CallServiceTag, r: CallServiceTag) => diffServiceCall(l, r)
private def diffRes(left: ResolvedOp, right: ResolvedOp): String = (left, right) match {
case (l: CallServiceRes, r: CallServiceRes) => diffServiceCall(l, r)
case _ =>
Console.BLUE + s" $left ${Console.RED}\n != ${Console.YELLOW}${right}${Console.RED}"
}
def diffToString(other: Node): String =
(if (tag == other.tag) Console.GREEN + tag
else diffTags(tag, other.tag)) + (if (ops.isEmpty && other.ops.isEmpty) "\n"
def diffToString(current: Node.Res, other: Node.Res): String =
(if (current.label == other.label) Console.GREEN + current.label
else
diffRes(current.label, other.label)) + (if (
current.children.isEmpty && other.children.isEmpty
) "\n"
else
"{\n") + Console.RESET +
(if (ops.length != other.ops.length)
Console.RED + s"number of ops: ${ops.length} != ${other.ops.length}\n" + Console.RESET
(if (current.children.length != other.children.length)
Console.RED + s"number of ops: ${current.children.length} != ${other.children.length}\n" + Console.RESET
else "") +
ops
.zip(other.ops)
.map { case (a, b) =>
a.diffToString(b)
current.children
.map(Option(_))
.zipAll(other.children.map(Option(_)), None, None)
.map {
case (Some(a), Some(b)) =>
diffToString(a, b)
case (Some(a), _) =>
Console.BLUE + a + Console.RESET
case (_, Some(b)) =>
Console.YELLOW + b + Console.RESET
case _ =>
Console.RED + "???" + Console.RESET
}
.mkString + (if (ops.isEmpty && other.ops.isEmpty) ""
.mkString + (if (current.children.isEmpty && other.children.isEmpty) ""
else
((if (tag == other.tag) Console.GREEN
((if (current.label == other.label) Console.GREEN
else Console.RED) + "}\n" + Console.RESET))
def equalsOrPrintDiff(other: Node): Boolean =
if (this == other) true
def equalsOrPrintDiff(current: Node.Res, other: Node.Res): Boolean =
if (current == other) true
else {
println(diffToString(other))
println(diffToString(current, other))
false
}
}
object Node {
type Cof = Cofree[Chain, OpTag]
implicit def cofToNode(cof: Cof): Node =
Node(cof.head, cof.tailForced.toList.map(cofToNode))
implicit def nodeToCof(tree: Node): Cof =
Cofree(tree.tag, Eval.later(Chain.fromSeq(tree.ops.map(nodeToCof))))
val relay = LiteralModel("-relay-", ScalarType.string)
val relayV = VarModel("-relay-", ScalarType.string)
val initPeer = LiteralModel.initPeerId
val emptyCall = Call(Nil, None)
val otherPeer = LiteralModel("other-peer", ScalarType.string)
val otherRelay = LiteralModel("other-relay", ScalarType.string)
val otherPeer2 = LiteralModel("other-peer-2", ScalarType.string)
val otherRelay2 = LiteralModel("other-relay-2", ScalarType.string)
val varNode = VarModel("node-id", ScalarType.string)
val viaList = VarModel("other-relay-2", ArrayType(ScalarType.string))
def call(i: Int, on: ValueModel = null) = Node(
CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(Nil, None), Option(on))
)
def callLiteral(i: Int, on: ValueModel = null) = Node(
CallServiceTag(
LiteralModel("\"srv" + i + "\"", LiteralType.string),
s"fn$i",
Call(Nil, None),
Option(on)
)
)
def errorCall(bc: BodyConfig, i: Int, on: ValueModel = null) = Node(
CallServiceTag(
bc.errorHandlingCallback,
bc.errorFuncName,
Call(
ErrorsCatcher.lastErrorArg :: LiteralModel(
i.toString,
LiteralType.number
) :: Nil,
None
),
Option(on)
)
)
def respCall(bc: BodyConfig, value: ValueModel, on: ValueModel = null) = Node(
CallServiceTag(
bc.callbackSrvId,
bc.respFuncName,
Call(value :: Nil, None),
Option(on)
)
)
def dataCall(bc: BodyConfig, name: String, on: ValueModel = null) = Node(
CallServiceTag(
bc.dataSrvId,
name,
Call(Nil, Some(Call.Export(name, ScalarType.string))),
Option(on)
)
)
def seq(nodes: Node*) = Node(SeqTag, nodes.toList)
def xor(left: Node, right: Node) = Node(XorTag, left :: right :: Nil)
def par(left: Node, right: Node) = Node(ParTag, left :: right :: Nil)
def on(peer: ValueModel, via: List[ValueModel], body: Node*) =
Node(
OnTag(peer, Chain.fromSeq(via)),
body.toList
)
def _match(l: ValueModel, r: ValueModel, body: Node) =
Node(
MatchMismatchTag(l, r, shouldMatch = true),
body :: Nil
)
def through(peer: ValueModel): Node =
FuncOps.noop(peer).tree
}

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
import aqua.Node
import aqua.model.VarModel
import aqua.model.func.Call
import aqua.model.func.raw.FuncOps
import aqua.model.func.resolved.{MakeRes, ResolvedOp, SeqRes, XorRes}
import aqua.types.ScalarType
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
@ -9,21 +17,21 @@ class TopologySpec extends AnyFlatSpec with Matchers {
"topology resolver" should "do nothing on init peer" in {
val init = on(
val init: Node.Raw = on(
initPeer,
relay :: Nil,
seq(
call(1),
call(2)
FuncOps.seq(
callTag(1),
callTag(2)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
call(1, initPeer),
call(2, initPeer)
val expected: Node.Res =
MakeRes.seq(
callRes(1, initPeer),
callRes(2, initPeer)
)
proc should be(expected)
@ -32,23 +40,23 @@ class TopologySpec extends AnyFlatSpec with Matchers {
"topology resolver" should "go through relay to any other node, directly" in {
val init = on(
val init: Node.Raw = on(
initPeer,
relay :: Nil,
on(
otherPeer,
Nil,
seq(
call(1),
call(2)
FuncOps.seq(
callTag(1),
callTag(2)
)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(through(relay), call(1, otherPeer), call(2, otherPeer))
val expected: Node.Res =
MakeRes.seq(through(relay), callRes(1, otherPeer), callRes(2, otherPeer))
proc should be(expected)
}
@ -61,21 +69,139 @@ class TopologySpec extends AnyFlatSpec with Matchers {
on(
otherPeer,
otherRelay :: Nil,
seq(
call(1),
call(2)
FuncOps.seq(
callTag(1),
callTag(2)
)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
val expected: Node.Res =
MakeRes.seq(
through(relay),
through(otherRelay),
call(1, otherPeer),
call(2, otherPeer)
callRes(1, otherPeer),
callRes(2, otherPeer)
)
proc.equalsOrPrintDiff(expected) should be(true)
}
"topology resolver" should "build return path in par if there are exported variables" in {
val export = Some(Call.Export("result", ScalarType.string))
val result = VarModel("result", ScalarType.string)
val init = on(
initPeer,
relay :: Nil,
FuncOps.seq(
FuncOps.par(
on(
otherPeer,
otherRelay :: Nil,
callTag(1, export)
),
callTag(2)
),
callTag(3, None, result :: Nil)
)
)
val proc: Node.Res = Topology.resolve(init)
val expected: Node.Res =
MakeRes.seq(
MakeRes.par(
MakeRes.seq(
through(relay),
through(otherRelay),
callRes(1, otherPeer, export),
through(otherRelay),
through(relay),
// we should return to a caller to continue execution
through(initPeer)
),
callRes(2, initPeer)
),
callRes(3, initPeer, None, result :: Nil)
)
Node.equalsOrPrintDiff(proc, expected) should be(true)
}
"topology resolver" should "work fine with par" in {
val init = on(
initPeer,
relay :: Nil,
FuncOps.par(
on(
otherPeer,
otherRelay :: Nil,
callTag(1)
),
callTag(2)
)
)
val proc = Topology.resolve(init)
val expected: Node.Res =
MakeRes.par(
MakeRes.seq(
through(relay),
through(otherRelay),
callRes(1, otherPeer)
),
callRes(2, initPeer)
)
proc.equalsOrPrintDiff(expected) should be(true)
}
"topology resolver" should "create correct calls in try" in {
val init = Node.`try`(callTag(1))
val proc = Topology.resolve(init)
proc.equalsOrPrintDiff(
Cofree[Chain, ResolvedOp](XorRes, Eval.now(Chain.one(callRes(1, initPeer))))
) should be(true)
}
"topology resolver" should "work fine with par with on" in {
val init: Node.Raw = on(
initPeer,
relay :: Nil,
FuncOps.par(
on(
otherPeer,
otherRelay :: Nil,
callTag(1)
),
on(
otherPeer2,
otherRelay2 :: Nil,
callTag(2)
)
)
)
val proc: Node.Res = Topology.resolve(init)
val expected: Node.Res =
MakeRes.par(
MakeRes.seq(
through(relay),
through(otherRelay),
callRes(1, otherPeer)
),
MakeRes.seq(
through(relay),
through(otherRelay2),
callRes(2, otherPeer2)
)
)
proc.equalsOrPrintDiff(expected) should be(true)
@ -89,28 +215,28 @@ class TopologySpec extends AnyFlatSpec with Matchers {
on(
otherPeer,
otherRelay :: Nil,
xor(
seq(
call(1),
call(2)
FuncOps.xor(
FuncOps.seq(
callTag(1),
callTag(2)
),
call(3)
callTag(3)
)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
val expected: Node.Res =
MakeRes.seq(
through(relay),
through(otherRelay),
xor(
seq(
call(1, otherPeer),
call(2, otherPeer)
MakeRes.xor(
MakeRes.seq(
callRes(1, otherPeer),
callRes(2, otherPeer)
),
call(3, otherPeer)
callRes(3, otherPeer)
)
)
@ -118,25 +244,25 @@ class TopologySpec extends AnyFlatSpec with Matchers {
}
"topology resolver" should "simplify a route with init_peer_id" in {
val init = on(
val init: Node.Raw = on(
initPeer,
relay :: Nil,
seq(
FuncOps.seq(
on(
initPeer,
relay :: Nil,
call(1)
callTag(1)
),
call(2)
callTag(2)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
call(1, initPeer),
call(2, initPeer)
MakeRes.seq(
callRes(1, initPeer),
callRes(2, initPeer)
)
proc.equalsOrPrintDiff(expected) should be(true)
@ -144,29 +270,29 @@ class TopologySpec extends AnyFlatSpec with Matchers {
"topology resolver" should "get back to init peer" in {
val init = on(
val init: Node.Raw = on(
initPeer,
relay :: Nil,
seq(
FuncOps.seq(
on(
otherPeer,
otherRelay :: Nil,
call(1)
callTag(1)
),
call(2)
callTag(2)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
val expected: Node.Res =
MakeRes.seq(
through(relay),
through(otherRelay),
call(1, otherPeer),
callRes(1, otherPeer),
through(otherRelay),
through(relay),
call(2, initPeer)
callRes(2, initPeer)
)
// println(Console.BLUE + init)
@ -197,19 +323,19 @@ class TopologySpec extends AnyFlatSpec with Matchers {
val init = on(
initPeer,
relay :: Nil,
seq(
call(1),
call(2),
call(3),
FuncOps.seq(
callTag(1),
callTag(2),
callTag(3),
on(
varNode,
viaList :: Nil,
call(4)
callTag(4)
),
on(
initPeer,
relay :: Nil,
call(5)
callTag(5)
)
)
)
@ -222,50 +348,50 @@ class TopologySpec extends AnyFlatSpec with Matchers {
val init = on(
initPeer,
relay :: Nil,
seq(
FuncOps.seq(
on(
otherPeer,
otherRelay :: Nil,
call(0),
callTag(0),
on(
otherPeer2,
otherRelay :: Nil,
call(1),
_match(
callTag(1),
matchRaw(
otherPeer,
otherRelay,
on(
otherPeer,
otherRelay :: Nil,
call(2)
callTag(2)
)
)
)
),
call(3)
callTag(3)
)
)
val proc: Node = Topology.resolve(init)
val proc: Node.Res = Topology.resolve(init)
val expected =
seq(
val expected: Node.Res =
MakeRes.seq(
through(relay),
through(otherRelay),
call(0, otherPeer),
callRes(0, otherPeer),
through(otherRelay),
call(1, otherPeer2),
_match(
callRes(1, otherPeer2),
matchRes(
otherPeer,
otherRelay,
seq(
MakeRes.seq(
through(otherRelay),
call(2, otherPeer)
callRes(2, otherPeer)
)
),
through(otherRelay),
through(relay),
call(3, initPeer)
callRes(3, initPeer)
)
// println(Console.BLUE + init)
@ -276,4 +402,63 @@ class TopologySpec extends AnyFlatSpec with Matchers {
proc.equalsOrPrintDiff(expected) should be(true)
}
"topology resolver" should "resolve xor path" in {
val init = on(
initPeer,
relay :: Nil,
FuncOps.seq(
FuncOps.xor(
on(
otherPeer,
otherRelay :: Nil,
callTag(0)
),
on(
initPeer,
relay :: Nil,
callTag(1)
)
),
on(
otherPeer,
otherRelay :: Nil,
callTag(3)
),
callTag(4)
)
)
val proc: Node.Res = Topology.resolve(init)
val expected: Node.Res =
MakeRes.seq(
MakeRes.xor(
MakeRes.seq(
through(relay),
through(otherRelay),
callRes(0, otherPeer)
),
MakeRes.seq(
through(otherRelay),
through(relay),
callRes(1, initPeer),
through(relay),
through(otherRelay)
)
),
callRes(3, otherPeer),
through(otherRelay),
through(relay),
callRes(4, initPeer)
)
// println(Console.BLUE + init)
println(Console.YELLOW + proc)
println(Console.MAGENTA + expected)
println(Console.RESET)
Node.equalsOrPrintDiff(proc, expected) should be(true)
}
}

View File

@ -1,7 +1,8 @@
package aqua.model.transform
import aqua.Node
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp, FuncOps}
import aqua.model.func.resolved.{CallServiceRes, MakeRes}
import aqua.model.func.{ArgsDef, Call, FuncCallable}
import aqua.model.{LiteralModel, VarModel}
import aqua.types.ScalarType
@ -11,14 +12,14 @@ import org.scalatest.matchers.should.Matchers
class TransformSpec extends AnyFlatSpec with Matchers {
import Node._
"transform.forClient" should "work well with function 1 (no calls before on)" in {
"transform.forClient" should "work well with function 1 (no calls before on), generate correct error handling" in {
val ret = LiteralModel.quote("return this")
val func: FuncCallable =
FuncCallable(
"ret",
FuncOp(on(otherPeer, otherRelay :: Nil, call(1))),
on(otherPeer, otherRelay :: Nil, callTag(1)),
ArgsDef.empty,
Some((ret, ScalarType.string)),
Map.empty,
@ -29,17 +30,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val fc = Transform.forClient(func, bc)
val procFC: Node = fc
val procFC: Node.Res = fc
val expectedFC = seq(
xor(
seq(
val expectedFC: Node.Res =
MakeRes.xor(
MakeRes.seq(
dataCall(bc, "-relay-", initPeer),
through(relayV),
through(otherRelay),
xor(
call(1, otherPeer),
seq(
MakeRes.xor(
callRes(1, otherPeer),
MakeRes.seq(
through(otherRelay),
through(relayV),
errorCall(bc, 1, initPeer),
@ -48,22 +49,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
),
through(otherRelay),
through(relayV),
xor(
MakeRes.xor(
respCall(bc, ret, initPeer),
seq(
errorCall(bc, 2, initPeer)
)
)
),
seq(
errorCall(bc, 3, initPeer)
)
)
)
println(procFC)
//println(procFC)
procFC.equalsOrPrintDiff(expectedFC) should be(true)
Node.equalsOrPrintDiff(procFC, expectedFC) should be(true)
}
@ -73,7 +69,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val func: FuncCallable = FuncCallable(
"ret",
FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))),
FuncOps.seq(callTag(0), on(otherPeer, Nil, callTag(1))),
ArgsDef.empty,
Some((ret, ScalarType.string)),
Map.empty,
@ -84,14 +80,14 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val fc = Transform.forClient(func, bc)
val procFC: Node = fc
val procFC: Res = fc
val expectedFC =
seq(
val expectedFC: Res =
MakeRes.seq(
dataCall(bc, "-relay-", initPeer),
call(0, initPeer),
callRes(0, initPeer),
through(relayV),
call(1, otherPeer),
callRes(1, otherPeer),
through(relayV),
respCall(bc, ret, initPeer)
)
@ -119,10 +115,9 @@ class TransformSpec extends AnyFlatSpec with Matchers {
CallServiceTag(
LiteralModel.quote("srv1"),
"foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))),
None
)
Call(Nil, Some(Call.Export("v", ScalarType.string)))
)
).cof
),
ArgsDef.empty,
Some((VarModel("v", ScalarType.string), ScalarType.string)),
@ -134,7 +129,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
FuncCallable(
"f2",
FuncOp(
Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string)))))
Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string))))).cof
),
ArgsDef.empty,
Some((VarModel("v", ScalarType.string), ScalarType.string)),
@ -144,17 +139,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val bc = BodyConfig(wrapWithXor = false)
val res = Transform.forClient(f2, bc): Node
val res = Transform.forClient(f2, bc): Node.Res
res.equalsOrPrintDiff(
seq(
MakeRes.seq(
dataCall(bc, "-relay-", initPeer),
Node(
CallServiceTag(
CallServiceRes(
LiteralModel.quote("srv1"),
"foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))),
Some(initPeer)
initPeer
)
),
respCall(bc, VarModel("v", ScalarType.string), initPeer)

View File

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