Fix via path, complex tests (#27)

This commit is contained in:
Dima 2021-04-05 10:40:51 +03:00 committed by GitHub
parent cad921a958
commit 992af16a08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 323 additions and 118 deletions

View File

@ -2,7 +2,7 @@ version = 2.7.5
docstrings = JavaDoc docstrings = JavaDoc
maxColumn = 120 maxColumn = 100
align = none align = none
align { align {

View File

@ -11,13 +11,15 @@ object Aqua {
def parse(input: String): ValidatedNec[AquaError, Ast[Span.F]] = def parse(input: String): ValidatedNec[AquaError, Ast[Span.F]] =
Ast.fromString[Span.F](input) Ast.fromString[Span.F](input)
def validate(input: String): ValidatedNec[AquaError, Model] = def generateModel(input: String): ValidatedNec[AquaError, Model] =
parse(input).andThen(ast => Semantics.validate(ast).leftMap(_.map(ts => CompilerError(ts._1.unit._1, ts._2)))) 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] = def generate(input: String, air: Boolean): ValidatedNec[AquaError, String] =
validate(input).map { generateModel(input).map {
case g: ScriptModel => case m: ScriptModel =>
if (air) g.generateAir else g.generateTypescript if (air) m.generateAir else m.generateTypescript
case _ => "//No input given" case _ => "//No input given"
} }

View File

@ -50,7 +50,8 @@ case class FuncCallable(
// We have some names in scope (forbiddenNames), can't introduce them again; so find new names // We have some names in scope (forbiddenNames), can't introduce them again; so find new names
val shouldRename = findNewNames(forbiddenNames, treeDefines) val shouldRename = findNewNames(forbiddenNames, treeDefines)
// If there was a collision, rename exports and usages with new names // 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 // Result could be derived from arguments, or renamed; take care about that
val result = ret.map(_._1).map(_.resolveWith(argsToData)).map { 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) => case ((noNames, resolvedExports), CallArrowTag(None, fn, c)) if allArrows.contains(fn) =>
// Apply arguments to a function recursion // Apply arguments to a function recursion
val (appliedOp, value) = 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 // Function defines new names inside its body need to collect them
// TODO: actually it's done and dropped so keep and pass it instead // 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 (noNames ++ newNames, resolvedExports ++ c.exportTo.zip(value)) -> appliedOp.tree
case (acc @ (_, resolvedExports), tag) => case (acc @ (_, resolvedExports), tag) =>
// All the other tags are already resolved and need no substitution // All the other tags are already resolved and need no substitution
acc -> Cofree[Chain, OpTag](tag.mapValues(_.resolveWith(resolvedExports)), Eval.now(Chain.empty)) acc -> Cofree[Chain, OpTag](
tag.mapValues(_.resolveWith(resolvedExports)),
Eval.now(Chain.empty)
)
} }
.map { case ((_, resolvedExports), b) => .map { case ((_, resolvedExports), b) =>
// If return value is affected by any of internal functions, resolve it // If return value is affected by any of internal functions, resolve it
@ -159,13 +165,7 @@ case class FuncCallable(
).value._1 ).value._1
) ++ Chain.fromSeq(returnCallback.toSeq) ) ++ Chain.fromSeq(returnCallback.toSeq)
) )
.resolveTopology(noop) .resolveTopology()
def noop(peerId: ValueModel): FuncOp =
FuncOp.wrap(
OnTag(peerId, Nil),
FuncOp.leaf(CallServiceTag(LiteralModel("\"op\""), "identity", Call(Nil, None), Some(peerId)))
)
def generateTsCall: Call = def generateTsCall: Call =
Call( Call(

View File

@ -1,6 +1,5 @@
package aqua.model package aqua.model
import aqua.model.FuncOp.wrap
import cats.Eval import cats.Eval
import cats.data.Chain import cats.data.Chain
import cats.free.Cofree import cats.free.Cofree
@ -16,8 +15,10 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
Cofree.cata(tree)(folder) Cofree.cata(tree)(folder)
def definesValueNames: Eval[Set[String]] = cata[Set[String]] { def definesValueNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, _, Call(_, Some(export))), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) case (CallArrowTag(_, _, Call(_, Some(export))), acc) =>
case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _)) 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 (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _)) case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
} }
@ -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) { FuncOp(FuncOp.transformWithPath(tree) {
case (path, c: CallServiceTag) => case (path, c: CallServiceTag) =>
Cofree[Chain, OpTag]( Cofree[Chain, OpTag](
@ -55,11 +56,12 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
OnTag(pid, Nil), OnTag(pid, Nil),
Eval.now( Eval.now(
Chain.fromSeq( 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 = def :+:(prev: FuncOp): FuncOp =
@ -68,14 +70,21 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
object FuncOp { 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)( def traverseA[A](cf: Cofree[Chain, OpTag], init: A)(
f: (A, OpTag) => (A, Cofree[Chain, OpTag]) f: (A, OpTag) => (A, Cofree[Chain, OpTag])
): Eval[(A, Cofree[Chain, OpTag])] = { ): Eval[(A, Cofree[Chain, OpTag])] = {
val (headA, head) = f(init, cf.head) val (headA, head) = f(init, cf.head)
// TODO: it should be in standard library, with some other types // TODO: it should be in standard library, with some other types
cf.tail cf.tail
.map(_.foldLeft[(A, Chain[Cofree[Chain, OpTag]])]((headA, head.tailForced)) { case ((aggrA, aggrTail), child) => .map(_.foldLeft[(A, Chain[Cofree[Chain, OpTag]])]((headA, head.tailForced)) {
traverseA(child, aggrA)(f).value.map(aggrTail.append) case ((aggrA, aggrTail), child) =>
traverseA(child, aggrA)(f).value.map(aggrTail.append)
}) })
.map(_.map(ch => head.copy(tail = Eval.now(ch)))) .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)( def transformWithPath(cf: Cofree[Chain, OpTag], path: List[OpTag] = Nil)(
f: (List[OpTag], OpTag) => Cofree[Chain, OpTag] f: (List[OpTag], OpTag) => Cofree[Chain, OpTag]
): Cofree[Chain, OpTag] = { ): Cofree[Chain, OpTag] = {
val h = f(path, cf.head) val newCf = f(path, cf.head)
Cofree[Chain, OpTag]( Cofree[Chain, OpTag](
h.head, newCf.head,
(h.tail, cf.tail) (newCf.tail, cf.tail)
.mapN(_ ++ _) .mapN(_ ++ _)
// IF make foldLeft here, will be possible to get info from prev sibling // 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)))
) )
} }

View File

@ -4,7 +4,12 @@ import aqua.model.{FuncOp, Model}
import aqua.parser.lexer.Token import aqua.parser.lexer.Token
import aqua.parser.{Ast, Expr} import aqua.parser.{Ast, Expr}
import aqua.semantics.rules.ReportError import aqua.semantics.rules.ReportError
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.names.{NameOp, NamesAlgebra, NamesInterpreter, NamesState}
import aqua.semantics.rules.types.{TypeOp, TypesAlgebra, TypesInterpreter, TypesState} import aqua.semantics.rules.types.{TypeOp, TypesAlgebra, TypesInterpreter, TypesState}
import cats.Eval import cats.Eval
@ -30,12 +35,13 @@ object Semantics {
// TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones // TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones
// Then foldLeft just like now // Then foldLeft just like now
inners inners
.foldRight[Free[G, List[Model]]](Free.pure[G, List[Model]](List.empty[Model])) { case (a, b) => .foldRight[Free[G, List[Model]]](Free.pure[G, List[Model]](List.empty[Model])) {
(a, b).mapN { case (a, b) =>
case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc => (a, b).mapN {
(prev :+: next) :: tail case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc =>
case (prev, acc) => prev :: acc (prev :+: next) :: tail
} case (prev, acc) => prev :: acc
}
} }
.map(_.reduceLeftOption(_ |+| _).getOrElse(Model.empty("AST is empty"))) .map(_.reduceLeftOption(_ |+| _).getOrElse(Model.empty("AST is empty")))
) )
@ -58,13 +64,15 @@ object Semantics {
import monocle.macros.syntax.all._ import monocle.macros.syntax.all._
implicit val re: ReportError[F, CompilerState[F]] = 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) implicit val ns: Lens[CompilerState[F], NamesState[F]] = GenLens[CompilerState[F]](_.names)
val names = new NamesInterpreter[F, CompilerState[F]]() 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]]() val abilities = new AbilitiesInterpreter[F, CompilerState[F]]()
@ -78,11 +86,13 @@ object Semantics {
free.foldMap[State[CompilerState[F], *]](interpreter) 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) (transpile[F] _ andThen interpret[F])(ast)
.run(CompilerState[F]()) .run(CompilerState[F]())
.map { case (state, gen) => .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 .value
} }

View File

@ -5,6 +5,7 @@ import aqua.parser.expr.OnExpr
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra
import cats.data.Chain
import cats.syntax.flatMap._ import cats.syntax.flatMap._
import cats.syntax.functor._ import cats.syntax.functor._
@ -24,7 +25,30 @@ class OnSem[F[_]](val expr: OnExpr[F]) extends AnyVal {
(_: Unit, ops: Model) => (_: Unit, ops: Model) =>
A.endScope() as (ops match { A.endScope() as (ops match {
case op: FuncOp => 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) case m => Model.error("On body is not an op, it's " + m)
}) })
) )

View File

@ -1,5 +1,6 @@
package aqua package aqua
import aqua.model.Call
import aqua.parser.expr.{ import aqua.parser.expr.{
AbilityIdExpr, AbilityIdExpr,
AliasExpr, AliasExpr,
@ -37,17 +38,23 @@ import aqua.semantics.LiteralType.{bool, number, string}
import aqua.semantics.{LiteralType, ScalarType} import aqua.semantics.{LiteralType, ScalarType}
import org.scalatest.EitherValues import org.scalatest.EitherValues
import scala.collection.mutable
import scala.language.implicitConversions import scala.language.implicitConversions
object Utils { object Utils {
implicit def toAb(str: String): Ability[Id] = Ability[Id](str) implicit def toAb(str: String): Ability[Id] = Ability[Id](str)
implicit def toName(str: String): Name[Id] = Name[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 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] = implicit def toVarLambda(name: String, fields: List[String]): VarLambda[Id] =
VarLambda[Id](toName(name), toFields(fields)) VarLambda[Id](toName(name), toFields(fields))
implicit def toLiteral(name: String, t: LiteralType): Literal[Id] = Literal[Id](name, t) 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) implicit def toCustomType(str: String): CustomTypeToken[Id] = CustomTypeToken[Id](str)
def toArrayType(str: String): ArrayTypeToken[Id] = ArrayTypeToken[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) 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 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) 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 { trait Utils extends EitherValues {
val emptyCall = Call(Nil, None)
def parseExpr(str: String): CallArrowExpr[Id] = def parseExpr(str: String): CallArrowExpr[Id] =
CallArrowExpr.p[Id].parseAll(str).value CallArrowExpr.p[Id].parseAll(str).value
@ -110,4 +129,8 @@ trait Utils extends EitherValues {
ArrowTypeExpr.p[Id].parseAll(str).value ArrowTypeExpr.p[Id].parseAll(str).value
def funcExpr(str: String): FuncExpr[Id] = FuncExpr.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()
}
} }

View File

@ -1,19 +1,44 @@
package aqua.parser package aqua.parser
import aqua.Utils import aqua.model.{
import aqua.parser.Ast.Tree Call,
import aqua.parser.expr.FuncExpr CallArrowTag,
import aqua.parser.lexer.{ArrowTypeToken, BasicTypeToken} 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 aqua.semantics.ScalarType.{bool, u64}
import cats.{Eval, Id} import cats.{Eval, Id}
import cats.syntax.traverse._ import cats.syntax.traverse._
import cats.syntax.foldable._
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import aqua.parser.lift.LiftParser.Implicits.idLiftParser import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.semantics.Semantics
import aqua.semantics.Semantics.{folder, Alg} import aqua.semantics.Semantics.{folder, Alg}
import cats.data.Chain import cats.data.{Chain, NonEmptyChain, Validated}
import cats.free.Cofree import cats.free.Cofree
import scala.collection.mutable
import scala.language.implicitConversions import scala.language.implicitConversions
class FuncExprSpec extends AnyFlatSpec with Matchers with Utils { 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} import aqua.semantics.ScalarType.{string, u32}
"func header" should "parse" in { "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)) 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("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 = 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("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) val arrowToken3 = ArrowTypeToken[Id]((), List(BasicTypeToken[Id](u32)), None)
@ -44,25 +86,26 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
None None
) )
) )
} }
"on" should "parse on x: y" in { def checkHeadGetTail(
val script = tree: Cofree[Chain, Expr[Id]],
"""func a(): headCheck: Expr[Id],
| on peer.id: lengthCheck: Int
| x <- Ab.func() ): Chain[Cofree[Chain, Expr[Id]]] = {
| Peer "some id" tree.head should be(headCheck)
| call(true)""".stripMargin val tail = tree.tailForced
tail.length should be(lengthCheck)
val c: Cofree[Chain, Expr[Id]] = FuncExpr.ast[Id](Indent()).parseAll(script).value tail
val a = Ast(c).cata(folder[Id, Alg[Id, *]]).value
// a.run
println(a)
FuncExpr.ast[Id](Indent()).parseAll(script).isRight should be(true)
} }
"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 = val script =
"""func a(): """func a():
| if peer.id == other: | if peer.id == other:
@ -70,59 +113,153 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
| Peer "some id" | Peer "some id"
| call(true)""".stripMargin | 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 { "some" should "other" in {
val func = val script =
"""func getTime(peer: PeerId, ret: u32 -> ()) -> string: """service Local("local"):
| on peer: | gt: -> bool
| Peer "peer" |
| t <- Peer.timestamp() |func tryGen() -> bool:
| ret(t)""".stripMargin | 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( val tree = parser[Id](Indent()).parseAll(script).value
DefFunc[Id, HNil](
getTimeHead, val qTree = tree.tree.foldLeft(mutable.Queue.empty[Expr[Id]]) { case (acc, tag) =>
NonEmptyList.of( acc.enqueue(tag)
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
)
)
} }
"function" should "parse getTime with no return" in { qTree.d() shouldBe RootExpr()
val func = // Local service
"""func getTime(peer: PeerId, ret: u32 -> ()) -> string: qTree.d() shouldBe ServiceExpr(toAb("Local"), Some(toStr("local")))
| on peer: qTree.d() shouldBe ArrowTypeExpr("gt", toArrowType(Nil, Some(scToBt(bool))))
| Peer "peer" qTree.d() shouldBe FuncExpr("tryGen", Nil, Some(scToBt(bool)), Some("v"))
| t <- Peer.timestamp()""".stripMargin 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( val f =
DefFunc[Id, HNil]( Semantics.generateModel(tree).toList.head.asInstanceOf[ScriptModel]
getTimeHead,
NonEmptyList.of( val funcs = f.funcs.toList
On[Id, HNil]( val funcTryGen = funcs.head
VarLambda[Id]("peer", Nil), val funcOpTryGen = funcTryGen.body.resolveTopology()
NonEmptyList.of(
AbilityId[Id, HNil]("Peer", Literal[Id]("\"peer\"", LiteralType.string), HNil), val qTryGen = funcOpTryGen.tree.foldLeft(mutable.Queue.empty[OpTag]) { case (acc, tag) =>
Extract[Id, HNil]("t", AbilityFuncCall[Id, HNil]("Peer", "timestamp", "Peer.timestamp", Nil, HNil), HNil) acc.enqueue(tag)
), }
HNil
) val smth = LiteralModel("\"smth\"")
), val smthOn = OnTag(smth, Nil)
HNil 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)
}
} }