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,13 +70,20 @@ 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)) {
case ((aggrA, aggrTail), child) =>
traverseA(child, aggrA)(f).value.map(aggrTail.append) 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,7 +35,8 @@ 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])) {
case (a, b) =>
(a, b).mapN { (a, b).mapN {
case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc => case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc =>
(prev :+: next) :: tail (prev :+: next) :: tail
@ -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)
"function" should "parse getTime as a whole" in { val ifBody =
val func = checkHeadGetTail(
"""func getTime(peer: PeerId, ret: u32 -> ()) -> string: funcBody.head,
| on peer: IfExpr(toVarLambda("peer", List("id")), EqOp[Id](true), toVar("other")),
| Peer "peer" 3
| t <- Peer.timestamp() ).toList
| ret(t)""".stripMargin
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))))
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
)
)
} }
"function" should "parse getTime with no return" 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()""".stripMargin |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( 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( qTree.d() shouldBe RootExpr()
AbilityId[Id, HNil]("Peer", Literal[Id]("\"peer\"", LiteralType.string), HNil), // Local service
Extract[Id, HNil]("t", AbilityFuncCall[Id, HNil]("Peer", "timestamp", "Peer.timestamp", Nil, HNil), HNil) qTree.d() shouldBe ServiceExpr(toAb("Local"), Some(toStr("local")))
), qTree.d() shouldBe ArrowTypeExpr("gt", toArrowType(Nil, Some(scToBt(bool))))
HNil 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)
HNil 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"))
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)
}
} }