39 fix callbacks (#43)

* Project layout reorganized

* Topology resolution extracted to a separate file

* FuncResolved class

* aqua.model.transform

* Basic bubbling

* Get more context into topology resolver

* Failing Topology test

* Get back in seq

* OnTag.via: List changed to Chain, as via is reversed and concatenated quite frequently
This commit is contained in:
Dmitry Kurinskiy 2021-04-06 19:01:25 +03:00 committed by GitHub
parent 75595b99bd
commit 0de43b470e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 937 additions and 723 deletions

View File

@ -1,4 +1,4 @@
package aqua.generator
package aqua.backend.air
import cats.Show
import cats.syntax.show._
@ -81,7 +81,8 @@ object Air {
case class Match(left: DataView, right: DataView, instruction: Air) extends Air(Keyword.Match)
case class Mismatch(left: DataView, right: DataView, instruction: Air) extends Air(Keyword.Mismatch)
case class Mismatch(left: DataView, right: DataView, instruction: Air)
extends Air(Keyword.Mismatch)
case class Par(left: Air, right: Air) extends Air(Keyword.Par)
@ -89,7 +90,8 @@ object Air {
case class Xor(left: Air, right: Air) extends Air(Keyword.Xor)
case class Call(triplet: Triplet, args: List[DataView], result: Option[String]) extends Air(Keyword.Call)
case class Call(triplet: Triplet, args: List[DataView], result: Option[String])
extends Air(Keyword.Call)
private def show(depth: Int, air: Air): String = {
def showNext(a: Air) = show(depth + 1, a)
@ -101,7 +103,8 @@ object Air {
case Air.Next(label) s" $label"
case Air.Fold(iter, label, inst) s" ${iter.show} $label\n${showNext(inst)}$space"
case Air.Match(left, right, inst) s" ${left.show} ${right.show}\n${showNext(inst)}$space"
case Air.Mismatch(left, right, inst) s" ${left.show} ${right.show}\n${showNext(inst)}$space"
case Air.Mismatch(left, right, inst)
s" ${left.show} ${right.show}\n${showNext(inst)}$space"
case Air.Par(l, r) s"\n${showNext(l)}${showNext(r)}$space"
case Air.Seq(l, r) s"\n${showNext(l)}${showNext(r)}$space"
case Air.Xor(l, r) s"\n${showNext(l)}${showNext(r)}$space"

View File

@ -1,23 +1,17 @@
package aqua.generator
package aqua.backend.air
import aqua.model.{
import aqua.model._
import aqua.model.body.{
Call,
CallArrowTag,
CallServiceTag,
ForTag,
InitPeerIdModel,
IntoArrayModel,
IntoFieldModel,
LambdaModel,
LiteralModel,
MatchMismatchTag,
NextTag,
OnTag,
OpTag,
ParTag,
SeqTag,
ValueModel,
VarModel,
XorTag
}
import cats.Eval

View File

@ -0,0 +1,22 @@
package aqua.backend.air
import aqua.model.FuncResolved
import aqua.model.transform.BodyConfig
case class FuncAirGen(func: FuncResolved) {
/**
* Generates AIR from the function body as it is, with no modifications and optimizations
*/
def generateAir: Air =
AirGen(func.func.body.tree).generate
/**
* Generates AIR from the optimized function body, assuming client is behind a relay
* @return
*/
def generateClientAir(conf: BodyConfig = BodyConfig()): Air =
AirGen(
func.forClient(conf)
).generate
}

View File

@ -0,0 +1,21 @@
package aqua.backend.ts
import aqua.model.ScriptModel
import aqua.model.transform.BodyConfig
import cats.data.Chain
case class TypescriptFile(script: ScriptModel) {
def funcs: Chain[TypescriptFunc] = script.resolveFunctions.map(TypescriptFunc(_))
def generateTS(conf: BodyConfig = BodyConfig()): String =
TypescriptFile.Header + "\n\n" + funcs.map(_.generateTypescript(conf)).toList.mkString("\n\n")
}
object TypescriptFile {
val Header: String =
"""import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
|import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
|""".stripMargin
}

View File

@ -1,42 +1,27 @@
package aqua.generator
package aqua.backend.ts
import aqua.model.FuncCallable
import aqua.semantics.{ArrayType, ArrowType, DataType, LiteralType, ProductType, ScalarType, Type}
import aqua.backend.air.FuncAirGen
import aqua.model.FuncResolved
import aqua.model.transform.BodyConfig
import aqua.types._
import cats.syntax.show._
case class TypescriptFunc(name: String, func: FuncCallable, tsAir: Air) {
case class TypescriptFunc(func: FuncResolved) {
def typeToTs(t: Type): String = t match {
case ArrayType(t) => typeToTs(t) + "[]"
case pt: ProductType => s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
case st: ScalarType if ScalarType.number(st) => "number"
case ScalarType.bool => "boolean"
case ScalarType.string => "string"
case lt: LiteralType if lt.oneOf.exists(ScalarType.number) => "number"
case lt: LiteralType if lt.oneOf(ScalarType.bool) => "boolean"
case lt: LiteralType if lt.oneOf(ScalarType.string) => "string"
case _: DataType => "any"
case at: ArrowType =>
s"(${argsToTs(at)}) => ${at.res
.fold("void")(typeToTs)}"
}
def argsToTs(at: ArrowType): String =
at.args.map(typeToTs).zipWithIndex.map(_.swap).map(kv => "arg" + kv._1 + ": " + kv._2).mkString(", ")
def argsCallToTs(at: ArrowType): String =
at.args.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
import TypescriptFunc._
def argsTypescript: String =
func.args.map {
func.func.args.map {
case (n, Left(t)) => s"${n}: " + typeToTs(t)
case (n, Right(at)) => s"${n}: " + typeToTs(at)
}.mkString(", ")
def generateTypescript: String = {
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
val returnCallback = func.ret.map { case (dv, t) =>
s"""h.on('${func.callbackService}', '${func.respFuncName}', (args) => {
val tsAir = FuncAirGen(func).generateClientAir(conf)
val returnCallback = func.func.ret.map { case (dv, t) =>
s"""h.on('${conf.callbackService}', '${conf.respFuncName}', (args) => {
| const [res] = args;
| resolve(res);
|});
@ -44,21 +29,21 @@ case class TypescriptFunc(name: String, func: FuncCallable, tsAir: Air) {
}
val setCallbacks = func.args.map {
val setCallbacks = func.func.args.map {
case (argName, Left(t)) =>
s"""h.on('${func.getDataService}', '$argName', () => {return $argName;});"""
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
case (argName, Right(at)) =>
s"""h.on('${func.callbackService}', '$argName', (args) => {return $argName(${argsCallToTs(
s"""h.on('${conf.callbackService}', '$argName', (args) => {return $argName(${argsCallToTs(
at
)});});"""
}.mkString("\n")
val retType = func.ret
val retType = func.func.ret
.map(_._2)
.fold("void")(typeToTs)
s"""
|export async function ${name}(client: FluenceClient${if (func.args.isEmpty) ""
|export async function ${func.name}(client: FluenceClient${if (func.func.args.isEmpty) ""
else ", "}${argsTypescript}): Promise<$retType> {
| let request;
| const promise = new Promise<$retType>((resolve, reject) => {
@ -69,7 +54,7 @@ case class TypescriptFunc(name: String, func: FuncCallable, tsAir: Air) {
| `,
| )
| .configHandler((h) => {
| h.on('${func.getDataService}', 'relay', () => {
| h.on('${conf.getDataService}', 'relay', () => {
| return client.relayPeerId;
| });
| h.on('getRelayService', 'hasReleay', () => {// Not Used
@ -96,3 +81,34 @@ case class TypescriptFunc(name: String, func: FuncCallable, tsAir: Air) {
}
}
object TypescriptFunc {
def typeToTs(t: Type): String = t match {
case ArrayType(t) => typeToTs(t) + "[]"
case pt: ProductType =>
s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
case st: ScalarType if ScalarType.number(st) => "number"
case ScalarType.bool => "boolean"
case ScalarType.string => "string"
case lt: LiteralType if lt.oneOf.exists(ScalarType.number) => "number"
case lt: LiteralType if lt.oneOf(ScalarType.bool) => "boolean"
case lt: LiteralType if lt.oneOf(ScalarType.string) => "string"
case _: DataType => "any"
case at: ArrowType =>
s"(${argsToTs(at)}) => ${at.res
.fold("void")(typeToTs)}"
}
def argsToTs(at: ArrowType): String =
at.args
.map(typeToTs)
.zipWithIndex
.map(_.swap)
.map(kv => "arg" + kv._1 + ": " + kv._2)
.mkString(", ")
def argsCallToTs(at: ArrowType): String =
at.args.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
}

View File

@ -1,29 +1,84 @@
val dottyVersion = "2.13.5"
scalaVersion := dottyVersion
//val dottyVersion = "3.0.0-RC1"
val catsV = "2.4.2"
val monocleV = "3.0.0-M3"
val aquaV = "0.1.0"
lazy val root = project
.in(file("."))
val catsV = "2.4.2"
val catsParseV = "0.3.1"
val monocleV = "3.0.0-M3"
val scalaTestV = "3.2.5"
val fs2V = "3.0.0-M7"
name := "aqua-hll"
mainClass in (Compile, run) := Some("aqua.Main")
mainClass in assembly := Some("aqua.Main")
assemblyJarName in assembly := "aqua-hll.jar"
val commons = Seq(
version := aquaV,
scalaVersion := dottyVersion,
libraryDependencies += "org.scalatest" %% "scalatest" % scalaTestV % Test,
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full)
)
lazy val cli = project
.settings(commons: _*)
.settings(
name := "aqua-hll",
version := "0.1.0",
scalaVersion := dottyVersion,
mainClass in (Compile, run) := Some("aqua.Main"),
mainClass in assembly := Some("aqua.Main"),
assemblyJarName in assembly := "aqua-hll.jar",
libraryDependencies ++= Seq(
"com.github.scopt" %% "scopt" % "4.0.1",
"org.typelevel" %% "cats-effect" % "3.0.0-RC2",
"org.typelevel" %% "cats-parse" % "0.3.1",
"org.typelevel" %% "cats-free" % catsV,
"com.github.julien-truffaut" %% "monocle-core" % monocleV,
"com.github.julien-truffaut" %% "monocle-macro" % monocleV,
"co.fs2" %% "fs2-core" % "3.0.0-M7",
"co.fs2" %% "fs2-io" % "3.0.0-M7",
"org.scalatest" %% "scalatest" % "3.2.5" % Test
"com.github.scopt" %% "scopt" % "4.0.1",
"org.typelevel" %% "cats-effect" % "3.0.0-RC2",
"co.fs2" %% "fs2-core" % fs2V,
"co.fs2" %% "fs2-io" % fs2V
)
)
.dependsOn(semantics, `backend-air`, `backend-ts`)
lazy val types = project
.settings(commons)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % catsV
)
)
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full)
lazy val parser = project
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-parse" % catsParseV,
"org.typelevel" %% "cats-free" % catsV
)
)
.dependsOn(types)
lazy val model = project
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-free" % catsV
)
)
.dependsOn(types)
lazy val semantics = project
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"com.github.julien-truffaut" %% "monocle-core" % monocleV,
"com.github.julien-truffaut" %% "monocle-macro" % monocleV
)
)
.dependsOn(model, parser)
lazy val `backend-air` = project
.in(file("backend/air"))
.settings(commons: _*)
.dependsOn(model)
lazy val `backend-ts` = project
.in(file("backend/ts"))
.settings(commons: _*)
.dependsOn(`backend-air`)

View File

@ -1,15 +1,18 @@
package aqua
import aqua.backend.air.FuncAirGen
import aqua.backend.ts.TypescriptFile
import aqua.model.{Model, ScriptModel}
import aqua.parser.Ast
import cats.data.ValidatedNec
import aqua.parser.lift.Span
import aqua.semantics.Semantics
import cats.syntax.show._
object Aqua {
def parse(input: String): ValidatedNec[AquaError, Ast[Span.F]] =
Ast.fromString[Span.F](input)
Ast.fromString[Span.F](input).leftMap(_.map(pe => SyntaxError(pe.failedAtOffset, pe.expected)))
def generateModel(input: String): ValidatedNec[AquaError, Model] =
parse(input).andThen(ast =>
@ -18,8 +21,20 @@ object Aqua {
def generate(input: String, air: Boolean): ValidatedNec[AquaError, String] =
generateModel(input).map {
case m: ScriptModel if air =>
// TODO it's meaningless to compile all functions to air, as resulting .air file is incorrect; only one function should be taken
m.resolveFunctions
.map(FuncAirGen)
.map(g =>
// add function name before body
s";; function name: ${g.func.name}\n\n" + g.generateAir.show
)
.toList
.mkString("\n\n\n")
case m: ScriptModel =>
if (air) m.generateAir else m.generateTypescript
TypescriptFile(m).generateTS()
case _ => "//No input given"
}

View File

@ -1,6 +1,7 @@
package aqua.model
import aqua.semantics.{ArrowType, DataType, Type}
import aqua.model.body.{Call, CallArrowTag, FuncOp, OpTag}
import aqua.types.{ArrowType, DataType, Type}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
@ -94,97 +95,4 @@ case class FuncCallable(
}
}
val getDataService: String = "getDataSrv"
val callbackService: String = "callbackSrv"
val respFuncName = "response"
val relayVarName = "relay"
val callbackId: ValueModel = LiteralModel("\"" + callbackService + "\"")
val returnCallback: Option[FuncOp] = ret.map { case (dv, t) =>
viaRelay(
FuncOp.leaf(
CallServiceTag(
callbackId,
respFuncName,
Call(
(dv, t) :: Nil,
None
)
)
)
)
}
// TODO it's an overkill
def initPeerCallable(name: String, arrowType: ArrowType): FuncCallable =
FuncCallable(
viaRelay(
FuncOp.leaf(
CallServiceTag(
callbackId,
name,
Call(
arrowType.args.zipWithIndex.map { case (t, i) =>
VarModel(s"arg$i") -> t
},
arrowType.res.map(_ => "init_call_res")
)
)
)
),
arrowType.args.zipWithIndex.map {
case (t: DataType, i) => s"arg$i" -> Left(t)
case (t: ArrowType, i) => s"arg$i" -> Right(t)
},
arrowType.res.map(VarModel("init_call_res") -> _),
Map.empty
)
// TODO rename
def generateTsModel: FuncOp =
FuncOp
.node(
SeqTag,
Chain
.fromSeq(
args.collect { case (argName, Left(_)) =>
getDataOp(argName)
} :+ getDataOp(relayVarName)
)
.append(
apply(
generateTsCall,
args.collect { case (argName, Right(arrowType)) =>
argName -> initPeerCallable(argName, arrowType)
}.toMap,
args.collect { case (argName, Left(_)) =>
argName
}.foldLeft(Set(relayVarName))(_ + _)
).value._1
) ++ Chain.fromSeq(returnCallback.toSeq)
)
.resolveTopology()
def generateTsCall: Call =
Call(
args.map { case (k, e) =>
(VarModel(k), e.fold(identity, identity))
},
None
)
def getDataOp(name: String): FuncOp =
FuncOp.leaf(
CallServiceTag(
LiteralModel("\"" + getDataService + "\""),
name,
Call(Nil, Some(name))
)
)
def viaRelay(op: FuncOp): FuncOp =
FuncOp.wrap(OnTag(InitPeerIdModel, VarModel(relayVarName) :: Nil), op)
}

View File

@ -0,0 +1,16 @@
package aqua.model
import aqua.model.body.FuncOp
import aqua.types.{ArrowType, DataType, Type}
case class FuncModel(
name: String,
args: List[(String, Either[DataType, ArrowType])],
ret: Option[(ValueModel, Type)],
body: FuncOp
) extends Model {
def captureArrows(arrows: Map[String, FuncCallable]): FuncCallable =
FuncCallable(body, args, ret, arrows)
}

View File

@ -0,0 +1,10 @@
package aqua.model
import aqua.model.body.OpTag
import aqua.model.transform.{BodyConfig, ForClient}
import cats.data.Chain
import cats.free.Cofree
case class FuncResolved(name: String, func: FuncCallable) {
def forClient(conf: BodyConfig): Cofree[Chain, OpTag] = ForClient(this, conf)
}

View File

@ -1,5 +1,6 @@
package aqua.model
import aqua.model.body.FuncOp
import cats.data.Chain
import cats.kernel.Semigroup

View File

@ -0,0 +1,21 @@
package aqua.model
import cats.data.Chain
case class ScriptModel(funcs: Chain[FuncModel]) extends Model {
def enqueue(m: Model): ScriptModel = m match {
case f: FuncModel => copy(funcs.append(f))
case _ => this
}
def resolveFunctions: Chain[FuncResolved] =
funcs
.foldLeft((Map.empty[String, FuncCallable], Chain.empty[FuncResolved])) {
case ((funcsAcc, outputAcc), func) =>
val fr = func.captureArrows(funcsAcc)
funcsAcc.updated(func.name, fr) -> outputAcc.append(FuncResolved(func.name, fr))
}
._2
}

View File

@ -1,5 +1,6 @@
package aqua.model
package aqua.model.body
import aqua.model.{LiteralModel, Model, ValueModel, VarModel}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
@ -42,28 +43,6 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
)
)
def resolveTopology(): FuncOp =
FuncOp(FuncOp.transformWithPath(tree) {
case (path, c: CallServiceTag) =>
Cofree[Chain, OpTag](
c.copy(peerId = path.collectFirst { case OnTag(peerId, _) =>
peerId
}),
Eval.now(Chain.empty)
)
case (_, OnTag(pid, via)) if via.nonEmpty =>
Cofree[Chain, OpTag](
OnTag(pid, Nil),
Eval.now(
Chain.fromSeq(
via.map(FuncOp.noop).map(_.tree)
)
)
)
case (_, t) =>
Cofree[Chain, OpTag](t, Eval.now(Chain.empty))
})
def :+:(prev: FuncOp): FuncOp =
FuncOp.RightAssocSemi.combine(prev, this)
}
@ -71,10 +50,7 @@ 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)))
)
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])
@ -89,19 +65,7 @@ object FuncOp {
.map(_.map(ch => head.copy(tail = Eval.now(ch))))
}
def transformWithPath(cf: Cofree[Chain, OpTag], path: List[OpTag] = Nil)(
f: (List[OpTag], OpTag) => Cofree[Chain, OpTag]
): Cofree[Chain, OpTag] = {
val newCf = f(path, cf.head)
Cofree[Chain, OpTag](
newCf.head,
(newCf.tail, cf.tail)
.mapN(_ ++ _)
// IF make foldLeft here, will be possible to get info from prev sibling
.map(_.map(transformWithPath(_, newCf.head :: path)(f)))
)
}
// Semigroup for foldRight processing
object RightAssocSemi extends Semigroup[FuncOp] {
override def combine(x: FuncOp, y: FuncOp): FuncOp = (x.tree.head, y.tree.head) match {

View File

@ -1,6 +1,8 @@
package aqua.model
package aqua.model.body
import aqua.semantics.Type
import aqua.model.{AbilityModel, ValueModel}
import aqua.types.Type
import cats.data.Chain
case class Call(args: List[(ValueModel, Type)], exportTo: Option[String]) {
@ -19,7 +21,8 @@ sealed trait OpTag {
def mapValues(f: ValueModel => ValueModel): OpTag = this match {
case OnTag(peerId, via) => OnTag(f(peerId), via.map(f))
case MatchMismatchTag(left, right, shouldMatch) => MatchMismatchTag(f(left), f(right), shouldMatch)
case MatchMismatchTag(left, right, shouldMatch) =>
MatchMismatchTag(f(left), f(right), shouldMatch)
case ForTag(item, iterable) => ForTag(item, f(iterable))
case CallArrowTag(ability, funcName, call) =>
CallArrowTag(
@ -42,7 +45,7 @@ sealed trait OpTag {
case object SeqTag extends OpTag
case object ParTag extends OpTag
case object XorTag extends OpTag
case class OnTag(peerId: ValueModel, via: List[ValueModel]) extends OpTag
case class OnTag(peerId: ValueModel, via: Chain[ValueModel]) extends OpTag
case class NextTag(item: String) extends OpTag
case class MatchMismatchTag(left: ValueModel, right: ValueModel, shouldMatch: Boolean) extends OpTag
case class ForTag(item: String, iterable: ValueModel) extends OpTag

View File

@ -0,0 +1,15 @@
package aqua.model.transform
import aqua.model.{LiteralModel, ValueModel}
case class BodyConfig(
getDataService: String = "getDataSrv",
callbackService: String = "callbackSrv",
respFuncName: String = "response",
relayVarName: String = "relay"
) {
val callbackSrvId: ValueModel = LiteralModel("\"" + callbackService + "\"")
val dataSrvId: ValueModel = LiteralModel("\"" + getDataService + "\"")
}

View File

@ -0,0 +1,107 @@
package aqua.model.transform
import aqua.model.body._
import aqua.model.{FuncCallable, FuncResolved, InitPeerIdModel, VarModel}
import aqua.types.{ArrowType, DataType}
import cats.data.Chain
import cats.free.Cofree
object ForClient {
def apply(func: FuncResolved, conf: BodyConfig): Cofree[Chain, OpTag] = {
import conf._
// Get to init user through a relay
def viaRelay(op: FuncOp): FuncOp =
FuncOp.wrap(OnTag(InitPeerIdModel, Chain.one(VarModel(relayVarName))), op)
val returnCallback: Option[FuncOp] = func.func.ret.map { case (dv, t) =>
viaRelay(
FuncOp.leaf(
CallServiceTag(
callbackSrvId,
respFuncName,
Call(
(dv, t) :: Nil,
None
)
)
)
)
}
// TODO it's an overkill, is it?
def initPeerCallable(name: String, arrowType: ArrowType): FuncCallable =
FuncCallable(
viaRelay(
FuncOp.leaf(
CallServiceTag(
callbackSrvId,
name,
Call(
arrowType.args.zipWithIndex.map { case (t, i) =>
VarModel(s"arg$i") -> t
},
arrowType.res.map(_ => "init_call_res")
)
)
)
),
arrowType.args.zipWithIndex.map {
case (t: DataType, i) => s"arg$i" -> Left(t)
case (t: ArrowType, i) => s"arg$i" -> Right(t)
},
arrowType.res.map(VarModel("init_call_res") -> _),
Map.empty
)
// Like it is called from TS
def funcArgsCall: Call =
Call(
func.func.args.map { case (k, e) =>
(VarModel(k), e.fold(identity, identity))
},
None
)
// Get data with this name from a local service
def getDataOp(name: String): FuncOp =
FuncOp.leaf(
CallServiceTag(
dataSrvId,
name,
Call(Nil, Some(name))
)
)
val body =
viaRelay(
FuncOp
.node(
SeqTag,
Chain
.fromSeq(
func.func.args.collect { case (argName, Left(_)) =>
getDataOp(argName)
} :+ getDataOp(relayVarName)
)
.append(
func.func
.apply(
funcArgsCall,
func.func.args.collect { case (argName, Right(arrowType)) =>
argName -> initPeerCallable(argName, arrowType)
}.toMap,
func.func.args.collect { case (argName, Left(_)) =>
argName
}.foldLeft(Set(relayVarName))(_ + _)
)
.value
._1
) ++ Chain.fromSeq(returnCallback.toSeq)
)
).tree
Topology.resolve(body)
}
}

View File

@ -0,0 +1,78 @@
package aqua.model.transform
import aqua.model.ValueModel
import aqua.model.body.{CallServiceTag, FuncOp, OnTag, OpTag, SeqTag}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
object Topology {
type Tree = Cofree[Chain, OpTag]
def through(peers: Chain[ValueModel]): Chain[Tree] =
peers.map(FuncOp.noop).map(_.tree)
// TODO: after topology is resolved, OnTag should be eliminated
def resolve(op: Tree): Tree =
transformWithPath(op) {
case (path, c: CallServiceTag, children) if c.peerId.isEmpty =>
Cofree[Chain, OpTag](
c.copy(peerId = path.collectFirst { case OnTag(peerId, _) =>
peerId
}),
children
)
case (path, tag @ OnTag(pid, via), children) =>
// Drop seq/par/xor from path
val pathOn = path.collect { case ot: OnTag =>
ot
}
pathOn match {
// If we are on the right node, do nothing
case Nil =>
Cofree[Chain, OpTag](tag, children)
case h :: _ if h.peerId == pid =>
Cofree[Chain, OpTag](tag, children)
case h :: _ =>
Cofree[Chain, OpTag](
tag,
// TODO: merge children, if possible
children.map(through(h.via.reverse ++ via) ++ _)
)
}
case (path, SeqTag, children) =>
// TODO if we have OnTag, and then something else, need to get back
// AND keep in mind that we will handle all the children with OnTag processor!
def modifyChildrenList(list: List[Tree]): Chain[Tree] = list match {
case Nil => Chain.empty
case op :: Nil =>
// TODO: it is a last op, and it could be an On tag; in this case, get back?
Chain.one(op)
case (oncf @ Cofree(ont: OnTag, _)) :: op :: tail =>
(oncf +: through(ont.via.reverse ++ Chain.fromSeq(path.collectFirst { case t: OnTag =>
t.via.toList
}.toList.flatten))) ++ modifyChildrenList(op :: tail)
case o :: ops => o +: modifyChildrenList(ops)
}
Cofree[Chain, OpTag](
SeqTag,
children.map(_.toList).map(modifyChildrenList)
)
case (_, t, children) =>
Cofree[Chain, OpTag](t, children)
}
def transformWithPath(cf: Tree, path: List[OpTag] = Nil)(
f: (List[OpTag], OpTag, Eval[Chain[Tree]]) => Tree
): Tree = {
val newCf = f(path, cf.head, cf.tail)
Cofree[Chain, OpTag](
newCf.head,
newCf.tail.map(_.map(transformWithPath(_, newCf.head :: path)(f)))
)
}
}

View File

@ -0,0 +1,48 @@
package aqua.model
import aqua.model.body.{Call, CallServiceTag, FuncOp, OnTag, OpTag, SeqTag}
import cats.Eval
import cats.data.Chain
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) {
override def toString: String =
tag.toString + (if (ops.isEmpty) "\n" else s"{\n${ops.mkString}\n}\n")
}
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")
val initPeer = InitPeerIdModel
val emptyCall = Call(Nil, None)
val otherPeer = LiteralModel("other-peer")
val otherRelay = LiteralModel("other-relay")
val otherPeer2 = LiteralModel("other-peer-2")
val otherRelay2 = LiteralModel("other-relay-2")
def call(i: Int, on: ValueModel = null) = Node(
CallServiceTag(LiteralModel(s"srv$i"), s"fn$i", Call(Nil, None), Option(on))
)
def seq(nodes: Node*) = Node(SeqTag, nodes.toList)
def on(peer: ValueModel, via: List[ValueModel], body: Node*) =
Node(
OnTag(peer, Chain.fromSeq(via)),
body.toList
)
def through(peer: ValueModel): Node =
FuncOp.noop(peer).tree
}

View File

@ -0,0 +1,147 @@
package aqua.model.transform
import aqua.model.Node
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class TopologySpec extends AnyFlatSpec with Matchers {
import Node._
"topology resolver" should "do nothing on init peer" in {
val init = on(
initPeer,
relay :: Nil,
seq(
call(1),
call(2)
)
)
val proc: Node = Topology.resolve(init)
val expected = on(
initPeer,
relay :: Nil,
seq(
call(1, initPeer),
call(2, initPeer)
)
)
proc should be(expected)
}
"topology resolver" should "go through relay to any other node, directly" in {
val init = on(
initPeer,
relay :: Nil,
on(
otherPeer,
Nil,
seq(
call(1),
call(2)
)
)
)
val proc: Node = Topology.resolve(init)
val expected = on(
initPeer,
relay :: Nil,
on(
otherPeer,
Nil,
through(relay),
seq(
call(1, otherPeer),
call(2, otherPeer)
)
)
)
proc should be(expected)
}
"topology resolver" should "go through relay to any other node, via another relay" in {
val init = on(
initPeer,
relay :: Nil,
on(
otherPeer,
otherRelay :: Nil,
seq(
call(1),
call(2)
)
)
)
val proc: Node = Topology.resolve(init)
val expected = on(
initPeer,
relay :: Nil,
on(
otherPeer,
otherRelay :: Nil,
through(relay),
through(otherRelay),
seq(
call(1, otherPeer),
call(2, otherPeer)
)
)
)
proc should be(expected)
}
"topology resolver" should "get back to init peer" in {
val init = on(
initPeer,
relay :: Nil,
seq(
on(
otherPeer,
otherRelay :: Nil,
call(1)
),
call(2)
)
)
val proc: Node = Topology.resolve(init)
val expected = on(
initPeer,
relay :: Nil,
seq(
on(
otherPeer,
otherRelay :: Nil,
through(relay),
through(otherRelay),
call(1, otherPeer)
),
through(otherRelay),
through(relay),
call(2, initPeer)
)
)
// println(Console.BLUE + init)
// println(Console.YELLOW + proc)
// println(Console.MAGENTA + expected)
// println(Console.RESET)
proc should be(expected)
}
}

View File

@ -3,7 +3,6 @@ package aqua.parser
import aqua.parser.expr._
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.{AquaError, SyntaxError}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.free.Cofree
import cats.parse.{Parser => P, Parser0 => P0}
@ -29,10 +28,10 @@ object Ast {
.surroundedBy(` \n+`.?)
.map(ls => Ast(Cofree(RootExpr(), Eval.now(ls))))
def fromString[F[_]: LiftParser: Comonad](script: String): ValidatedNec[AquaError, Ast[F]] =
def fromString[F[_]: LiftParser: Comonad](script: String): ValidatedNec[P.Error, Ast[F]] =
Validated
.fromEither(
parser[F](Indent()).parseAll(script)
)
.leftMap(pe => NonEmptyChain.one(SyntaxError(pe.failedAtOffset, pe.expected)))
.leftMap(NonEmptyChain.one)
}

View File

@ -5,12 +5,13 @@ import aqua.parser.lexer.{EqOp, Literal, Value}
import cats.parse.{Parser => P}
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.semantics.LiteralType
import aqua.types.LiteralType
import cats.Comonad
case class IfExpr[F[_]](left: Value[F], eqOp: EqOp[F], right: Value[F]) extends Expr[F]
object IfExpr extends Expr.AndIndented(Expr.defer(OnExpr), ParExpr, CallArrowExpr, AbilityIdExpr, ForExpr) {
object IfExpr
extends Expr.AndIndented(Expr.defer(OnExpr), ParExpr, CallArrowExpr, AbilityIdExpr, ForExpr) {
override def p[F[_]: LiftParser: Comonad]: P[IfExpr[F]] =
(`if` *> ` ` *> Value.`value`[F] ~ (` ` *> EqOp.p[F] ~ (` ` *> Value.`value`[F])).?).map {

View File

@ -3,8 +3,8 @@ package aqua.parser.lexer
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._
import aqua.semantics.ScalarType
import cats.{Comonad, Functor}
import aqua.types.ScalarType
import cats.Comonad
import cats.parse.{Parser => P}
import cats.syntax.functor._
import cats.syntax.comonad._
@ -12,7 +12,8 @@ import cats.syntax.comonad._
sealed trait TypeToken[F[_]] extends Token[F]
sealed trait DataTypeToken[F[_]] extends TypeToken[F]
case class ArrayTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F]) extends DataTypeToken[F] {
case class ArrayTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F])
extends DataTypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v)
}
@ -68,7 +69,8 @@ object ArrowTypeToken {
}
}
case class AquaArrowType[F[_]](args: List[TypeToken[F]], res: Option[DataTypeToken[F]]) extends ArrowDef[F] {
case class AquaArrowType[F[_]](args: List[TypeToken[F]], res: Option[DataTypeToken[F]])
extends ArrowDef[F] {
override def argTypes: List[TypeToken[F]] = args
override def resType: Option[DataTypeToken[F]] = res
@ -80,7 +82,9 @@ object DataTypeToken {
(P.string("[]").lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf(P.defer(`arraytypedef`[F]) :: BasicTypeToken.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil)
P.oneOf(
P.defer(`arraytypedef`[F]) :: BasicTypeToken.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil
)
}
object TypeToken {

View File

@ -3,7 +3,7 @@ package aqua.parser.lexer
import aqua.parser.lexer.Token._
import aqua.parser.lift.LiftParser
import aqua.parser.lift.LiftParser._
import aqua.semantics.LiteralType
import aqua.types.LiteralType
import cats.{Comonad, Functor}
import cats.parse.{Numbers, Parser => P}
import cats.syntax.functor._
@ -43,7 +43,9 @@ object Value {
)
def float[F[_]: LiftParser: Comonad]: P[Literal[F]] =
(P.char('-').?.with1 ~ (Numbers.nonNegativeIntString <* P.char('.')) ~ Numbers.nonNegativeIntString).string.lift
(P.char('-').?.with1 ~ (Numbers.nonNegativeIntString <* P.char(
'.'
)) ~ Numbers.nonNegativeIntString).string.lift
.map(Literal(_, LiteralType.float))
// TODO make more sophisticated escaping/unescaping
@ -51,8 +53,10 @@ object Value {
(`"` *> P.charsWhile0(_ != '"') <* `"`).string.lift
.map(Literal(_, LiteralType.string))
def literal[F[_]: LiftParser: Comonad]: P[Literal[F]] = P.oneOf(bool :: float.backtrack :: num :: string :: Nil)
def literal[F[_]: LiftParser: Comonad]: P[Literal[F]] =
P.oneOf(bool :: float.backtrack :: num :: string :: Nil)
def `value`[F[_]: LiftParser: Comonad]: P[Value[F]] = P.oneOf(literal.backtrack :: varLambda :: Nil)
def `value`[F[_]: LiftParser: Comonad]: P[Value[F]] =
P.oneOf(literal.backtrack :: varLambda :: Nil)
}

View File

@ -1,6 +1,5 @@
package aqua
import aqua.model.Call
import aqua.parser.expr.{
AbilityIdExpr,
AliasExpr,
@ -34,14 +33,14 @@ import aqua.parser.lexer.{
}
import cats.Id
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.semantics.LiteralType.{bool, number, string}
import aqua.semantics.{LiteralType, ScalarType}
import aqua.types.LiteralType.{bool, number, string}
import aqua.types.{LiteralType, ScalarType}
import org.scalatest.EitherValues
import scala.collection.mutable
import scala.language.implicitConversions
object Utils {
object AquaSpec {
implicit def toAb(str: String): Ability[Id] = Ability[Id](str)
implicit def toName(str: String): Name[Id] = Name[Id](str)
@ -85,9 +84,7 @@ object Utils {
val stringSc: BasicTypeToken[Id] = BasicTypeToken[Id](ScalarType.string)
}
trait Utils extends EitherValues {
val emptyCall = Call(Nil, None)
trait AquaSpec extends EitherValues {
def parseExpr(str: String): CallArrowExpr[Id] =
CallArrowExpr.p[Id].parseAll(str).value

View File

@ -1,15 +1,15 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.AbilityIdExpr
import aqua.parser.lexer.Literal
import aqua.semantics.LiteralType
import aqua.types.LiteralType
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class AbilitIdExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class AbilitIdExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"abilities" should "be parsed" in {
parseAbId("Ab a") should be(

View File

@ -1,15 +1,15 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.AliasExpr
import aqua.semantics.ScalarType.u32
import aqua.types.ScalarType.u32
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class AliasExprSpec extends AnyFlatSpec with Matchers with Utils {
class AliasExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import Utils._
import AquaSpec._
"alias" should "be parsed properly" in {
parseAlias("alias SomeAlias : u32") should be(

View File

@ -1,16 +1,16 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.ArrowTypeExpr
import aqua.semantics.ScalarType.string
import aqua.semantics.ScalarType.u32
import aqua.types.ScalarType.string
import aqua.types.ScalarType.u32
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with Utils {
class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import Utils._
import AquaSpec._
"arrow types" should "be parsed properly" in {
parseArrow("onIn: string -> ()") should be(
@ -22,7 +22,10 @@ class ArrowTypeExprSpec extends AnyFlatSpec with Matchers with Utils {
)
parseArrow("onIn: Custom, string, u32, Custom3 -> Custom2") should be(
ArrowTypeExpr[Id]("onIn", toArrowType(List("Custom", string, u32, "Custom3"), Some("Custom2")))
ArrowTypeExpr[Id](
"onIn",
toArrowType(List("Custom", string, u32, "Custom3"), Some("Custom2"))
)
)
}
}

View File

@ -1,19 +1,24 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.CallArrowExpr
import aqua.parser.lexer.{Name, VarLambda}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class CallArrowSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class CallArrowSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"func calls" should "parse func()" in {
parseExpr("func()") should be(CallArrowExpr[Id](None, None, toName("func"), List()))
parseExpr("Ab.func(arg)") should be(
CallArrowExpr[Id](None, Some(toAb("Ab")), Name[Id]("func"), List(VarLambda[Id](toName("arg"))))
CallArrowExpr[Id](
None,
Some(toAb("Ab")),
Name[Id]("func"),
List(VarLambda[Id](toName("arg")))
)
)
parseExpr("func(arg.doSomething)") should be(
@ -34,7 +39,10 @@ class CallArrowSpec extends AnyFlatSpec with Matchers with Utils {
None,
Some(toAb("Ab")),
Name[Id]("func"),
List(toVarLambda("arg", List("doSomething", "and", "doSomethingElse")), toVarLambda("arg2", List("someFunc")))
List(
toVarLambda("arg", List("doSomething", "and", "doSomethingElse")),
toVarLambda("arg2", List("someFunc"))
)
)
)

View File

@ -1,15 +1,15 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{AliasExpr, DataStructExpr}
import aqua.semantics.ScalarType.u32
import aqua.types.ScalarType.u32
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class DataStructExprSpec extends AnyFlatSpec with Matchers with Utils {
class DataStructExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import Utils._
import AquaSpec._
"data struct" should "be parsed properly" in {
parseDataStruct("data Smth") should be(

View File

@ -1,14 +1,14 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{ElseOtherwiseExpr, OnExpr}
import aqua.semantics.LiteralType.{number, string}
import aqua.types.LiteralType.{number, string}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ElseOtherwiseExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class ElseOtherwiseExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"else" should "be parsed" in {
parseElse("else") should be(

View File

@ -1,14 +1,14 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{ElseOtherwiseExpr, FieldTypeExpr}
import aqua.semantics.ScalarType.bool
import aqua.types.ScalarType.bool
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class FieldTypeExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class FieldTypeExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"else" should "be parsed" in {
parseFieldType("some: bool") should be(

View File

@ -1,13 +1,13 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.ForExpr
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ForExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class ForExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
parseFor("for some <- \"a\"") should be(

View File

@ -1,49 +1,23 @@
package aqua.parser
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.AquaSpec
import aqua.parser.Ast.parser
import aqua.parser.expr._
import aqua.parser.lexer.{ArrowTypeToken, BasicTypeToken, EqOp}
import aqua.semantics.ScalarType.{bool, u64}
import cats.{Eval, Id}
import cats.syntax.traverse._
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.types.ScalarType.{bool, string, u32, u64}
import cats.Id
import cats.data.Chain
import cats.free.Cofree
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, NonEmptyChain, Validated}
import cats.free.Cofree
import scala.collection.mutable
import scala.language.implicitConversions
class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
import aqua.semantics.ScalarType.{string, u32}
class FuncExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"func header" should "parse" in {
funcExpr("func some() -> bool") should be(
@ -130,7 +104,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
}
"some" should "other" in {
"multi function expression" should "parse" in {
val script =
"""service Local("local"):
| gt: -> bool
@ -168,6 +142,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
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"))
/* TODO this is semantics, not parser test
val f =
Semantics.generateModel(tree).toList.head.asInstanceOf[ScriptModel]
@ -260,6 +235,6 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Utils {
// return to 'else' node
qGenComplex.d() shouldBe elseOn
qGenComplex.d() shouldBe elseCall
qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None)
qGenComplex.d() shouldBe CallServiceTag(local, "gt", Call(List(), Some("three")), None)*/
}
}

View File

@ -1,16 +1,15 @@
package aqua.parser
import aqua.Utils
import aqua.parser.expr.{ForExpr, IfExpr}
import aqua.AquaSpec
import aqua.parser.expr.IfExpr
import aqua.parser.lexer.EqOp
import aqua.semantics.LiteralType.{bool, number, string}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class IfExprSpec extends AnyFlatSpec with Matchers with Utils {
class IfExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import Utils._
import AquaSpec._
"if" should "be parsed" in {
parseIf("if a") should be(

View File

@ -1,14 +1,13 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.OnExpr
import aqua.semantics.LiteralType.{number, string}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class OnExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class OnExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
parseOn("on peer") should be(

View File

@ -1,14 +1,13 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{OnExpr, ParExpr}
import aqua.semantics.LiteralType.{number, string}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ParExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class ParExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
parsePar("par") should be(

View File

@ -1,13 +1,13 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{ParExpr, ReturnExpr}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ReturnExprSpec extends AnyFlatSpec with Matchers with Utils {
import Utils._
class ReturnExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._
"on" should "be parsed" in {
parseReturn("<- true") should be(

View File

@ -1,14 +1,14 @@
package aqua.parser
import aqua.Utils
import aqua.AquaSpec
import aqua.parser.expr.{ReturnExpr, ServiceExpr}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ServiceExprSpec extends AnyFlatSpec with Matchers with Utils {
class ServiceExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import Utils._
import AquaSpec._
"on" should "be parsed" in {
parseService("service Local(\"local\")") should be(

View File

@ -1,11 +1,11 @@
package aqua.parser.lexer
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.Id
import cats.data.NonEmptyList
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.Id
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.data.NonEmptyList
class LambdaOpSpec extends AnyFlatSpec with Matchers with EitherValues {
@ -14,7 +14,9 @@ class LambdaOpSpec extends AnyFlatSpec with Matchers with EitherValues {
opsP(".field") should be(NonEmptyList.of(IntoField[Id]("field")))
opsP(".field.sub") should be(NonEmptyList.of(IntoField[Id]("field"), IntoField[Id]("sub")))
opsP(".field*.sub") should be(NonEmptyList.of(IntoField[Id]("field"), IntoArray[Id](()), IntoField[Id]("sub")))
opsP(".field*.sub") should be(
NonEmptyList.of(IntoField[Id]("field"), IntoArray[Id](()), IntoField[Id]("sub"))
)
}
}

View File

@ -2,10 +2,10 @@ package aqua.parser.lexer
import aqua.parser.lexer.Token._
import cats.data.NonEmptyList
import cats.parse.{Parser => P}
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.parse.{Parser => P}
class TokenSpec extends AnyFlatSpec with Matchers with EitherValues {
@ -49,9 +49,15 @@ class TokenSpec extends AnyFlatSpec with Matchers with EitherValues {
"indented" should "parse 1 or more lines" in {
indented(_ => `.`, "").parseAll(" .\n .").right.value should be(NonEmptyList.of((), ()))
indented(_ => `.`, "").parseAll(" .\n .\n .").right.value should be(NonEmptyList.of((), (), ()))
indented(_ => `.`, "").parse(" .\n .\n .\n").right.value should be(("\n", NonEmptyList.of((), (), ())))
indented(_ => `.`, "").parse(" .\n .\n .\n ").right.value should be(("\n ", NonEmptyList.of((), (), ())))
indented(_ => `.`, "").parse(" .\n .\n .\n .").right.value should be(("", NonEmptyList.of((), (), (), ())))
indented(_ => `.`, "").parse(" .\n .\n .\n").right.value should be(
("\n", NonEmptyList.of((), (), ()))
)
indented(_ => `.`, "").parse(" .\n .\n .\n ").right.value should be(
("\n ", NonEmptyList.of((), (), ()))
)
indented(_ => `.`, "").parse(" .\n .\n .\n .").right.value should be(
("", NonEmptyList.of((), (), (), ()))
)
indented(_ => `.`, "").parseAll(" .").right.value should be(NonEmptyList.of(()))
indented(_ => `.`, " ").parse(" .\n .").right.value should be(("\n .", NonEmptyList.of(())))
@ -64,7 +70,10 @@ class TokenSpec extends AnyFlatSpec with Matchers with EitherValues {
case class Node(branches: NonEmptyList[Tree]) extends Tree
lazy val p: P[NonEmptyList[Tree]] =
indented(_ => P.string("newline") *> (` \n+` *> P.defer(p)).?.map(_.fold[Tree](Leaf)(Node)), "")
indented(
_ => P.string("newline") *> (` \n+` *> P.defer(p)).?.map(_.fold[Tree](Leaf)(Node)),
""
)
p.parseAll(" newline").right.value should be(NonEmptyList.of(Leaf))
}

View File

@ -4,14 +4,13 @@ import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.semantics.ScalarType
import aqua.types.ScalarType
import aqua.types.ScalarType.u32
import cats.Id
import scala.language.implicitConversions
class TypeSpec extends AnyFlatSpec with Matchers with EitherValues {
import ScalarType.u32
class TypeTokenSpec extends AnyFlatSpec with Matchers with EitherValues {
implicit def strToBt(st: ScalarType): BasicTypeToken[Id] = BasicTypeToken[Id](st)
@ -44,7 +43,9 @@ class TypeSpec extends AnyFlatSpec with Matchers with EitherValues {
ArrowTypeToken.`arrowdef`.parseAll("[]Absolutely, u32 -> B").right.value should be(
ArrowTypeToken[Id](
(),
ArrayTypeToken[Id]((), CustomTypeToken[Id]("Absolutely")) :: (u32: BasicTypeToken[Id]) :: Nil,
ArrayTypeToken[Id]((), CustomTypeToken[Id]("Absolutely")) :: (u32: BasicTypeToken[
Id
]) :: Nil,
Some(CustomTypeToken[Id]("B"))
)
)
@ -55,7 +56,9 @@ class TypeSpec extends AnyFlatSpec with Matchers with EitherValues {
TypeToken.`typedef`.parseAll("[]Something") should be(
Right(ArrayTypeToken[Id]((), CustomTypeToken[Id]("Something")))
)
TypeToken.`typedef`.parseAll("[]u32") should be(Right(ArrayTypeToken[Id]((), u32: BasicTypeToken[Id])))
TypeToken.`typedef`.parseAll("[]u32") should be(
Right(ArrayTypeToken[Id]((), u32: BasicTypeToken[Id]))
)
TypeToken.`typedef`.parseAll("[][]u32") should be(
Right(ArrayTypeToken[Id]((), ArrayTypeToken[Id]((), u32: BasicTypeToken[Id])))
)

View File

@ -4,7 +4,7 @@ import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.semantics.LiteralType
import aqua.types.LiteralType
import cats.Id
class ValueSpec extends AnyFlatSpec with Matchers with EitherValues {

View File

@ -1,6 +1,7 @@
package aqua.semantics
import aqua.model.{FuncOp, Model}
import aqua.model.Model
import aqua.model.body.FuncOp
import aqua.parser.lexer.Token
import aqua.parser.{Ast, Expr}
import aqua.semantics.rules.ReportError

View File

@ -1,12 +1,14 @@
package aqua.semantics.expr
import aqua.model.{Call, CallArrowTag, CallServiceTag, FuncOp, Model, ValueModel}
import aqua.model.body.{Call, CallArrowTag, CallServiceTag, FuncOp}
import aqua.model.{Model, ValueModel}
import aqua.parser.expr.CallArrowExpr
import aqua.semantics.{ArrowType, Prog, Type}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrowType, Type}
import cats.free.Free
import cats.syntax.flatMap._
import cats.syntax.functor._
@ -20,7 +22,10 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
private def checkArgsRes[Alg[_]](
at: ArrowType
)(implicit N: NamesAlgebra[F, Alg], V: ValuesAlgebra[F, Alg]): Free[Alg, List[(ValueModel, Type)]] =
)(implicit
N: NamesAlgebra[F, Alg],
V: ValuesAlgebra[F, Alg]
): Free[Alg, List[(ValueModel, Type)]] =
V.checkArguments(expr.funcName, at, args) >> variable
.fold(freeUnit[Alg])(exportVar =>
at.res.fold(

View File

@ -1,6 +1,7 @@
package aqua.semantics.expr
import aqua.model.{FuncOp, Model, XorTag}
import aqua.model.Model
import aqua.model.body.{FuncOp, XorTag}
import aqua.parser.expr.ElseOtherwiseExpr
import aqua.semantics.Prog
import cats.free.Free

View File

@ -1,11 +1,13 @@
package aqua.semantics.expr
import aqua.model.{ForTag, FuncOp, Model, NextTag, OpTag, ParTag, SeqTag}
import aqua.model.Model
import aqua.model.body.{ForTag, FuncOp, NextTag, OpTag, ParTag, SeqTag}
import aqua.parser.expr.ForExpr
import aqua.semantics.{ArrayType, DataType, Prog}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, DataType}
import cats.data.Chain
import cats.free.Free
import cats.syntax.flatMap._
@ -31,7 +33,10 @@ class ForSem[F[_]](val expr: ForExpr[F]) extends AnyVal {
case op: FuncOp =>
FuncOp.wrap(
ForTag(expr.item.value, ValuesAlgebra.valueToModel(expr.iterable)),
FuncOp.node(expr.par.fold[OpTag](SeqTag)(_ => ParTag), Chain(op, FuncOp.leaf(NextTag(expr.item.value))))
FuncOp.node(
expr.par.fold[OpTag](SeqTag)(_ => ParTag),
Chain(op, FuncOp.leaf(NextTag(expr.item.value)))
)
)
case _ => Model.error("Wrong body of For expr")
})

View File

@ -1,13 +1,15 @@
package aqua.semantics.expr
import aqua.model.{FuncModel, FuncOp, Model}
import aqua.model.body.FuncOp
import aqua.model.{FuncModel, Model}
import aqua.parser.expr.FuncExpr
import aqua.parser.lexer.Arg
import aqua.semantics.{ArrowType, DataType, Prog, Type}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrowType, DataType, Type}
import cats.Applicative
import cats.data.Chain
import cats.free.Free

View File

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

View File

@ -1,6 +1,7 @@
package aqua.semantics.expr
import aqua.model.{FuncOp, Model, OnTag}
import aqua.model.Model
import aqua.model.body.{FuncOp, OnTag}
import aqua.parser.expr.OnExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
@ -25,28 +26,12 @@ class OnSem[F[_]](val expr: OnExpr[F]) extends AnyVal {
(_: Unit, ops: Model) =>
A.endScope() as (ops match {
case op: FuncOp =>
// 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(
FuncOp.wrap(
OnTag(
ValuesAlgebra.valueToModel(expr.peerId),
path.map(ValuesAlgebra.valueToModel)
Chain.fromSeq(expr.via).map(ValuesAlgebra.valueToModel)
),
Chain(
op,
returnLeaf
)
op
)
case m => Model.error("On body is not an op, it's " + m)

View File

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

Some files were not shown because too many files have changed in this diff Show More