diff --git a/.scalafmt.conf b/.scalafmt.conf index 8e4daf02..04974b20 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -2,7 +2,7 @@ version = 2.7.5 docstrings = JavaDoc -maxColumn = 120 +maxColumn = 100 align = none align { diff --git a/src/main/scala/aqua/Aqua.scala b/src/main/scala/aqua/Aqua.scala index dccc6309..5ee36783 100644 --- a/src/main/scala/aqua/Aqua.scala +++ b/src/main/scala/aqua/Aqua.scala @@ -11,13 +11,15 @@ object Aqua { def parse(input: String): ValidatedNec[AquaError, Ast[Span.F]] = Ast.fromString[Span.F](input) - def validate(input: String): ValidatedNec[AquaError, Model] = - parse(input).andThen(ast => Semantics.validate(ast).leftMap(_.map(ts => CompilerError(ts._1.unit._1, ts._2)))) + def generateModel(input: String): ValidatedNec[AquaError, Model] = + parse(input).andThen(ast => + Semantics.generateModel(ast).leftMap(_.map(ts => CompilerError(ts._1.unit._1, ts._2))) + ) def generate(input: String, air: Boolean): ValidatedNec[AquaError, String] = - validate(input).map { - case g: ScriptModel => - if (air) g.generateAir else g.generateTypescript + generateModel(input).map { + case m: ScriptModel => + if (air) m.generateAir else m.generateTypescript case _ => "//No input given" } diff --git a/src/main/scala/aqua/model/FuncCallable.scala b/src/main/scala/aqua/model/FuncCallable.scala index dc1f00f8..f9a56ea9 100644 --- a/src/main/scala/aqua/model/FuncCallable.scala +++ b/src/main/scala/aqua/model/FuncCallable.scala @@ -50,7 +50,8 @@ case class FuncCallable( // We have some names in scope (forbiddenNames), can't introduce them again; so find new names val shouldRename = findNewNames(forbiddenNames, treeDefines) // If there was a collision, rename exports and usages with new names - val treeRenamed = if (shouldRename.isEmpty) treeWithValues else treeWithValues.rename(shouldRename) + val treeRenamed = + if (shouldRename.isEmpty) treeWithValues else treeWithValues.rename(shouldRename) // Result could be derived from arguments, or renamed; take care about that val result = ret.map(_._1).map(_.resolveWith(argsToData)).map { @@ -71,7 +72,9 @@ case class FuncCallable( case ((noNames, resolvedExports), CallArrowTag(None, fn, c)) if allArrows.contains(fn) => // Apply arguments to a function – recursion val (appliedOp, value) = - allArrows(fn).apply(c.mapValues(_.resolveWith(resolvedExports)), argsToArrows, noNames).value + allArrows(fn) + .apply(c.mapValues(_.resolveWith(resolvedExports)), argsToArrows, noNames) + .value // Function defines new names inside its body – need to collect them // TODO: actually it's done and dropped – so keep and pass it instead @@ -80,7 +83,10 @@ case class FuncCallable( (noNames ++ newNames, resolvedExports ++ c.exportTo.zip(value)) -> appliedOp.tree case (acc @ (_, resolvedExports), tag) => // All the other tags are already resolved and need no substitution - acc -> Cofree[Chain, OpTag](tag.mapValues(_.resolveWith(resolvedExports)), Eval.now(Chain.empty)) + acc -> Cofree[Chain, OpTag]( + tag.mapValues(_.resolveWith(resolvedExports)), + Eval.now(Chain.empty) + ) } .map { case ((_, resolvedExports), b) => // If return value is affected by any of internal functions, resolve it @@ -159,13 +165,7 @@ case class FuncCallable( ).value._1 ) ++ Chain.fromSeq(returnCallback.toSeq) ) - .resolveTopology(noop) - - def noop(peerId: ValueModel): FuncOp = - FuncOp.wrap( - OnTag(peerId, Nil), - FuncOp.leaf(CallServiceTag(LiteralModel("\"op\""), "identity", Call(Nil, None), Some(peerId))) - ) + .resolveTopology() def generateTsCall: Call = Call( diff --git a/src/main/scala/aqua/model/FuncOp.scala b/src/main/scala/aqua/model/FuncOp.scala index 3bf2c0ef..0447e955 100644 --- a/src/main/scala/aqua/model/FuncOp.scala +++ b/src/main/scala/aqua/model/FuncOp.scala @@ -1,6 +1,5 @@ package aqua.model -import aqua.model.FuncOp.wrap import cats.Eval import cats.data.Chain import cats.free.Cofree @@ -16,8 +15,10 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { Cofree.cata(tree)(folder) def definesValueNames: Eval[Set[String]] = cata[Set[String]] { - case (CallArrowTag(_, _, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) - case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) + case (CallArrowTag(_, _, Call(_, Some(export))), acc) => + Eval.later(acc.foldLeft(Set(export))(_ ++ _)) + case (CallServiceTag(_, _, Call(_, Some(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])(_ ++ _)) } @@ -41,7 +42,7 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { ) ) - def resolveTopology(doVia: ValueModel => FuncOp): FuncOp = + def resolveTopology(): FuncOp = FuncOp(FuncOp.transformWithPath(tree) { case (path, c: CallServiceTag) => Cofree[Chain, OpTag]( @@ -55,11 +56,12 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { OnTag(pid, Nil), Eval.now( Chain.fromSeq( - via.map(doVia).map(_.tree) + via.map(FuncOp.noop).map(_.tree) ) ) ) - case (_, t) => Cofree[Chain, OpTag](t, Eval.now(Chain.empty)) + case (_, t) => + Cofree[Chain, OpTag](t, Eval.now(Chain.empty)) }) def :+:(prev: FuncOp): FuncOp = @@ -68,14 +70,21 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model { object FuncOp { + def noop(peerId: ValueModel): FuncOp = + FuncOp.wrap( + OnTag(peerId, Nil), + FuncOp.leaf(CallServiceTag(LiteralModel("\"op\""), "identity", Call(Nil, None), Some(peerId))) + ) + def traverseA[A](cf: Cofree[Chain, OpTag], init: A)( f: (A, OpTag) => (A, Cofree[Chain, OpTag]) ): Eval[(A, Cofree[Chain, OpTag])] = { val (headA, head) = f(init, cf.head) // TODO: it should be in standard library, with some other types cf.tail - .map(_.foldLeft[(A, Chain[Cofree[Chain, OpTag]])]((headA, head.tailForced)) { case ((aggrA, aggrTail), child) => - traverseA(child, aggrA)(f).value.map(aggrTail.append) + .map(_.foldLeft[(A, Chain[Cofree[Chain, OpTag]])]((headA, head.tailForced)) { + case ((aggrA, aggrTail), child) => + traverseA(child, aggrA)(f).value.map(aggrTail.append) }) .map(_.map(ch => head.copy(tail = Eval.now(ch)))) } @@ -83,13 +92,13 @@ object FuncOp { def transformWithPath(cf: Cofree[Chain, OpTag], path: List[OpTag] = Nil)( f: (List[OpTag], OpTag) => Cofree[Chain, OpTag] ): Cofree[Chain, OpTag] = { - val h = f(path, cf.head) + val newCf = f(path, cf.head) Cofree[Chain, OpTag]( - h.head, - (h.tail, cf.tail) + newCf.head, + (newCf.tail, cf.tail) .mapN(_ ++ _) // IF make foldLeft here, will be possible to get info from prev sibling - .map(_.map(transformWithPath(_, h.head :: path)(f))) + .map(_.map(transformWithPath(_, newCf.head :: path)(f))) ) } diff --git a/src/main/scala/aqua/semantics/Semantics.scala b/src/main/scala/aqua/semantics/Semantics.scala index 197f96c1..b4765c9f 100644 --- a/src/main/scala/aqua/semantics/Semantics.scala +++ b/src/main/scala/aqua/semantics/Semantics.scala @@ -4,7 +4,12 @@ import aqua.model.{FuncOp, Model} import aqua.parser.lexer.Token import aqua.parser.{Ast, Expr} import aqua.semantics.rules.ReportError -import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState, AbilityOp} +import aqua.semantics.rules.abilities.{ + AbilitiesAlgebra, + AbilitiesInterpreter, + AbilitiesState, + AbilityOp +} import aqua.semantics.rules.names.{NameOp, NamesAlgebra, NamesInterpreter, NamesState} import aqua.semantics.rules.types.{TypeOp, TypesAlgebra, TypesInterpreter, TypesState} import cats.Eval @@ -30,12 +35,13 @@ object Semantics { // TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones // Then foldLeft just like now inners - .foldRight[Free[G, List[Model]]](Free.pure[G, List[Model]](List.empty[Model])) { case (a, b) => - (a, b).mapN { - case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc => - (prev :+: next) :: tail - case (prev, acc) => prev :: acc - } + .foldRight[Free[G, List[Model]]](Free.pure[G, List[Model]](List.empty[Model])) { + case (a, b) => + (a, b).mapN { + case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc => + (prev :+: next) :: tail + case (prev, acc) => prev :: acc + } } .map(_.reduceLeftOption(_ |+| _).getOrElse(Model.empty("AST is empty"))) ) @@ -58,13 +64,15 @@ object Semantics { import monocle.macros.syntax.all._ implicit val re: ReportError[F, CompilerState[F]] = - (st: CompilerState[F], token: Token[F], hint: String) => st.focus(_.errors).modify(_.append(token -> hint)) + (st: CompilerState[F], token: Token[F], hint: String) => + st.focus(_.errors).modify(_.append(token -> hint)) implicit val ns: Lens[CompilerState[F], NamesState[F]] = GenLens[CompilerState[F]](_.names) val names = new NamesInterpreter[F, CompilerState[F]]() - implicit val as: Lens[CompilerState[F], AbilitiesState[F]] = GenLens[CompilerState[F]](_.abilities) + implicit val as: Lens[CompilerState[F], AbilitiesState[F]] = + GenLens[CompilerState[F]](_.abilities) val abilities = new AbilitiesInterpreter[F, CompilerState[F]]() @@ -78,11 +86,13 @@ object Semantics { free.foldMap[State[CompilerState[F], *]](interpreter) } - def validate[F[_]](ast: Ast[F]): ValidatedNec[(Token[F], String), Model] = + def generateModel[F[_]](ast: Ast[F]): ValidatedNec[(Token[F], String), Model] = (transpile[F] _ andThen interpret[F])(ast) .run(CompilerState[F]()) .map { case (state, gen) => - NonEmptyChain.fromChain(state.errors).fold[ValidatedNec[(Token[F], String), Model]](Valid(gen))(Invalid(_)) + NonEmptyChain + .fromChain(state.errors) + .fold[ValidatedNec[(Token[F], String), Model]](Valid(gen))(Invalid(_)) } .value } diff --git a/src/main/scala/aqua/semantics/expr/OnSem.scala b/src/main/scala/aqua/semantics/expr/OnSem.scala index 40236fef..7f246fdf 100644 --- a/src/main/scala/aqua/semantics/expr/OnSem.scala +++ b/src/main/scala/aqua/semantics/expr/OnSem.scala @@ -5,6 +5,7 @@ import aqua.parser.expr.OnExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra +import cats.data.Chain import cats.syntax.flatMap._ import cats.syntax.functor._ @@ -24,7 +25,30 @@ class OnSem[F[_]](val expr: OnExpr[F]) extends AnyVal { (_: Unit, ops: Model) => A.endScope() as (ops match { case op: FuncOp => - FuncOp.wrap(OnTag(ValuesAlgebra.valueToModel(expr.peerId), expr.via.map(ValuesAlgebra.valueToModel)), op) + // the way to the node may be lost if there will be chains of `on` without calls + // so peerId is added to the path + val path = expr.via :+ expr.peerId + val returnPath = path.reverse + val returnLeaf = { + FuncOp.leaf( + OnTag( + ValuesAlgebra.valueToModel(returnPath.last), + returnPath.map(ValuesAlgebra.valueToModel) + ) + ) + } + + FuncOp.node( + OnTag( + ValuesAlgebra.valueToModel(expr.peerId), + path.map(ValuesAlgebra.valueToModel) + ), + Chain( + op, + returnLeaf + ) + ) + case m => Model.error("On body is not an op, it's " + m) }) ) diff --git a/src/test/scala/aqua/Utils.scala b/src/test/scala/aqua/Utils.scala index a411caa5..16b7ed27 100644 --- a/src/test/scala/aqua/Utils.scala +++ b/src/test/scala/aqua/Utils.scala @@ -1,5 +1,6 @@ package aqua +import aqua.model.Call import aqua.parser.expr.{ AbilityIdExpr, AliasExpr, @@ -37,17 +38,23 @@ import aqua.semantics.LiteralType.{bool, number, string} import aqua.semantics.{LiteralType, ScalarType} import org.scalatest.EitherValues +import scala.collection.mutable import scala.language.implicitConversions object Utils { implicit def toAb(str: String): Ability[Id] = Ability[Id](str) implicit def toName(str: String): Name[Id] = Name[Id](str) + implicit def toNameOp(str: Option[String]): Option[Name[Id]] = str.map(s => toName(s)) - implicit def toFields(fields: List[String]): List[IntoField[Id]] = fields.map(f => IntoField[Id](f)) + implicit def toFields(fields: List[String]): List[IntoField[Id]] = + fields.map(f => IntoField[Id](f)) implicit def toVar(name: String): VarLambda[Id] = VarLambda[Id](toName(name), Nil) + implicit def toVarOp(name: Option[String]): Option[VarLambda[Id]] = + name.map(s => VarLambda[Id](toName(s), Nil)) + implicit def toVarLambda(name: String, fields: List[String]): VarLambda[Id] = VarLambda[Id](toName(name), toFields(fields)) implicit def toLiteral(name: String, t: LiteralType): Literal[Id] = Literal[Id](name, t) @@ -58,18 +65,30 @@ object Utils { implicit def toCustomType(str: String): CustomTypeToken[Id] = CustomTypeToken[Id](str) def toArrayType(str: String): ArrayTypeToken[Id] = ArrayTypeToken[Id]((), str) - implicit def toArrowType(args: List[DataTypeToken[Id]], res: Option[DataTypeToken[Id]]): ArrowTypeToken[Id] = + implicit def toArrowType( + args: List[DataTypeToken[Id]], + res: Option[DataTypeToken[Id]] + ): ArrowTypeToken[Id] = ArrowTypeToken[Id]((), args, res) - implicit def toCustomArg(str: String, customType: String): Arg[Id] = Arg[Id](str, toCustomType(customType)) + implicit def toCustomArg(str: String, customType: String): Arg[Id] = + Arg[Id](str, toCustomType(customType)) implicit def toArg(str: String, typeToken: TypeToken[Id]): Arg[Id] = Arg[Id](str, typeToken) + implicit def toArgSc(str: String, scalarType: ScalarType): Arg[Id] = + Arg[Id](str, scToBt(scalarType)) + implicit def scToBt(sc: ScalarType): BasicTypeToken[Id] = BasicTypeToken[Id](sc) + + val boolSc: BasicTypeToken[Id] = BasicTypeToken[Id](ScalarType.bool) + val stringSc: BasicTypeToken[Id] = BasicTypeToken[Id](ScalarType.string) } trait Utils extends EitherValues { + val emptyCall = Call(Nil, None) + def parseExpr(str: String): CallArrowExpr[Id] = CallArrowExpr.p[Id].parseAll(str).value @@ -110,4 +129,8 @@ trait Utils extends EitherValues { ArrowTypeExpr.p[Id].parseAll(str).value def funcExpr(str: String): FuncExpr[Id] = FuncExpr.p[Id].parseAll(str).value + + implicit class QueueHelper[T](q: mutable.Queue[T]) { + def d(): T = q.dequeue() + } } diff --git a/src/test/scala/aqua/parser/FuncExprSpec.scala b/src/test/scala/aqua/parser/FuncExprSpec.scala index 6dd5f840..e51e5bd6 100644 --- a/src/test/scala/aqua/parser/FuncExprSpec.scala +++ b/src/test/scala/aqua/parser/FuncExprSpec.scala @@ -1,19 +1,44 @@ package aqua.parser -import aqua.Utils -import aqua.parser.Ast.Tree -import aqua.parser.expr.FuncExpr -import aqua.parser.lexer.{ArrowTypeToken, BasicTypeToken} +import aqua.model.{ + Call, + CallArrowTag, + CallServiceTag, + FuncCallable, + LiteralModel, + OnTag, + OpTag, + ScriptModel, + SeqTag, + XorTag +} +import aqua.{SyntaxError, Utils} +import aqua.parser.Ast.{parser, Tree} +import aqua.parser.expr.{ + AbilityIdExpr, + ArrowTypeExpr, + CallArrowExpr, + FuncExpr, + IfExpr, + OnExpr, + ReturnExpr, + RootExpr, + ServiceExpr +} +import aqua.parser.lexer.{ArrowTypeToken, BasicTypeToken, EqOp} import aqua.semantics.ScalarType.{bool, u64} import cats.{Eval, Id} import cats.syntax.traverse._ +import cats.syntax.foldable._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import aqua.parser.lift.LiftParser.Implicits.idLiftParser +import aqua.semantics.Semantics import aqua.semantics.Semantics.{folder, Alg} -import cats.data.Chain +import cats.data.{Chain, NonEmptyChain, Validated} import cats.free.Cofree +import scala.collection.mutable import scala.language.implicitConversions class FuncExprSpec extends AnyFlatSpec with Matchers with Utils { @@ -21,18 +46,35 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils { import aqua.semantics.ScalarType.{string, u32} "func header" should "parse" in { - funcExpr("func some() -> bool") should be(FuncExpr("some", List(), Some(bool: BasicTypeToken[Id]), None)) + funcExpr("func some() -> bool") should be( + FuncExpr("some", List(), Some(bool: BasicTypeToken[Id]), None) + ) funcExpr("func some()") should be(FuncExpr("some", List(), None, None)) - val arrowToken = ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32)), Some(BasicTypeToken[Id](bool))) + val arrowToken = + ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32)), Some(BasicTypeToken[Id](bool))) funcExpr("func some(peer: PeerId, other: u32 -> bool)") should be( - FuncExpr(toName("some"), List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken)), None, None) + FuncExpr( + toName("some"), + List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken)), + None, + None + ) ) val arrowToken2 = - ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32), BasicTypeToken[Id](u64)), Some(BasicTypeToken[Id](bool))) + ArrowTypeToken[Id]( + (), + List(BasicTypeToken[Id](u32), BasicTypeToken[Id](u64)), + Some(BasicTypeToken[Id](bool)) + ) funcExpr("func some(peer: PeerId, other: u32, u64 -> bool)") should be( - FuncExpr(toName("some"), List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken2)), None, None) + FuncExpr( + toName("some"), + List(toCustomArg("peer", "PeerId"), toArg("other", arrowToken2)), + None, + None + ) ) val arrowToken3 = ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32)), None) @@ -44,25 +86,26 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils { None ) ) - } - "on" should "parse on x: y" in { - val script = - """func a(): - | on peer.id: - | x <- Ab.func() - | Peer "some id" - | call(true)""".stripMargin - - val c: Cofree[Chain, Expr[Id]] = FuncExpr.ast[Id](Indent()).parseAll(script).value - val a = Ast(c).cata(folder[Id, Alg[Id, *]]).value -// a.run - println(a) - - FuncExpr.ast[Id](Indent()).parseAll(script).isRight should be(true) + def checkHeadGetTail( + tree: Cofree[Chain, Expr[Id]], + headCheck: Expr[Id], + lengthCheck: Int + ): Chain[Cofree[Chain, Expr[Id]]] = { + tree.head should be(headCheck) + val tail = tree.tailForced + tail.length should be(lengthCheck) + tail } - "if" should "parse if x == y" in { + + def headTail[T]( + tree: Cofree[Chain, T] + ): (T, List[Cofree[Chain, T]]) = { + (tree.head, tree.tailForced.toList) + } + + "func expr" should "parse on x: y" in { val script = """func a(): | if peer.id == other: @@ -70,59 +113,153 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils { | Peer "some id" | call(true)""".stripMargin - FuncExpr.ast[Id](Indent()).parseAll(script).isRight should be(true) + val tree = FuncExpr.ast[Id](Indent()).parseAll(script).value + val funcBody = checkHeadGetTail(tree, FuncExpr("a", Nil, None, None), 1).toList + println("body: " + funcBody) + + val ifBody = + checkHeadGetTail( + funcBody.head, + IfExpr(toVarLambda("peer", List("id")), EqOp[Id](true), toVar("other")), + 3 + ).toList + + ifBody.head.head should be(CallArrowExpr(Some(toName("x")), Some(toAb("Ab")), "func", Nil)) + ifBody(1).head should be(AbilityIdExpr(toAb("Peer"), toStr("some id"))) + ifBody(2).head should be(CallArrowExpr(None, None, "call", List(toBool(true)))) + } - /* - "function" should "parse getTime as a whole" in { - val func = - """func getTime(peer: PeerId, ret: u32 -> ()) -> string: - | on peer: - | Peer "peer" - | t <- Peer.timestamp() - | ret(t)""".stripMargin + "some" should "other" in { + val script = + """service Local("local"): + | gt: -> bool + | + |func tryGen() -> bool: + | on "deeper" via "deep": + | v <- Local.gt() + | <- v + | + |func genC(val: string) -> bool: + | one <- Local.gt() + | on "smth" via "else": + | two <- tryGen() + | three <- Local.gt() + | <- two""".stripMargin - DefFunc.`deffunc`.parseAll(func).right.value should be( - DefFunc[Id, HNil]( - getTimeHead, - NonEmptyList.of( - On[Id, HNil]( - VarLambda[Id]("peer", Nil), - NonEmptyList.of( - AbilityId[Id, HNil]("Peer", Literal[Id]("\"peer\"", LiteralType.string), HNil), - Extract[Id, HNil]("t", AbilityFuncCall[Id, HNil]("Peer", "timestamp", "Peer.timestamp", Nil, HNil), HNil) - ), - HNil - ), - FuncCall[Id, HNil]("ret", VarLambda[Id]("t", Nil) :: Nil, HNil) - ), - HNil - ) - ) + val tree = parser[Id](Indent()).parseAll(script).value + + val qTree = tree.tree.foldLeft(mutable.Queue.empty[Expr[Id]]) { case (acc, tag) => + acc.enqueue(tag) } - "function" should "parse getTime with no return" in { - val func = - """func getTime(peer: PeerId, ret: u32 -> ()) -> string: - | on peer: - | Peer "peer" - | t <- Peer.timestamp()""".stripMargin + qTree.d() shouldBe RootExpr() + // Local service + qTree.d() shouldBe ServiceExpr(toAb("Local"), Some(toStr("local"))) + qTree.d() shouldBe ArrowTypeExpr("gt", toArrowType(Nil, Some(scToBt(bool)))) + qTree.d() shouldBe FuncExpr("tryGen", Nil, Some(scToBt(bool)), Some("v")) + qTree.d() shouldBe OnExpr(toStr("deeper"), List(toStr("deep"))) + qTree.d() shouldBe CallArrowExpr(Some("v"), Some(toAb("Local")), "gt", Nil) + qTree.d() shouldBe ReturnExpr(toVar("v")) + // genC function + qTree.d() shouldBe FuncExpr("genC", List(toArgSc("val", string)), Some(boolSc), Some("two")) + qTree.d() shouldBe CallArrowExpr(Some("one"), Some(toAb("Local")), "gt", List()) + qTree.d() shouldBe OnExpr(toStr("smth"), List(toStr("else"))) + qTree.d() shouldBe CallArrowExpr(Some("two"), None, "tryGen", List()) + qTree.d() shouldBe CallArrowExpr(Some("three"), Some(toAb("Local")), "gt", List()) + qTree.d() shouldBe ReturnExpr(toVar("two")) - DefFunc.`deffunc`.parseAll(func).right.value should be( - DefFunc[Id, HNil]( - getTimeHead, - NonEmptyList.of( - On[Id, HNil]( - VarLambda[Id]("peer", Nil), - NonEmptyList.of( - AbilityId[Id, HNil]("Peer", Literal[Id]("\"peer\"", LiteralType.string), HNil), - Extract[Id, HNil]("t", AbilityFuncCall[Id, HNil]("Peer", "timestamp", "Peer.timestamp", Nil, HNil), HNil) - ), - HNil - ) - ), - HNil - ) - ) - }*/ + val f = + Semantics.generateModel(tree).toList.head.asInstanceOf[ScriptModel] + + val funcs = f.funcs.toList + val funcTryGen = funcs.head + val funcOpTryGen = funcTryGen.body.resolveTopology() + + val qTryGen = funcOpTryGen.tree.foldLeft(mutable.Queue.empty[OpTag]) { case (acc, tag) => + acc.enqueue(tag) + } + + val smth = LiteralModel("\"smth\"") + val smthOn = OnTag(smth, Nil) + val smthCall = CallServiceTag( + LiteralModel("\"op\""), + "identity", + emptyCall, + Some(LiteralModel("\"smth\"")) + ) + + val elseL = LiteralModel("\"else\"") + val elseOn = OnTag(elseL, Nil) + val elseCall = CallServiceTag( + LiteralModel("\"op\""), + "identity", + emptyCall, + Some(LiteralModel("\"else\"")) + ) + + val deeper = LiteralModel("\"deeper\"") + val deeperOn = OnTag(deeper, Nil) + val deeperCall = CallServiceTag( + LiteralModel("\"op\""), + "identity", + emptyCall, + Some(LiteralModel("\"deeper\"")) + ) + val deep = LiteralModel("\"deep\"") + val deepOn = OnTag(deep, Nil) + val deepCall = CallServiceTag( + LiteralModel("\"op\""), + "identity", + emptyCall, + Some(LiteralModel("\"deep\"")) + ) + val local = LiteralModel("\"local\"") + + // tag that we will go to 'deeper' + qTryGen.d() shouldBe deeperOn + // move to 'deep' node + qTryGen.d() shouldBe deepOn + qTryGen.d() shouldBe deepCall + // move to 'deeper' node + qTryGen.d() shouldBe deeperOn + qTryGen.d() shouldBe deeperCall + // a call must be with `on` too, so we need to call 'deeper' after we will go out of the scope + qTryGen.d() shouldBe CallServiceTag(local, "gt", Call(Nil, Some("v")), Some(deeper)) + qTryGen.d() shouldBe deepOn + // return to 'deeper' node + qTryGen.d() shouldBe deeperOn + qTryGen.d() shouldBe deeperCall + // return to 'deep' node + qTryGen.d() shouldBe deepOn + qTryGen.d() shouldBe deepCall + + val funcGenComplex = f.funcs.toList(1) + val funcOpGenComplex = funcGenComplex.body.resolveTopology() + + val qGenComplex = funcOpGenComplex.tree.foldLeft(mutable.Queue.empty[OpTag]) { + case (acc, tag) => + acc.enqueue(tag) + } + + qGenComplex.d() shouldBe SeqTag + qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("one")), None) + // tag that we will go to 'smth' + qGenComplex.d() shouldBe smthOn + // move to 'else' node + qGenComplex.d() shouldBe elseOn + qGenComplex.d() shouldBe elseCall + // move to 'smth' node + qGenComplex.d() shouldBe smthOn + qGenComplex.d() shouldBe smthCall + qGenComplex.d() shouldBe CallArrowTag(None, "tryGen", Call(List(), Some("two"))) + qGenComplex.d() shouldBe elseOn + // return to 'smth' node + qGenComplex.d() shouldBe smthOn + qGenComplex.d() shouldBe smthCall + // return to 'else' node + qGenComplex.d() shouldBe elseOn + qGenComplex.d() shouldBe elseCall + qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None) + } }