mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
69 wrong varnames (#74)
* Bug #69 reproduced in test * Bug #69 reproduced in a test * Fixes #47 (allow arg names in service function definitions) * ServiceModel * AbilityModel is removed, as it's unused * Fixes #65: expose %init_peer_id% as a literal * Removed FuncResolved, as it's not resolved actually * Fixes #69 * Helpers for func op showing & building * Compile bug fixed * Comments * Removed ScriptModel.enqueue
This commit is contained in:
parent
2d1427b124
commit
7512648cd0
@ -1,11 +1,12 @@
|
||||
service Test("test"):
|
||||
getBool: -> bool
|
||||
getList: -> []string
|
||||
service Demo("demo"):
|
||||
get4() -> u64
|
||||
get5(arg: u32)
|
||||
get6(a: string) -> bool
|
||||
|
||||
func f():
|
||||
list <- Test.getList()
|
||||
for user <- list:
|
||||
on "peer" via "relay":
|
||||
isOnline <- Test.getBool()
|
||||
if isOnline:
|
||||
Test.getBool()
|
||||
func one() -> u64:
|
||||
variable <- Demo.get4()
|
||||
<- variable
|
||||
|
||||
func two() -> u64:
|
||||
variable <- one()
|
||||
<- variable
|
@ -1,8 +1,8 @@
|
||||
package aqua.backend.air
|
||||
|
||||
import aqua.model._
|
||||
import aqua.model.body.{
|
||||
Call,
|
||||
import aqua.model.func.Call
|
||||
import aqua.model.func.body.{
|
||||
CallArrowTag,
|
||||
CallServiceTag,
|
||||
ForTag,
|
||||
@ -35,7 +35,6 @@ object AirGen {
|
||||
|
||||
def valueToData(vm: ValueModel): DataView = vm match {
|
||||
case LiteralModel(value) => DataView.StringScalar(value)
|
||||
case InitPeerIdModel => DataView.InitPeerId
|
||||
case VarModel(name, lambda) =>
|
||||
if (lambda.isEmpty) DataView.Variable(name)
|
||||
else DataView.VarLens(name, lambdaToString(lambda.toList))
|
||||
@ -88,12 +87,12 @@ object AirGen {
|
||||
peerId.map(valueToData).getOrElse(DataView.InitPeerId),
|
||||
valueToData(serviceId),
|
||||
funcName,
|
||||
args.map(_._1).map(valueToData),
|
||||
args.map(_.model).map(valueToData),
|
||||
exportTo
|
||||
)
|
||||
)
|
||||
|
||||
case (CallArrowTag(_, funcName, Call(args, exportTo)), ops) =>
|
||||
case (CallArrowTag(funcName, Call(args, exportTo)), ops) =>
|
||||
// TODO: should be already resolved & removed from tree
|
||||
Eval later opsToSingle(
|
||||
ops
|
||||
|
@ -1,15 +1,15 @@
|
||||
package aqua.backend.air
|
||||
|
||||
import aqua.model.FuncResolved
|
||||
import aqua.model.transform.BodyConfig
|
||||
import aqua.model.func.FuncCallable
|
||||
import aqua.model.transform.{BodyConfig, ForClient}
|
||||
|
||||
case class FuncAirGen(func: FuncResolved) {
|
||||
case class FuncAirGen(func: FuncCallable) {
|
||||
|
||||
/**
|
||||
* Generates AIR from the function body as it is, with no modifications and optimizations
|
||||
*/
|
||||
def generateAir: Air =
|
||||
AirGen(func.func.body.tree).generate
|
||||
AirGen(func.body.tree).generate
|
||||
|
||||
/**
|
||||
* Generates AIR from the optimized function body, assuming client is behind a relay
|
||||
@ -17,6 +17,6 @@ case class FuncAirGen(func: FuncResolved) {
|
||||
*/
|
||||
def generateClientAir(conf: BodyConfig = BodyConfig()): Air =
|
||||
AirGen(
|
||||
func.forClient(conf)
|
||||
ForClient.resolve(func, conf)
|
||||
).generate
|
||||
}
|
||||
|
@ -1,26 +1,24 @@
|
||||
package aqua.backend.ts
|
||||
|
||||
import aqua.backend.air.FuncAirGen
|
||||
import aqua.model.FuncResolved
|
||||
import aqua.model.func.{ArgDef, FuncCallable}
|
||||
import aqua.model.transform.BodyConfig
|
||||
import aqua.types._
|
||||
import cats.syntax.show._
|
||||
import cats.syntax.functor._
|
||||
|
||||
case class TypescriptFunc(func: FuncResolved) {
|
||||
case class TypescriptFunc(func: FuncCallable) {
|
||||
|
||||
import TypescriptFunc._
|
||||
|
||||
def argsTypescript: String =
|
||||
func.func.args.map {
|
||||
case (n, Left(t)) => s"${n}: " + typeToTs(t)
|
||||
case (n, Right(at)) => s"${n}: " + typeToTs(at)
|
||||
}.mkString(", ")
|
||||
func.args.args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
||||
|
||||
def generateTypescript(conf: BodyConfig = BodyConfig()): String = {
|
||||
|
||||
val tsAir = FuncAirGen(func).generateClientAir(conf)
|
||||
|
||||
val returnCallback = func.func.ret.map { case (dv, t) =>
|
||||
val returnCallback = func.ret.as {
|
||||
s"""h.on('${conf.callbackService}', '${conf.respFuncName}', (args) => {
|
||||
| const [res] = args;
|
||||
| resolve(res);
|
||||
@ -29,24 +27,24 @@ case class TypescriptFunc(func: FuncResolved) {
|
||||
|
||||
}
|
||||
|
||||
val setCallbacks = func.func.args.map {
|
||||
case (argName, Left(t)) =>
|
||||
val setCallbacks = func.args.args.map {
|
||||
case ArgDef.Data(argName, _) =>
|
||||
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
|
||||
case (argName, Right(at)) =>
|
||||
case ArgDef.Arrow(argName, at) =>
|
||||
s"""h.on('${conf.callbackService}', '$argName', (args) => {return $argName(${argsCallToTs(
|
||||
at
|
||||
)});});"""
|
||||
}.mkString("\n")
|
||||
|
||||
val retType = func.func.ret
|
||||
.map(_._2)
|
||||
val retType = func.ret
|
||||
.map(_.`type`)
|
||||
.fold("void")(typeToTs)
|
||||
|
||||
val returnVal =
|
||||
func.func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||
|
||||
s"""
|
||||
|export async function ${func.name}(client: FluenceClient${if (func.func.args.isEmpty) ""
|
||||
|export async function ${func.funcName}(client: FluenceClient${if (func.args.isEmpty) ""
|
||||
else ", "}${argsTypescript}): Promise<$retType> {
|
||||
| let request;
|
||||
| const promise = new Promise<$retType>((resolve, reject) => {
|
||||
@ -73,7 +71,7 @@ case class TypescriptFunc(func: FuncResolved) {
|
||||
| })
|
||||
| .handleScriptError(reject)
|
||||
| .handleTimeout(() => {
|
||||
| reject('Request timed out for ${func.name}');
|
||||
| reject('Request timed out for ${func.funcName}');
|
||||
| })
|
||||
| .build();
|
||||
| });
|
||||
|
@ -35,7 +35,7 @@ object Aqua {
|
||||
.map(FuncAirGen)
|
||||
.map(g =>
|
||||
// add function name before body
|
||||
s";; function name: ${g.func.name}\n\n" + g.generateAir.show
|
||||
s";; function name: ${g.func.funcName}\n\n" + g.generateAir.show
|
||||
)
|
||||
.toList
|
||||
.mkString("\n\n\n")
|
||||
|
@ -119,7 +119,7 @@ object AquaCompiler {
|
||||
.map(FuncAirGen)
|
||||
.map(g =>
|
||||
// add function name before body
|
||||
s";; function name: ${g.func.name}\n\n" + g.generateAir.show
|
||||
s";; function name: ${g.func.funcName}\n\n" + g.generateAir.show
|
||||
)
|
||||
.toList
|
||||
.mkString("\n\n\n")
|
||||
|
@ -1,3 +0,0 @@
|
||||
package aqua.model
|
||||
|
||||
sealed trait AbilityModel extends Model
|
@ -1,16 +0,0 @@
|
||||
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)
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.model.body.FuncOp
|
||||
import cats.data.Chain
|
||||
import aqua.model.func.body.FuncOp
|
||||
import cats.kernel.Semigroup
|
||||
|
||||
trait Model
|
||||
@ -15,17 +14,25 @@ object Model {
|
||||
override def combine(x: Model, y: Model): Model = (x, y) match {
|
||||
case (l: FuncOp, r: FuncOp) =>
|
||||
FuncOp.FuncOpSemigroup.combine(l, r)
|
||||
case (l: ScriptModel, r: ScriptModel) => ScriptModel(l.funcs ++ r.funcs)
|
||||
case (l: FuncModel, r: FuncModel) => ScriptModel(Chain(l, r))
|
||||
case (l: ScriptModel, r: FuncModel) => ScriptModel(l.funcs.append(r))
|
||||
case (l: FuncModel, r: ScriptModel) => ScriptModel(r.funcs.prepend(l))
|
||||
case (_, r: ScriptModel) => r
|
||||
case (l: ScriptModel, _) => l
|
||||
case (_, r: FuncModel) => r
|
||||
case (l: FuncModel, _) => l
|
||||
case (l: EmptyModel, r: EmptyModel) => EmptyModel(l.log + " |+| " + r.log)
|
||||
case (l: ScriptModel, r: ScriptModel) =>
|
||||
ScriptModel.SMMonoid.combine(l, r)
|
||||
|
||||
case (_: EmptyModel, r) => r
|
||||
case (l, _: EmptyModel) => l
|
||||
|
||||
case (l, r: ScriptModel) =>
|
||||
ScriptModel.toScriptPart(l).fold(r)(ScriptModel.SMMonoid.combine(_, r))
|
||||
case (l: ScriptModel, r) =>
|
||||
ScriptModel.toScriptPart(r).fold(l)(ScriptModel.SMMonoid.combine(l, _))
|
||||
case (l, r) =>
|
||||
ScriptModel
|
||||
.toScriptPart(l)
|
||||
.fold(r)(ls =>
|
||||
ScriptModel.toScriptPart(r).fold(l)(rs => ScriptModel.SMMonoid.combine(ls, rs))
|
||||
)
|
||||
|
||||
case (l: EmptyModel, r: EmptyModel) => EmptyModel(l.log + " |+| " + r.log)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,40 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.model.func.{FuncCallable, FuncModel}
|
||||
import cats.Monoid
|
||||
import cats.data.Chain
|
||||
|
||||
case class ScriptModel(funcs: Chain[FuncModel]) extends Model {
|
||||
case class ScriptModel(
|
||||
funcs: Chain[FuncModel] = Chain.empty,
|
||||
services: Chain[ServiceModel] = Chain.empty,
|
||||
types: Chain[TypeModel] = Chain.empty
|
||||
) extends Model {
|
||||
|
||||
def enqueue(m: Model): ScriptModel = m match {
|
||||
case f: FuncModel => copy(funcs.append(f))
|
||||
case _ => this
|
||||
}
|
||||
|
||||
def resolveFunctions: Chain[FuncResolved] =
|
||||
def resolveFunctions: Chain[FuncCallable] =
|
||||
funcs
|
||||
.foldLeft((Map.empty[String, FuncCallable], Chain.empty[FuncResolved])) {
|
||||
.foldLeft((Map.empty[String, FuncCallable], Chain.empty[FuncCallable])) {
|
||||
case ((funcsAcc, outputAcc), func) =>
|
||||
val fr = func.captureArrows(funcsAcc)
|
||||
funcsAcc.updated(func.name, fr) -> outputAcc.append(FuncResolved(func.name, fr))
|
||||
funcsAcc.updated(func.name, fr) -> outputAcc.append(fr)
|
||||
}
|
||||
._2
|
||||
|
||||
}
|
||||
|
||||
object ScriptModel {
|
||||
|
||||
implicit object SMMonoid extends Monoid[ScriptModel] {
|
||||
override def empty: ScriptModel = ScriptModel()
|
||||
|
||||
override def combine(x: ScriptModel, y: ScriptModel): ScriptModel =
|
||||
ScriptModel(x.funcs ++ y.funcs, x.services ++ y.services, x.types ++ y.types)
|
||||
}
|
||||
|
||||
// Builds a ScriptModel if given model can be considered as a part of a script
|
||||
def toScriptPart(m: Model): Option[ScriptModel] = m match {
|
||||
case fm: FuncModel => Some(ScriptModel(funcs = Chain.one(fm)))
|
||||
case sm: ServiceModel => Some(ScriptModel(services = Chain.one(sm)))
|
||||
case tm: TypeModel => Some(ScriptModel(types = Chain.one(tm)))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
6
model/src/main/scala/aqua/model/ServiceModel.scala
Normal file
6
model/src/main/scala/aqua/model/ServiceModel.scala
Normal file
@ -0,0 +1,6 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.types.ArrowType
|
||||
import cats.data.NonEmptyMap
|
||||
|
||||
case class ServiceModel(name: String, arrows: NonEmptyMap[String, ArrowType]) extends Model
|
5
model/src/main/scala/aqua/model/TypeModel.scala
Normal file
5
model/src/main/scala/aqua/model/TypeModel.scala
Normal file
@ -0,0 +1,5 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.types.Type
|
||||
|
||||
case class TypeModel(name: String, `type`: Type) extends Model
|
@ -7,7 +7,10 @@ sealed trait ValueModel {
|
||||
}
|
||||
|
||||
case class LiteralModel(value: String) extends ValueModel
|
||||
case object InitPeerIdModel extends ValueModel
|
||||
|
||||
object LiteralModel {
|
||||
val initPeerId: LiteralModel = LiteralModel("%init_peer_id%")
|
||||
}
|
||||
|
||||
sealed trait LambdaModel
|
||||
case object IntoArrayModel extends LambdaModel
|
||||
|
12
model/src/main/scala/aqua/model/func/ArgDef.scala
Normal file
12
model/src/main/scala/aqua/model/func/ArgDef.scala
Normal file
@ -0,0 +1,12 @@
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.types.{ArrowType, DataType, Type}
|
||||
|
||||
sealed abstract class ArgDef(val `type`: Type) {
|
||||
def name: String
|
||||
}
|
||||
|
||||
object ArgDef {
|
||||
case class Data(name: String, dataType: DataType) extends ArgDef(dataType)
|
||||
case class Arrow(name: String, arrowType: ArrowType) extends ArgDef(arrowType)
|
||||
}
|
54
model/src/main/scala/aqua/model/func/ArgsCall.scala
Normal file
54
model/src/main/scala/aqua/model/func/ArgsCall.scala
Normal file
@ -0,0 +1,54 @@
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.model.{ValueModel, VarModel}
|
||||
import aqua.types.{ArrowType, DataType}
|
||||
import cats.syntax.functor._
|
||||
|
||||
/**
|
||||
* Wraps argument definitions of a function, along with values provided when this function is called
|
||||
* @param args Argument definitions
|
||||
* @param callWith Values provided for arguments
|
||||
*/
|
||||
case class ArgsCall(args: List[ArgDef], callWith: List[Call.Arg]) {
|
||||
// Both arguments (arg names and types how they seen from the function body)
|
||||
// and values (value models and types how they seen on the call site)
|
||||
lazy val zipped: List[(ArgDef, Call.Arg)] = args zip callWith
|
||||
|
||||
lazy val dataArgs: Map[String, ValueModel] =
|
||||
zipped.collect { case (ArgDef.Data(name, _), Call.Arg(value, _)) =>
|
||||
name -> value
|
||||
}.toMap
|
||||
|
||||
def arrowArgs(arrowsInScope: Map[String, FuncCallable]): Map[String, FuncCallable] =
|
||||
zipped.collect {
|
||||
case (ArgDef.Arrow(name, _), Call.Arg(VarModel(value, _), _))
|
||||
if arrowsInScope.contains(value) =>
|
||||
name -> arrowsInScope(value)
|
||||
}.toMap
|
||||
}
|
||||
|
||||
object ArgsCall {
|
||||
|
||||
def arrowToArgsCallRet(
|
||||
arrow: ArrowType,
|
||||
argPrefix: String = "arg",
|
||||
retName: String = "init_call_res"
|
||||
): (ArgsDef, Call, Option[Call.Arg]) = {
|
||||
val argNamesTypes = arrow.args.zipWithIndex.map(iv => iv.map(i => argPrefix + i).swap)
|
||||
|
||||
val argsDef = ArgsDef(argNamesTypes.map {
|
||||
case (a, t: DataType) => ArgDef.Data(a, t)
|
||||
case (a, t: ArrowType) => ArgDef.Arrow(a, t)
|
||||
})
|
||||
|
||||
val call = Call(
|
||||
argNamesTypes.map { case (a, t) =>
|
||||
Call.Arg(VarModel(a), t)
|
||||
},
|
||||
arrow.res.as(retName)
|
||||
)
|
||||
|
||||
(argsDef, call, arrow.res.map(t => Call.Arg(VarModel(retName), t)))
|
||||
}
|
||||
|
||||
}
|
27
model/src/main/scala/aqua/model/func/ArgsDef.scala
Normal file
27
model/src/main/scala/aqua/model/func/ArgsDef.scala
Normal file
@ -0,0 +1,27 @@
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.model.VarModel
|
||||
import aqua.types.Type
|
||||
import cats.data.Chain
|
||||
|
||||
case class ArgsDef(args: List[ArgDef]) {
|
||||
def isEmpty: Boolean = args.isEmpty
|
||||
|
||||
def call(c: Call): ArgsCall = ArgsCall(args, c.args)
|
||||
|
||||
def types: List[Type] = args.map(_.`type`)
|
||||
|
||||
def toCallArgs: List[Call.Arg] = args.map(ad => Call.Arg(VarModel(ad.name), ad.`type`))
|
||||
|
||||
lazy val dataArgNames: Chain[String] = Chain.fromSeq(args.collect { case ArgDef.Data(n, _) =>
|
||||
n
|
||||
})
|
||||
|
||||
lazy val arrowArgs: Chain[ArgDef.Arrow] = Chain.fromSeq(args.collect { case ad: ArgDef.Arrow =>
|
||||
ad
|
||||
})
|
||||
}
|
||||
|
||||
object ArgsDef {
|
||||
val empty: ArgsDef = ArgsDef(Nil)
|
||||
}
|
24
model/src/main/scala/aqua/model/func/Call.scala
Normal file
24
model/src/main/scala/aqua/model/func/Call.scala
Normal file
@ -0,0 +1,24 @@
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.model.ValueModel
|
||||
import aqua.types.Type
|
||||
|
||||
case class Call(args: List[Call.Arg], exportTo: Option[String]) {
|
||||
|
||||
def mapValues(f: ValueModel => ValueModel): Call =
|
||||
Call(
|
||||
args.map(_.mapValues(f)),
|
||||
exportTo
|
||||
)
|
||||
|
||||
def mapExport(f: String => String): Call = copy(exportTo = exportTo.map(f))
|
||||
}
|
||||
|
||||
object Call {
|
||||
|
||||
case class Arg(model: ValueModel, `type`: Type) {
|
||||
|
||||
def mapValues(f: ValueModel => ValueModel): Arg =
|
||||
copy(f(model))
|
||||
}
|
||||
}
|
@ -1,18 +1,26 @@
|
||||
package aqua.model
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.model.body.{Call, CallArrowTag, FuncOp, OpTag}
|
||||
import aqua.types.{ArrowType, DataType, Type}
|
||||
import aqua.model.func.body.{CallArrowTag, FuncOp, OpTag}
|
||||
import aqua.model.{ValueModel, VarModel}
|
||||
import aqua.types.ArrowType
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
|
||||
case class FuncCallable(
|
||||
funcName: String,
|
||||
body: FuncOp,
|
||||
args: List[(String, Either[DataType, ArrowType])],
|
||||
ret: Option[(ValueModel, Type)],
|
||||
args: ArgsDef,
|
||||
ret: Option[Call.Arg],
|
||||
capturedArrows: Map[String, FuncCallable]
|
||||
) {
|
||||
|
||||
def arrowType: ArrowType =
|
||||
ArrowType(
|
||||
args.types,
|
||||
ret.map(_.`type`)
|
||||
)
|
||||
|
||||
def findNewNames(forbidden: Set[String], introduce: Set[String]): Map[String, String] =
|
||||
(forbidden intersect introduce).foldLeft(Map.empty[String, String]) { case (acc, name) =>
|
||||
acc + (name -> LazyList
|
||||
@ -23,21 +31,18 @@ case class FuncCallable(
|
||||
}
|
||||
|
||||
// Apply a callable function, get its fully resolved body & optional value, if any
|
||||
def apply(
|
||||
def resolve(
|
||||
call: Call,
|
||||
arrows: Map[String, FuncCallable],
|
||||
forbiddenNames: Set[String]
|
||||
): Eval[(FuncOp, Option[ValueModel])] = {
|
||||
|
||||
// Collect all arguments: what names are used inside the function, what values are received
|
||||
val argsFull = args.zip(call.args)
|
||||
val argsFull = args.call(call)
|
||||
// DataType arguments
|
||||
val argsToData = argsFull.collect { case ((n, Left(_)), v) =>
|
||||
n -> v._1
|
||||
}.toMap
|
||||
val argsToData = argsFull.dataArgs
|
||||
// Arrow arguments: expected type is Arrow, given by-name
|
||||
val argsToArrows = argsFull.collect { case ((n, Right(_)), (VarModel(name, _), _)) =>
|
||||
n -> arrows(name)
|
||||
}.toMap
|
||||
val argsToArrows = argsFull.arrowArgs(arrows)
|
||||
|
||||
// Going to resolve arrows: collect them all. Names should never collide: it's semantically checked
|
||||
val allArrows = capturedArrows ++ argsToArrows
|
||||
@ -46,7 +51,7 @@ case class FuncCallable(
|
||||
val treeWithValues = body.resolveValues(argsToData)
|
||||
|
||||
// Function body on its own defines some values; collect their names
|
||||
val treeDefines = treeWithValues.definesValueNames.value
|
||||
val treeDefines = treeWithValues.definesValueNames.value -- call.exportTo
|
||||
|
||||
// We have some names in scope (forbiddenNames), can't introduce them again; so find new names
|
||||
val shouldRename = findNewNames(forbiddenNames, treeDefines)
|
||||
@ -55,7 +60,7 @@ case class FuncCallable(
|
||||
if (shouldRename.isEmpty) treeWithValues else treeWithValues.rename(shouldRename)
|
||||
|
||||
// Result could be derived from arguments, or renamed; take care about that
|
||||
val result = ret.map(_._1).map(_.resolveWith(argsToData)).map {
|
||||
val result = ret.map(_.model).map(_.resolveWith(argsToData)).map {
|
||||
case v: VarModel if shouldRename.contains(v.name) => v.copy(shouldRename(v.name))
|
||||
case v => v
|
||||
}
|
||||
@ -70,11 +75,11 @@ case class FuncCallable(
|
||||
// Functions may export variables, so collect them
|
||||
Map.empty[String, ValueModel]
|
||||
) {
|
||||
case ((noNames, resolvedExports), CallArrowTag(None, fn, c)) if allArrows.contains(fn) =>
|
||||
case ((noNames, resolvedExports), CallArrowTag(fn, c)) if allArrows.contains(fn) =>
|
||||
// Apply arguments to a function – recursion
|
||||
val (appliedOp, value) =
|
||||
allArrows(fn)
|
||||
.apply(c.mapValues(_.resolveWith(resolvedExports)), argsToArrows, noNames)
|
||||
.resolve(c.mapValues(_.resolveWith(resolvedExports)), argsToArrows, noNames)
|
||||
.value
|
||||
|
||||
// Function defines new names inside its body – need to collect them
|
16
model/src/main/scala/aqua/model/func/FuncModel.scala
Normal file
16
model/src/main/scala/aqua/model/func/FuncModel.scala
Normal file
@ -0,0 +1,16 @@
|
||||
package aqua.model.func
|
||||
|
||||
import aqua.model.func.body.FuncOp
|
||||
import aqua.model.Model
|
||||
|
||||
case class FuncModel(
|
||||
name: String,
|
||||
args: ArgsDef,
|
||||
ret: Option[Call.Arg],
|
||||
body: FuncOp
|
||||
) extends Model {
|
||||
|
||||
def captureArrows(arrows: Map[String, FuncCallable]): FuncCallable =
|
||||
FuncCallable(name, body, args, ret, arrows)
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package aqua.model.body
|
||||
package aqua.model.func.body
|
||||
|
||||
import aqua.model.{LiteralModel, Model, ValueModel, VarModel}
|
||||
import aqua.model.func.Call
|
||||
import aqua.model.{Model, ValueModel, VarModel}
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
@ -16,7 +17,7 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
|
||||
Cofree.cata(tree)(folder)
|
||||
|
||||
def definesValueNames: Eval[Set[String]] = cata[Set[String]] {
|
||||
case (CallArrowTag(_, _, Call(_, Some(export))), acc) =>
|
||||
case (CallArrowTag(_, Call(_, Some(export))), acc) =>
|
||||
Eval.later(acc.foldLeft(Set(export))(_ ++ _))
|
||||
case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) =>
|
||||
Eval.later(acc.foldLeft(Set(export))(_ ++ _))
|
||||
@ -48,17 +49,15 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
|
||||
}
|
||||
|
||||
object FuncOp {
|
||||
type Tree = Cofree[Chain, OpTag]
|
||||
|
||||
def noop(peerId: ValueModel): FuncOp =
|
||||
FuncOp.leaf(CallServiceTag(LiteralModel("\"op\""), "identity", Call(Nil, None), Some(peerId)))
|
||||
|
||||
def traverseA[A](cf: Cofree[Chain, OpTag], init: A)(
|
||||
f: (A, OpTag) => (A, Cofree[Chain, OpTag])
|
||||
): Eval[(A, Cofree[Chain, OpTag])] = {
|
||||
def traverseA[A](cf: Tree, init: A)(
|
||||
f: (A, OpTag) => (A, Tree)
|
||||
): Eval[(A, Tree)] = {
|
||||
val (headA, head) = f(init, cf.head)
|
||||
// TODO: it should be in standard library, with some other types
|
||||
cf.tail
|
||||
.map(_.foldLeft[(A, Chain[Cofree[Chain, OpTag]])]((headA, head.tailForced)) {
|
||||
.map(_.foldLeft[(A, Chain[Tree])]((headA, head.tailForced)) {
|
||||
case ((aggrA, aggrTail), child) =>
|
||||
traverseA(child, aggrA)(f).value.map(aggrTail.append)
|
||||
})
|
32
model/src/main/scala/aqua/model/func/body/FuncOps.scala
Normal file
32
model/src/main/scala/aqua/model/func/body/FuncOps.scala
Normal file
@ -0,0 +1,32 @@
|
||||
package aqua.model.func.body
|
||||
|
||||
import aqua.model.{LiteralModel, ValueModel}
|
||||
import aqua.model.func.Call
|
||||
import cats.data.Chain
|
||||
|
||||
object FuncOps {
|
||||
|
||||
def noop(peerId: ValueModel): FuncOp =
|
||||
FuncOp.leaf(CallServiceTag(LiteralModel("\"op\""), "identity", Call(Nil, None), Some(peerId)))
|
||||
|
||||
def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
|
||||
FuncOp.leaf(
|
||||
CallServiceTag(
|
||||
srvId,
|
||||
funcName,
|
||||
call
|
||||
)
|
||||
)
|
||||
|
||||
def onVia(on: ValueModel, via: Chain[ValueModel], wrap: FuncOp): FuncOp =
|
||||
FuncOp.wrap(
|
||||
OnTag(on, via),
|
||||
wrap
|
||||
)
|
||||
|
||||
def seq(op1: FuncOp, ops: FuncOp*): FuncOp =
|
||||
FuncOp.node(SeqTag, Chain.fromSeq(op1 +: ops))
|
||||
|
||||
def xor(left: FuncOp, right: FuncOp): FuncOp =
|
||||
FuncOp.node(XorTag, Chain(left, right))
|
||||
}
|
@ -1,22 +1,9 @@
|
||||
package aqua.model.body
|
||||
package aqua.model.func.body
|
||||
|
||||
import aqua.model.{AbilityModel, ValueModel}
|
||||
import aqua.types.Type
|
||||
import aqua.model.ValueModel
|
||||
import aqua.model.func.Call
|
||||
import cats.data.Chain
|
||||
|
||||
case class Call(args: List[(ValueModel, Type)], exportTo: Option[String]) {
|
||||
|
||||
def mapValues(f: ValueModel => ValueModel): Call =
|
||||
Call(
|
||||
args.map { case (v, t) =>
|
||||
(f(v), t)
|
||||
},
|
||||
exportTo
|
||||
)
|
||||
|
||||
def mapExport(f: String => String): Call = copy(exportTo = exportTo.map(f))
|
||||
}
|
||||
|
||||
sealed trait OpTag {
|
||||
|
||||
def mapValues(f: ValueModel => ValueModel): OpTag = this match {
|
||||
@ -24,9 +11,8 @@ sealed trait OpTag {
|
||||
case MatchMismatchTag(left, right, shouldMatch) =>
|
||||
MatchMismatchTag(f(left), f(right), shouldMatch)
|
||||
case ForTag(item, iterable) => ForTag(item, f(iterable))
|
||||
case CallArrowTag(ability, funcName, call) =>
|
||||
case CallArrowTag(funcName, call) =>
|
||||
CallArrowTag(
|
||||
ability,
|
||||
funcName,
|
||||
call.mapValues(f)
|
||||
)
|
||||
@ -51,7 +37,6 @@ case class MatchMismatchTag(left: ValueModel, right: ValueModel, shouldMatch: Bo
|
||||
case class ForTag(item: String, iterable: ValueModel) extends OpTag
|
||||
|
||||
case class CallArrowTag(
|
||||
ability: Option[AbilityModel],
|
||||
funcName: String,
|
||||
call: Call
|
||||
) extends OpTag
|
19
model/src/main/scala/aqua/model/func/body/ShowFuncOp.scala
Normal file
19
model/src/main/scala/aqua/model/func/body/ShowFuncOp.scala
Normal file
@ -0,0 +1,19 @@
|
||||
package aqua.model.func.body
|
||||
|
||||
import cats.Show
|
||||
import cats.free.Cofree
|
||||
|
||||
object ShowFuncOp {
|
||||
|
||||
private def showTreeOffset(offset: Int): Show[FuncOp.Tree] = { case Cofree(head, tail) =>
|
||||
val children = tail.value
|
||||
s"${" " * offset}$head" +
|
||||
(if (children.isEmpty) "\n"
|
||||
else
|
||||
" {\n" + children.toList
|
||||
.map(showTreeOffset(offset + 1).show) + s"${" " * offset}}\n")
|
||||
}
|
||||
|
||||
implicit val showFuncOp: Show[FuncOp] =
|
||||
Show.show(op => showTreeOffset(0).show(op.tree))
|
||||
}
|
@ -8,7 +8,8 @@ case class BodyConfig(
|
||||
errorHandlingService: String = "errorHandlingSrv",
|
||||
errorFuncName: String = "error",
|
||||
respFuncName: String = "response",
|
||||
relayVarName: String = "relay"
|
||||
relayVarName: String = "relay",
|
||||
wrapWithXor: Boolean = true
|
||||
) {
|
||||
|
||||
val errorId: ValueModel = LiteralModel("\"" + errorFuncName + "\"")
|
||||
|
@ -1,130 +1,130 @@
|
||||
package aqua.model.transform
|
||||
|
||||
import aqua.model.body._
|
||||
import aqua.model.{FuncCallable, FuncResolved, InitPeerIdModel, LiteralModel, VarModel}
|
||||
import aqua.model.func.body._
|
||||
import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable}
|
||||
import aqua.model.{LiteralModel, VarModel}
|
||||
import aqua.types.ScalarType.string
|
||||
import aqua.types.{ArrowType, DataType}
|
||||
import aqua.types.ArrowType
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
|
||||
object ForClient {
|
||||
// TODO not a string
|
||||
private val lastErrorArg = Call.Arg(LiteralModel("%last_error%"), string)
|
||||
|
||||
def apply(func: FuncResolved, conf: BodyConfig): Cofree[Chain, OpTag] = {
|
||||
import conf._
|
||||
// Get to init user through a relay
|
||||
def viaRelay(op: FuncOp)(implicit conf: BodyConfig): FuncOp =
|
||||
FuncOps.onVia(LiteralModel.initPeerId, Chain.one(VarModel(conf.relayVarName)), op)
|
||||
|
||||
def wrapXor(op: FuncOp): FuncOp =
|
||||
def wrapXor(op: FuncOp)(implicit conf: BodyConfig): FuncOp =
|
||||
if (conf.wrapWithXor)
|
||||
FuncOp.node(
|
||||
XorTag,
|
||||
Chain(
|
||||
op,
|
||||
viaRelay(
|
||||
FuncOp.leaf(
|
||||
CallServiceTag(
|
||||
errorHandlingCallback,
|
||||
errorFuncName,
|
||||
Call(
|
||||
// TODO not a string
|
||||
(LiteralModel("%last_error%"), string) :: Nil,
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 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,
|
||||
FuncOps.callService(
|
||||
conf.errorHandlingCallback,
|
||||
conf.errorFuncName,
|
||||
Call(
|
||||
arrowType.args.zipWithIndex.map { case (t, i) =>
|
||||
VarModel(s"arg$i") -> t
|
||||
},
|
||||
arrowType.res.map(_ => "init_call_res")
|
||||
lastErrorArg :: Nil,
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
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
|
||||
)
|
||||
)
|
||||
else op
|
||||
|
||||
def returnCallback(func: FuncCallable)(implicit conf: BodyConfig): Option[FuncOp] = func.ret.map {
|
||||
retArg =>
|
||||
viaRelay(
|
||||
FuncOps.callService(
|
||||
conf.callbackSrvId,
|
||||
conf.respFuncName,
|
||||
Call(
|
||||
retArg :: Nil,
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def initPeerCallable(name: String, arrowType: ArrowType)(implicit
|
||||
conf: BodyConfig
|
||||
): FuncCallable = {
|
||||
val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType)
|
||||
FuncCallable(
|
||||
s"init_peer_callable_$name",
|
||||
viaRelay(
|
||||
FuncOps.callService(
|
||||
conf.callbackSrvId,
|
||||
name,
|
||||
call
|
||||
)
|
||||
),
|
||||
args,
|
||||
ret,
|
||||
Map.empty
|
||||
)
|
||||
}
|
||||
|
||||
// Get data with this name from a local service
|
||||
def getDataOp(name: String)(implicit conf: BodyConfig): FuncOp =
|
||||
FuncOps.callService(
|
||||
conf.dataSrvId,
|
||||
name,
|
||||
Call(Nil, Some(name))
|
||||
)
|
||||
|
||||
def resolve(func: FuncCallable, conf: BodyConfig): Cofree[Chain, OpTag] = {
|
||||
implicit val c: BodyConfig = conf
|
||||
|
||||
// Like it is called from TS
|
||||
def funcArgsCall: Call =
|
||||
Call(
|
||||
func.func.args.map { case (k, e) =>
|
||||
(VarModel(k), e.fold(identity, identity))
|
||||
},
|
||||
func.args.toCallArgs,
|
||||
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 =
|
||||
val funcAround: FuncCallable = FuncCallable(
|
||||
"funcAround",
|
||||
wrapXor(
|
||||
viaRelay(
|
||||
FuncOp
|
||||
.node(
|
||||
SeqTag,
|
||||
Chain
|
||||
.fromSeq(
|
||||
func.func.args.collect { case (argName, Left(_)) =>
|
||||
getDataOp(argName)
|
||||
} :+ getDataOp(relayVarName)
|
||||
)
|
||||
(
|
||||
func.args.dataArgNames.map(getDataOp) :+ getDataOp(conf.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))(_ + _)
|
||||
FuncOp.leaf(
|
||||
CallArrowTag(
|
||||
func.funcName,
|
||||
funcArgsCall
|
||||
)
|
||||
.value
|
||||
._1
|
||||
) ++ Chain.fromSeq(returnCallback.toSeq)
|
||||
)
|
||||
) ++ Chain.fromSeq(returnCallback(func).toSeq)
|
||||
)
|
||||
)
|
||||
).tree
|
||||
),
|
||||
ArgsDef(ArgDef.Arrow(func.funcName, func.arrowType) :: Nil),
|
||||
None,
|
||||
func.args.arrowArgs.collect { case ArgDef.Arrow(argName, arrowType) =>
|
||||
argName -> initPeerCallable(argName, arrowType)
|
||||
}.toList.toMap
|
||||
)
|
||||
|
||||
val body =
|
||||
funcAround
|
||||
.resolve(
|
||||
Call(Call.Arg(VarModel("_func"), func.arrowType) :: Nil, None),
|
||||
Map("_func" -> func),
|
||||
Set.empty
|
||||
)
|
||||
.value
|
||||
._1
|
||||
.tree
|
||||
|
||||
Topology.resolve(body)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.model.transform
|
||||
|
||||
import aqua.model.ValueModel
|
||||
import aqua.model.body.{CallServiceTag, FuncOp, OnTag, OpTag, SeqTag}
|
||||
import aqua.model.func.body.{CallServiceTag, FuncOps, OnTag, OpTag, SeqTag}
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
@ -9,13 +9,16 @@ import cats.free.Cofree
|
||||
object Topology {
|
||||
type Tree = Cofree[Chain, OpTag]
|
||||
|
||||
def through(peers: Chain[ValueModel]): Chain[Tree] =
|
||||
peers
|
||||
// Walks through peer IDs, doing a noop function on each
|
||||
// If same IDs are found in a row, does noop only once
|
||||
// TODO: if there's a chain like a -> b -> c -> ... -> b -> g, remove everything between b and b
|
||||
def through(peerIds: Chain[ValueModel]): Chain[Tree] =
|
||||
peerIds
|
||||
.foldLeft(Chain.empty[ValueModel]) {
|
||||
case (acc, p) if acc.lastOption.contains(p) => acc
|
||||
case (acc, p) => acc :+ p
|
||||
}
|
||||
.map(FuncOp.noop)
|
||||
.map(FuncOps.noop)
|
||||
.map(_.tree)
|
||||
|
||||
// TODO: after topology is resolved, OnTag should be eliminated
|
||||
|
@ -1,8 +1,9 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.model.body.{Call, CallServiceTag, FuncOp, OnTag, OpTag, SeqTag, XorTag}
|
||||
import aqua.model.func.Call
|
||||
import aqua.model.func.body.{CallServiceTag, FuncOp, FuncOps, OnTag, OpTag, SeqTag, XorTag}
|
||||
import aqua.model.transform.BodyConfig
|
||||
import aqua.types.{LiteralType, ScalarType, Type}
|
||||
import aqua.types.ScalarType
|
||||
import cats.Eval
|
||||
import cats.data.Chain
|
||||
import cats.free.Cofree
|
||||
@ -20,10 +21,10 @@ case class Node(tag: OpTag, ops: List[Node] = Nil) {
|
||||
else
|
||||
Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right)
|
||||
|
||||
private def diffArg(left: (ValueModel, Type), right: (ValueModel, Type)): String =
|
||||
private def diffArg(left: Call.Arg, right: Call.Arg): String =
|
||||
Console.GREEN + "(" +
|
||||
equalOrNot(left._1, right._1) + Console.GREEN + ", " +
|
||||
equalOrNot(left._2, right._2) + Console.GREEN + ")"
|
||||
equalOrNot(left.model, right.model) + Console.GREEN + ", " +
|
||||
equalOrNot(left.`type`, right.`type`) + Console.GREEN + ")"
|
||||
|
||||
private def diffCall(left: Call, right: Call): String =
|
||||
if (left == right) Console.GREEN + left + Console.RESET
|
||||
@ -86,7 +87,7 @@ object Node {
|
||||
|
||||
val relay = LiteralModel("relay")
|
||||
val relayV = VarModel("relay")
|
||||
val initPeer = InitPeerIdModel
|
||||
val initPeer = LiteralModel.initPeerId
|
||||
val emptyCall = Call(Nil, None)
|
||||
val otherPeer = LiteralModel("other-peer")
|
||||
val otherRelay = LiteralModel("other-relay")
|
||||
@ -101,7 +102,7 @@ object Node {
|
||||
CallServiceTag(
|
||||
bc.errorHandlingCallback,
|
||||
bc.errorFuncName,
|
||||
Call((LiteralModel("%last_error%"), ScalarType.string) :: Nil, None),
|
||||
Call(Call.Arg(LiteralModel("%last_error%"), ScalarType.string) :: Nil, None),
|
||||
Option(on)
|
||||
)
|
||||
)
|
||||
@ -110,7 +111,7 @@ object Node {
|
||||
CallServiceTag(
|
||||
bc.callbackSrvId,
|
||||
bc.respFuncName,
|
||||
Call((value, ScalarType.string) :: Nil, None),
|
||||
Call(Call.Arg(value, ScalarType.string) :: Nil, None),
|
||||
Option(on)
|
||||
)
|
||||
)
|
||||
@ -134,5 +135,5 @@ object Node {
|
||||
)
|
||||
|
||||
def through(peer: ValueModel): Node =
|
||||
FuncOp.noop(peer).tree
|
||||
FuncOps.noop(peer).tree
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package aqua.model.transform
|
||||
|
||||
import aqua.model.body.FuncOp
|
||||
import aqua.model.{FuncCallable, FuncResolved, InitPeerIdModel, LiteralModel, Node}
|
||||
import aqua.types.{LiteralType, ScalarType}
|
||||
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
|
||||
import aqua.model.func.{ArgsDef, Call, FuncCallable}
|
||||
import aqua.model.{LiteralModel, Node, VarModel}
|
||||
import aqua.types.ScalarType
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
@ -150,19 +151,18 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
|
||||
val ret = LiteralModel("\"return this\"")
|
||||
|
||||
val func: FuncResolved = FuncResolved(
|
||||
"ret",
|
||||
val func: FuncCallable =
|
||||
FuncCallable(
|
||||
"ret",
|
||||
FuncOp(on(otherPeer, Nil, call(1))),
|
||||
Nil,
|
||||
Some(ret -> ScalarType.string),
|
||||
ArgsDef.empty,
|
||||
Some(Call.Arg(ret, ScalarType.string)),
|
||||
Map.empty
|
||||
)
|
||||
)
|
||||
|
||||
val bc = BodyConfig()
|
||||
|
||||
val fc = ForClient(func, bc)
|
||||
val fc = ForClient.resolve(func, bc)
|
||||
|
||||
val procFC: Node = fc
|
||||
|
||||
@ -189,19 +189,17 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
|
||||
val ret = LiteralModel("\"return this\"")
|
||||
|
||||
val func: FuncResolved = FuncResolved(
|
||||
val func: FuncCallable = FuncCallable(
|
||||
"ret",
|
||||
FuncCallable(
|
||||
FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))),
|
||||
Nil,
|
||||
Some(ret -> ScalarType.string),
|
||||
Map.empty
|
||||
)
|
||||
FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))),
|
||||
ArgsDef.empty,
|
||||
Some(Call.Arg(ret, ScalarType.string)),
|
||||
Map.empty
|
||||
)
|
||||
|
||||
val bc = BodyConfig()
|
||||
|
||||
val fc = ForClient(func, bc)
|
||||
val fc = ForClient.resolve(func, bc)
|
||||
|
||||
val procFC: Node = fc
|
||||
|
||||
@ -227,4 +225,58 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
||||
|
||||
}
|
||||
|
||||
"topology resolver" should "link funcs correctly" in {
|
||||
/*
|
||||
func one() -> u64:
|
||||
variable <- Demo.get42()
|
||||
<- variable
|
||||
|
||||
func two() -> u64:
|
||||
variable <- one()
|
||||
<- variable
|
||||
*/
|
||||
|
||||
val f1: FuncCallable =
|
||||
FuncCallable(
|
||||
"f1",
|
||||
FuncOp(Node(CallServiceTag(LiteralModel("\"srv1\""), "foo", Call(Nil, Some("v")), None))),
|
||||
ArgsDef.empty,
|
||||
Some(Call.Arg(VarModel("v"), ScalarType.string)),
|
||||
Map.empty
|
||||
)
|
||||
|
||||
val f2: FuncCallable =
|
||||
FuncCallable(
|
||||
"f2",
|
||||
FuncOp(
|
||||
Node(CallArrowTag("callable", Call(Nil, Some("v"))))
|
||||
),
|
||||
ArgsDef.empty,
|
||||
Some(Call.Arg(VarModel("v"), ScalarType.string)),
|
||||
Map("callable" -> f1)
|
||||
)
|
||||
|
||||
val bc = BodyConfig(wrapWithXor = false)
|
||||
|
||||
val res = ForClient.resolve(f2, bc): Node
|
||||
|
||||
res.equalsOrPrintDiff(
|
||||
on(
|
||||
initPeer,
|
||||
relayV :: Nil,
|
||||
seq(
|
||||
dataCall(bc, "relay", initPeer),
|
||||
Node(
|
||||
CallServiceTag(LiteralModel("\"srv1\""), "foo", Call(Nil, Some("v")), Some(initPeer))
|
||||
),
|
||||
on(
|
||||
initPeer,
|
||||
relayV :: Nil,
|
||||
respCall(bc, VarModel("v"), initPeer)
|
||||
)
|
||||
)
|
||||
)
|
||||
) should be(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ case class ArrowTypeExpr[F[_]](name: Name[F], `type`: ArrowTypeToken[F]) extends
|
||||
object ArrowTypeExpr extends Expr.Leaf {
|
||||
|
||||
override def p[F[_]: LiftParser: Comonad]: Parser[ArrowTypeExpr[F]] =
|
||||
((Name.p[F] <* ` : `) ~ ArrowTypeToken.`arrowdef`[F]).map { case (name, t) =>
|
||||
ArrowTypeExpr(name, t)
|
||||
(Name.p[F] ~ ((` : ` *> ArrowTypeToken.`arrowdef`[F]) | ArrowTypeToken.`arrowWithNames`)).map {
|
||||
case (name, t) =>
|
||||
ArrowTypeExpr(name, t)
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ object Token {
|
||||
val `func`: P[Unit] = P.string("func")
|
||||
val `on`: P[Unit] = P.string("on")
|
||||
val `via`: P[Unit] = P.string("via")
|
||||
val `%init_peer_id%` : P[Unit] = P.string("%init_peer_id%")
|
||||
val `for`: P[Unit] = P.string("for")
|
||||
val `if`: P[Unit] = P.string("if")
|
||||
val `eqs`: P[Unit] = P.string("==")
|
||||
|
@ -67,6 +67,12 @@ object ArrowTypeToken {
|
||||
.map(Some(_)) | P.string("()").as(None))).map { case ((args, point), res) ⇒
|
||||
ArrowTypeToken(point, args, res)
|
||||
}
|
||||
|
||||
def `arrowWithNames`[F[_]: LiftParser: Comonad]: P[ArrowTypeToken[F]] =
|
||||
((`(`.lift ~ comma0(Name.p[F] *> ` : ` *> DataTypeToken.`datatypedef`) <* `)`) ~
|
||||
(` -> ` *> DataTypeToken.`datatypedef`).?).map { case ((point, args), res) =>
|
||||
ArrowTypeToken(point, args, res)
|
||||
}
|
||||
}
|
||||
|
||||
case class AquaArrowType[F[_]](args: List[TypeToken[F]], res: Option[DataTypeToken[F]])
|
||||
|
@ -34,6 +34,9 @@ object Value {
|
||||
.map(t ⇒ P.string(t).lift.map(fu => Literal(fu.as(t), LiteralType.bool)))
|
||||
)
|
||||
|
||||
def initPeerId[F[_]: LiftParser: Comonad]: P[Literal[F]] =
|
||||
`%init_peer_id%`.string.lift.map(Literal(_, LiteralType.string))
|
||||
|
||||
def num[F[_]: LiftParser: Comonad]: P[Literal[F]] =
|
||||
(P.char('-').?.with1 ~ Numbers.nonNegativeIntString).lift.map(fu =>
|
||||
fu.extract match {
|
||||
@ -57,6 +60,6 @@ object Value {
|
||||
P.oneOf(bool :: float.backtrack :: num :: string :: Nil)
|
||||
|
||||
def `value`[F[_]: LiftParser: Comonad]: P[Value[F]] =
|
||||
P.oneOf(literal.backtrack :: varLambda :: Nil)
|
||||
P.oneOf(literal.backtrack :: initPeerId.backtrack :: varLambda :: Nil)
|
||||
|
||||
}
|
||||
|
@ -28,27 +28,15 @@ object CompilerState {
|
||||
_ <- State.set(
|
||||
CompilerState[F](
|
||||
a.errors ++ b.errors,
|
||||
NamesState(
|
||||
Nil,
|
||||
a.names.rootArrows ++ b.names.rootArrows,
|
||||
a.names.definitions ++ b.names.definitions
|
||||
),
|
||||
AbilitiesState(
|
||||
Nil,
|
||||
a.abilities.services ++ b.abilities.services,
|
||||
a.abilities.rootServiceIds ++ b.abilities.rootServiceIds,
|
||||
definitions = a.abilities.definitions ++ b.abilities.definitions
|
||||
),
|
||||
TypesState(
|
||||
strict = a.types.strict ++ b.types.strict,
|
||||
definitions = a.types.definitions ++ b.types.definitions
|
||||
)
|
||||
a.names |+| b.names,
|
||||
a.abilities |+| b.abilities,
|
||||
a.types |+| b.types
|
||||
)
|
||||
)
|
||||
am <- x
|
||||
ym <- y
|
||||
} yield {
|
||||
println(s"MONOID COMBINE $am $ym")
|
||||
//println(s"MONOID COMBINE $am $ym")
|
||||
am |+| ym
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.FuncOp
|
||||
import aqua.model.func.body.FuncOp
|
||||
import aqua.parser.lexer.Token
|
||||
import aqua.parser.{Ast, Expr}
|
||||
import aqua.semantics.rules.ReportError
|
||||
|
@ -1,6 +1,6 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.{Model, TypeModel}
|
||||
import aqua.parser.expr.AliasExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
@ -11,7 +11,7 @@ class AliasSem[F[_]](val expr: AliasExpr[F]) extends AnyVal {
|
||||
|
||||
def program[Alg[_]](implicit T: TypesAlgebra[F, Alg]): Prog[Alg, Model] =
|
||||
T.resolveType(expr.target).flatMap {
|
||||
case Some(t) => T.defineAlias(expr.name, t) as Model.empty("Alias generates no model")
|
||||
case Some(t) => T.defineAlias(expr.name, t) as (TypeModel(expr.name.value, t): Model)
|
||||
case None => Free.pure[Alg, Model](Model.error("Alias type unresolved"))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.{Model, TypeModel}
|
||||
import aqua.parser.expr.ArrowTypeExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
@ -10,9 +10,12 @@ import cats.syntax.functor._
|
||||
|
||||
class ArrowTypeSem[F[_]](val expr: ArrowTypeExpr[F]) extends AnyVal {
|
||||
|
||||
def program[Alg[_]](implicit T: TypesAlgebra[F, Alg], A: AbilitiesAlgebra[F, Alg]): Prog[Alg, Model] =
|
||||
def program[Alg[_]](implicit
|
||||
T: TypesAlgebra[F, Alg],
|
||||
A: AbilitiesAlgebra[F, Alg]
|
||||
): Prog[Alg, Model] =
|
||||
T.resolveArrowDef(expr.`type`).flatMap {
|
||||
case Some(t) => A.defineArrow(expr.name, t) as Model.empty("Arrow type generates no model")
|
||||
case Some(t) => A.defineArrow(expr.name, t) as (TypeModel(expr.name.value, t): Model)
|
||||
case None => Free.pure[Alg, Model](Model.error("Arrow type unresolved"))
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.body.{Call, CallArrowTag, CallServiceTag, FuncOp}
|
||||
import aqua.model.{Model, ValueModel}
|
||||
import aqua.model.func.Call
|
||||
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
|
||||
import aqua.model.Model
|
||||
import aqua.parser.expr.CallArrowExpr
|
||||
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 aqua.types.ArrowType
|
||||
import cats.free.Free
|
||||
import cats.syntax.flatMap._
|
||||
import cats.syntax.functor._
|
||||
@ -25,15 +26,15 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
|
||||
)(implicit
|
||||
N: NamesAlgebra[F, Alg],
|
||||
V: ValuesAlgebra[F, Alg]
|
||||
): Free[Alg, List[(ValueModel, Type)]] =
|
||||
): Free[Alg, List[Call.Arg]] =
|
||||
V.checkArguments(expr.funcName, at, args) >> variable
|
||||
.fold(freeUnit[Alg])(exportVar =>
|
||||
at.res.fold(
|
||||
// TODO: error! we're trying to export variable, but function has no export type
|
||||
freeUnit[Alg]
|
||||
)(resType => N.define(exportVar, resType).void)
|
||||
) >> args.foldLeft(Free.pure[Alg, List[(ValueModel, Type)]](Nil)) { case (acc, v) =>
|
||||
(acc, V.resolveType(v)).mapN((a, b) => a ++ b.map(ValuesAlgebra.valueToModel(v) -> _))
|
||||
) >> args.foldLeft(Free.pure[Alg, List[Call.Arg]](Nil)) { case (acc, v) =>
|
||||
(acc, V.resolveType(v)).mapN((a, b) => a ++ b.map(Call.Arg(ValuesAlgebra.valueToModel(v), _)))
|
||||
}
|
||||
|
||||
private def toModel[Alg[_]](implicit
|
||||
@ -68,7 +69,6 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
|
||||
.map(argsResolved =>
|
||||
FuncOp.leaf(
|
||||
CallArrowTag(
|
||||
ability = None,
|
||||
funcName = funcName.value,
|
||||
Call(argsResolved, variable.map(_.value))
|
||||
)
|
||||
|
@ -1,10 +1,11 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.{Model, TypeModel}
|
||||
import aqua.parser.expr.DataStructExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.ProductType
|
||||
import cats.free.Free
|
||||
import cats.syntax.functor._
|
||||
|
||||
@ -16,7 +17,11 @@ class DataStructSem[F[_]](val expr: DataStructExpr[F]) extends AnyVal {
|
||||
): Prog[Alg, Model] =
|
||||
Prog.after((_: Model) =>
|
||||
T.purgeFields(expr.name).flatMap {
|
||||
case Some(fields) => T.defineDataType(expr.name, fields) as Model.empty("Data struct makes no model")
|
||||
case Some(fields) =>
|
||||
T.defineDataType(expr.name, fields) as (TypeModel(
|
||||
expr.name.value,
|
||||
ProductType(expr.name.value, fields)
|
||||
): Model)
|
||||
case None => Free.pure[Alg, Model](Model.error("Data struct types unresolved"))
|
||||
}
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.{FuncOp, XorTag}
|
||||
import aqua.model.func.body.{FuncOp, XorTag}
|
||||
import aqua.parser.expr.ElseOtherwiseExpr
|
||||
import aqua.semantics.Prog
|
||||
import cats.free.Free
|
||||
|
@ -1,6 +1,6 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.{Model, TypeModel}
|
||||
import aqua.parser.expr.FieldTypeExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
@ -11,7 +11,7 @@ class FieldTypeSem[F[_]](val expr: FieldTypeExpr[F]) extends AnyVal {
|
||||
|
||||
def program[Alg[_]](implicit T: TypesAlgebra[F, Alg]): Prog[Alg, Model] =
|
||||
T.resolveType(expr.`type`).flatMap {
|
||||
case Some(t) => T.defineField(expr.name, t) as Model.empty("Field type makes no model")
|
||||
case Some(t) => T.defineField(expr.name, t) as (TypeModel(expr.name.value, t): Model)
|
||||
case None => Free.pure[Alg, Model](Model.error("Field type unresolved"))
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.{ForTag, FuncOp, NextTag, OpTag, ParTag, SeqTag}
|
||||
import aqua.model.func.body.{ForTag, FuncOp, NextTag, OpTag, ParTag, SeqTag}
|
||||
import aqua.parser.expr.ForExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
|
@ -1,7 +1,8 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.body.FuncOp
|
||||
import aqua.model.{FuncModel, Model}
|
||||
import aqua.model.func.body.FuncOp
|
||||
import aqua.model.Model
|
||||
import aqua.model.func.{ArgDef, ArgsDef, Call, FuncModel}
|
||||
import aqua.parser.expr.FuncExpr
|
||||
import aqua.parser.lexer.Arg
|
||||
import aqua.semantics.Prog
|
||||
@ -74,13 +75,17 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
|
||||
|
||||
val model = FuncModel(
|
||||
name = name.value,
|
||||
args = argNames
|
||||
.zip(funcArrow.args)
|
||||
.map {
|
||||
case (n, dt: DataType) => n -> Left(dt)
|
||||
case (n, at: ArrowType) => n -> Right(at)
|
||||
},
|
||||
ret = retValue.map(ValuesAlgebra.valueToModel).flatMap(vd => funcArrow.res.map(vd -> _)),
|
||||
args = ArgsDef(
|
||||
argNames
|
||||
.zip(funcArrow.args)
|
||||
.map {
|
||||
case (n, dt: DataType) => ArgDef.Data(n, dt)
|
||||
case (n, at: ArrowType) => ArgDef.Arrow(n, at)
|
||||
}
|
||||
),
|
||||
ret = retValue
|
||||
.map(ValuesAlgebra.valueToModel)
|
||||
.flatMap(vd => funcArrow.res.map(Call.Arg(vd, _))),
|
||||
body = bg
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.{FuncOp, MatchMismatchTag}
|
||||
import aqua.model.func.body.{FuncOp, MatchMismatchTag}
|
||||
import aqua.parser.expr.IfExpr
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.{FuncOp, OnTag}
|
||||
import aqua.model.func.body.{FuncOp, OnTag}
|
||||
import aqua.parser.expr.OnExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.body.{FuncOp, ParTag}
|
||||
import aqua.model.func.body.{FuncOp, ParTag}
|
||||
import aqua.parser.expr.ParExpr
|
||||
import aqua.semantics.Prog
|
||||
import cats.free.Free
|
||||
|
@ -1,6 +1,7 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.{FuncModel, Model, ScriptModel}
|
||||
import aqua.model.func.FuncModel
|
||||
import aqua.model.{Model, ScriptModel, ServiceModel, TypeModel}
|
||||
import aqua.parser.expr.RootExpr
|
||||
import aqua.semantics.Prog
|
||||
import cats.data.Chain
|
||||
@ -11,7 +12,11 @@ class RootSem[F[_]](val expr: RootExpr[F]) extends AnyVal {
|
||||
def program[Alg[_]]: Prog[Alg, Model] =
|
||||
Prog.after {
|
||||
case sm: ScriptModel => Free.pure[Alg, Model](sm)
|
||||
case fm: FuncModel => Free.pure[Alg, Model](ScriptModel(Chain.one(fm)))
|
||||
case m => Free.pure[Alg, Model](Model.error("Root contains not a script model, it's " + m))
|
||||
case m =>
|
||||
Free.pure[Alg, Model](
|
||||
ScriptModel
|
||||
.toScriptPart(m)
|
||||
.getOrElse(Model.error("Root contains not a script model, it's " + m))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.model.Model
|
||||
import aqua.model.{Model, ServiceModel}
|
||||
import aqua.parser.expr.ServiceExpr
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
@ -25,13 +25,20 @@ class ServiceSem[F[_]](val expr: ServiceExpr[F]) extends AnyVal {
|
||||
(_: Unit, body: Model) =>
|
||||
(A.purgeArrows(expr.name) <* A.endScope()).flatMap {
|
||||
case Some(nel) =>
|
||||
val arrows = nel.map(kv => kv._1.value -> kv._2).toNem
|
||||
A.defineService(
|
||||
expr.name,
|
||||
nel.map(kv => kv._1.value -> kv._2).toNem
|
||||
) >>
|
||||
expr.id.fold(Free.pure[Alg, Model](Model.empty("No service id is OK")))(idV =>
|
||||
V.ensureIsString(idV) >> A.setServiceId(expr.name, idV) as Model.empty("Service with ID defined")
|
||||
)
|
||||
arrows
|
||||
).flatMap {
|
||||
case true =>
|
||||
val srv = ServiceModel(expr.name.value, arrows)
|
||||
expr.id.fold(Free.pure[Alg, Model](srv))(idV =>
|
||||
V.ensureIsString(idV) >> A.setServiceId(expr.name, idV) as (srv: Model)
|
||||
)
|
||||
case false =>
|
||||
Free.pure(Model.empty("Service not created due to validation errors"))
|
||||
}
|
||||
|
||||
case None =>
|
||||
Free.pure(Model.error("Service has no arrows, fails"))
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
package aqua.semantics.rules.abilities
|
||||
|
||||
import aqua.model.ServiceModel
|
||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
||||
import aqua.parser.lexer.{Ability, Name, Token, Value}
|
||||
import aqua.parser.lexer.{Name, Value}
|
||||
import aqua.types.ArrowType
|
||||
import cats.data.{NonEmptyList, NonEmptyMap, State}
|
||||
import cats.data.{NonEmptyList, State}
|
||||
import cats.~>
|
||||
import cats.syntax.functor._
|
||||
import monocle.Lens
|
||||
@ -12,17 +13,17 @@ import monocle.macros.GenLens
|
||||
class AbilitiesInterpreter[F[_], X](implicit
|
||||
lens: Lens[X, AbilitiesState[F]],
|
||||
error: ReportError[F, X]
|
||||
) extends StackInterpreter[F, X, AbilitiesState[F], AbilityStackFrame[F]](
|
||||
) extends StackInterpreter[F, X, AbilitiesState[F], AbilitiesState.Frame[F]](
|
||||
GenLens[AbilitiesState[F]](_.stack)
|
||||
) with (AbilityOp[F, *] ~> State[X, *]) {
|
||||
|
||||
private def getService(name: String): S[Option[NonEmptyMap[String, ArrowType]]] =
|
||||
private def getService(name: String): S[Option[ServiceModel]] =
|
||||
getState.map(_.services.get(name))
|
||||
|
||||
override def apply[A](fa: AbilityOp[F, A]): State[X, A] =
|
||||
(fa match {
|
||||
case bs: BeginScope[F] =>
|
||||
beginScope(AbilityStackFrame[F](bs.token))
|
||||
beginScope(AbilitiesState.Frame[F](bs.token))
|
||||
|
||||
case EndScope() =>
|
||||
endScope
|
||||
@ -37,7 +38,7 @@ class AbilitiesInterpreter[F[_], X](implicit
|
||||
}
|
||||
|
||||
case ga: GetArrow[F] =>
|
||||
getService(ga.name.value).flatMap {
|
||||
getService(ga.name.value).map(_.map(_.arrows)).flatMap {
|
||||
case Some(arrows) =>
|
||||
arrows(ga.arrow.value)
|
||||
.fold(
|
||||
@ -104,7 +105,8 @@ class AbilitiesInterpreter[F[_], X](implicit
|
||||
case None =>
|
||||
modify(s =>
|
||||
s.copy(
|
||||
services = s.services.updated(ds.name.value, ds.arrows),
|
||||
services =
|
||||
s.services.updated(ds.name.value, ServiceModel(ds.name.value, ds.arrows)),
|
||||
definitions = s.definitions.updated(ds.name.value, ds.name)
|
||||
)
|
||||
).as(true)
|
||||
@ -112,26 +114,3 @@ class AbilitiesInterpreter[F[_], X](implicit
|
||||
|
||||
}).asInstanceOf[State[X, A]]
|
||||
}
|
||||
|
||||
case class AbilitiesState[F[_]](
|
||||
stack: List[AbilityStackFrame[F]] = Nil,
|
||||
services: Map[String, NonEmptyMap[String, ArrowType]] = Map.empty,
|
||||
rootServiceIds: Map[String, Value[F]] = Map.empty[String, Value[F]],
|
||||
definitions: Map[String, Ability[F]] = Map.empty[String, Ability[F]]
|
||||
) {
|
||||
|
||||
def purgeArrows: Option[(NonEmptyList[(Name[F], ArrowType)], AbilitiesState[F])] =
|
||||
stack match {
|
||||
case sc :: tail =>
|
||||
NonEmptyList
|
||||
.fromList(sc.arrows.values.toList)
|
||||
.map(_ -> copy[F](sc.copy(arrows = Map.empty) :: tail))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
case class AbilityStackFrame[F[_]](
|
||||
token: Token[F],
|
||||
arrows: Map[String, (Name[F], ArrowType)] = Map.empty[String, (Name[F], ArrowType)],
|
||||
serviceIds: Map[String, Value[F]] = Map.empty[String, Value[F]]
|
||||
)
|
||||
|
@ -0,0 +1,46 @@
|
||||
package aqua.semantics.rules.abilities
|
||||
|
||||
import aqua.model.ServiceModel
|
||||
import aqua.parser.lexer.{Ability, Name, Token, Value}
|
||||
import aqua.types.ArrowType
|
||||
import cats.Monoid
|
||||
import cats.data.NonEmptyList
|
||||
|
||||
case class AbilitiesState[F[_]](
|
||||
stack: List[AbilitiesState.Frame[F]] = Nil,
|
||||
services: Map[String, ServiceModel] = Map.empty,
|
||||
rootServiceIds: Map[String, Value[F]] = Map.empty[String, Value[F]],
|
||||
definitions: Map[String, Ability[F]] = Map.empty[String, Ability[F]]
|
||||
) {
|
||||
|
||||
def purgeArrows: Option[(NonEmptyList[(Name[F], ArrowType)], AbilitiesState[F])] =
|
||||
stack match {
|
||||
case sc :: tail =>
|
||||
NonEmptyList
|
||||
.fromList(sc.arrows.values.toList)
|
||||
.map(_ -> copy[F](sc.copy(arrows = Map.empty) :: tail))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
object AbilitiesState {
|
||||
|
||||
case class Frame[F[_]](
|
||||
token: Token[F],
|
||||
arrows: Map[String, (Name[F], ArrowType)] = Map.empty[String, (Name[F], ArrowType)],
|
||||
serviceIds: Map[String, Value[F]] = Map.empty[String, Value[F]]
|
||||
)
|
||||
|
||||
implicit def abilitiesStateMonoid[F[_]]: Monoid[AbilitiesState[F]] =
|
||||
new Monoid[AbilitiesState[F]] {
|
||||
override def empty: AbilitiesState[F] = AbilitiesState()
|
||||
|
||||
override def combine(x: AbilitiesState[F], y: AbilitiesState[F]): AbilitiesState[F] =
|
||||
AbilitiesState(
|
||||
Nil,
|
||||
x.services ++ y.services,
|
||||
x.rootServiceIds ++ y.rootServiceIds,
|
||||
x.definitions ++ y.definitions
|
||||
)
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package aqua.semantics.rules.names
|
||||
|
||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
||||
import aqua.parser.lexer.{Name, Token}
|
||||
import aqua.types.{ArrowType, Type}
|
||||
import cats.data.State
|
||||
import cats.~>
|
||||
@ -11,8 +10,9 @@ import cats.syntax.functor._
|
||||
import cats.syntax.flatMap._
|
||||
|
||||
class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: ReportError[F, X])
|
||||
extends StackInterpreter[F, X, NamesState[F], NamesFrame[F]](GenLens[NamesState[F]](_.stack))
|
||||
with (NameOp[F, *] ~> State[X, *]) {
|
||||
extends StackInterpreter[F, X, NamesState[F], NamesState.Frame[F]](
|
||||
GenLens[NamesState[F]](_.stack)
|
||||
) with (NameOp[F, *] ~> State[X, *]) {
|
||||
|
||||
def readName(name: String): S[Option[Type]] =
|
||||
getState.map { st =>
|
||||
@ -84,30 +84,8 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re
|
||||
)(fr => fr.addArrow(da.name.value, da.gen) -> true)
|
||||
}
|
||||
case bs: BeginScope[F] =>
|
||||
beginScope(NamesFrame(bs.token))
|
||||
beginScope(NamesState.Frame(bs.token))
|
||||
case _: EndScope[F] =>
|
||||
endScope
|
||||
}).asInstanceOf[State[X, A]]
|
||||
}
|
||||
|
||||
case class NamesState[F[_]](
|
||||
stack: List[NamesFrame[F]] = Nil,
|
||||
rootArrows: Map[String, ArrowType] = Map.empty,
|
||||
definitions: Map[String, Name[F]] = Map.empty[String, Name[F]]
|
||||
) {
|
||||
|
||||
def allNames: LazyList[String] =
|
||||
LazyList.from(stack).flatMap(s => s.names.keys ++ s.arrows.keys).appendedAll(rootArrows.keys)
|
||||
|
||||
def allArrows: LazyList[String] =
|
||||
LazyList.from(stack).flatMap(_.arrows.keys).appendedAll(rootArrows.keys)
|
||||
}
|
||||
|
||||
case class NamesFrame[F[_]](
|
||||
token: Token[F],
|
||||
names: Map[String, Type] = Map.empty,
|
||||
arrows: Map[String, ArrowType] = Map.empty
|
||||
) {
|
||||
def addName(n: String, t: Type): NamesFrame[F] = copy[F](names = names.updated(n, t))
|
||||
def addArrow(n: String, g: ArrowType): NamesFrame[F] = copy[F](arrows = arrows.updated(n, g))
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
package aqua.semantics.rules.names
|
||||
|
||||
import aqua.parser.lexer.{Name, Token}
|
||||
import aqua.types.{ArrowType, Type}
|
||||
import cats.kernel.Monoid
|
||||
|
||||
case class NamesState[F[_]](
|
||||
stack: List[NamesState.Frame[F]] = Nil,
|
||||
rootArrows: Map[String, ArrowType] = Map.empty,
|
||||
definitions: Map[String, Name[F]] = Map.empty[String, Name[F]]
|
||||
) {
|
||||
|
||||
def allNames: LazyList[String] =
|
||||
LazyList.from(stack).flatMap(s => s.names.keys ++ s.arrows.keys).appendedAll(rootArrows.keys)
|
||||
|
||||
def allArrows: LazyList[String] =
|
||||
LazyList.from(stack).flatMap(_.arrows.keys).appendedAll(rootArrows.keys)
|
||||
}
|
||||
|
||||
object NamesState {
|
||||
|
||||
case class Frame[F[_]](
|
||||
token: Token[F],
|
||||
names: Map[String, Type] = Map.empty,
|
||||
arrows: Map[String, ArrowType] = Map.empty
|
||||
) {
|
||||
def addName(n: String, t: Type): NamesState.Frame[F] = copy[F](names = names.updated(n, t))
|
||||
|
||||
def addArrow(n: String, g: ArrowType): NamesState.Frame[F] =
|
||||
copy[F](arrows = arrows.updated(n, g))
|
||||
}
|
||||
|
||||
implicit def namesStateMonoid[F[_]]: Monoid[NamesState[F]] = new Monoid[NamesState[F]] {
|
||||
override def empty: NamesState[F] = NamesState[F]()
|
||||
|
||||
override def combine(x: NamesState[F], y: NamesState[F]): NamesState[F] =
|
||||
NamesState(
|
||||
stack = Nil,
|
||||
rootArrows = x.rootArrows ++ y.rootArrows,
|
||||
definitions = x.definitions ++ y.definitions
|
||||
)
|
||||
}
|
||||
}
|
@ -1,21 +1,11 @@
|
||||
package aqua.semantics.rules.types
|
||||
|
||||
import aqua.semantics.rules.ReportError
|
||||
import aqua.parser.lexer.{
|
||||
ArrayTypeToken,
|
||||
ArrowTypeToken,
|
||||
BasicTypeToken,
|
||||
CustomTypeToken,
|
||||
IntoArray,
|
||||
IntoField,
|
||||
LambdaOp,
|
||||
Name,
|
||||
Token,
|
||||
TypeToken
|
||||
}
|
||||
import aqua.types.{ArrayType, ArrowType, DataType, ProductType, Type}
|
||||
import aqua.parser.lexer.Token
|
||||
|
||||
import aqua.types.{ArrowType, ProductType}
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{Chain, NonEmptyChain, NonEmptyList, NonEmptyMap, State, ValidatedNec}
|
||||
import cats.data.{NonEmptyMap, State}
|
||||
import cats.~>
|
||||
import monocle.Lens
|
||||
import cats.syntax.functor._
|
||||
@ -123,69 +113,3 @@ class TypesInterpreter[F[_], X](implicit lens: Lens[X, TypesState[F]], error: Re
|
||||
).as(false)
|
||||
}
|
||||
}
|
||||
|
||||
case class TypesState[F[_]](
|
||||
fields: Map[String, (Name[F], Type)] = Map.empty[String, (Name[F], Type)],
|
||||
strict: Map[String, Type] = Map.empty[String, Type],
|
||||
definitions: Map[String, CustomTypeToken[F]] = Map.empty[String, CustomTypeToken[F]]
|
||||
) {
|
||||
def isDefined(t: String): Boolean = strict.contains(t)
|
||||
|
||||
def resolveTypeToken(tt: TypeToken[F]): Option[Type] =
|
||||
tt match {
|
||||
case ArrayTypeToken(_, dtt) =>
|
||||
resolveTypeToken(dtt).collect { case it: DataType =>
|
||||
ArrayType(it)
|
||||
}
|
||||
case ctt: CustomTypeToken[F] => strict.get(ctt.value)
|
||||
case btt: BasicTypeToken[F] => Some(btt.value)
|
||||
case ArrowTypeToken(_, args, res) =>
|
||||
val strictArgs = args.map(resolveTypeToken).collect { case Some(dt: DataType) =>
|
||||
dt
|
||||
}
|
||||
val strictRes = res.flatMap(resolveTypeToken).collect { case dt: DataType =>
|
||||
dt
|
||||
}
|
||||
Option.when(strictRes.isDefined == res.isDefined && strictArgs.length == args.length)(
|
||||
ArrowType(strictArgs, strictRes)
|
||||
)
|
||||
}
|
||||
|
||||
def resolveArrowDef(ad: ArrowTypeToken[F]): ValidatedNec[(Token[F], String), ArrowType] =
|
||||
ad.resType.flatMap(resolveTypeToken) match {
|
||||
case resType if resType.isDefined == ad.resType.isDefined =>
|
||||
val (errs, argTypes) = ad.argTypes
|
||||
.map(tt => resolveTypeToken(tt).toRight(tt -> s"Type unresolved"))
|
||||
.foldLeft[(Chain[(Token[F], String)], Chain[Type])]((Chain.empty, Chain.empty)) {
|
||||
case ((errs, argTypes), Right(at)) => (errs, argTypes.append(at))
|
||||
case ((errs, argTypes), Left(e)) => (errs.append(e), argTypes)
|
||||
}
|
||||
|
||||
NonEmptyChain
|
||||
.fromChain(errs)
|
||||
.fold[ValidatedNec[(Token[F], String), ArrowType]](
|
||||
Valid(ArrowType(argTypes.toList, resType))
|
||||
)(Invalid(_))
|
||||
|
||||
case _ =>
|
||||
Invalid(NonEmptyChain.one(ad.resType.getOrElse(ad) -> "Cannot resolve the result type"))
|
||||
}
|
||||
|
||||
def resolveOps(rootT: Type, ops: List[LambdaOp[F]]): Either[(Token[F], String), Type] =
|
||||
ops.headOption.fold[Either[(Token[F], String), Type]](Right(rootT)) {
|
||||
case i @ IntoArray(f) =>
|
||||
rootT match {
|
||||
case ArrayType(intern) => resolveOps(intern, ops.tail).map[Type](ArrayType)
|
||||
case _ => Left(i -> s"Expected $rootT to be an array")
|
||||
}
|
||||
case i @ IntoField(name) =>
|
||||
rootT match {
|
||||
case pt @ ProductType(_, fields) =>
|
||||
fields(i.value)
|
||||
.toRight(i -> s"Field `${i.value}` not found in type `${pt.name}``")
|
||||
.flatMap(resolveOps(_, ops.tail))
|
||||
case _ => Left(i -> s"Expected product to resolve a field, got $rootT")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
package aqua.semantics.rules.types
|
||||
|
||||
import aqua.parser.lexer.{
|
||||
ArrayTypeToken,
|
||||
ArrowTypeToken,
|
||||
BasicTypeToken,
|
||||
CustomTypeToken,
|
||||
IntoArray,
|
||||
IntoField,
|
||||
LambdaOp,
|
||||
Name,
|
||||
Token,
|
||||
TypeToken
|
||||
}
|
||||
import aqua.types.{ArrayType, ArrowType, DataType, ProductType, Type}
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
|
||||
import cats.kernel.Monoid
|
||||
|
||||
case class TypesState[F[_]](
|
||||
fields: Map[String, (Name[F], Type)] = Map.empty[String, (Name[F], Type)],
|
||||
strict: Map[String, Type] = Map.empty[String, Type],
|
||||
definitions: Map[String, CustomTypeToken[F]] = Map.empty[String, CustomTypeToken[F]]
|
||||
) {
|
||||
def isDefined(t: String): Boolean = strict.contains(t)
|
||||
|
||||
def resolveTypeToken(tt: TypeToken[F]): Option[Type] =
|
||||
tt match {
|
||||
case ArrayTypeToken(_, dtt) =>
|
||||
resolveTypeToken(dtt).collect { case it: DataType =>
|
||||
ArrayType(it)
|
||||
}
|
||||
case ctt: CustomTypeToken[F] => strict.get(ctt.value)
|
||||
case btt: BasicTypeToken[F] => Some(btt.value)
|
||||
case ArrowTypeToken(_, args, res) =>
|
||||
val strictArgs = args.map(resolveTypeToken).collect { case Some(dt: DataType) =>
|
||||
dt
|
||||
}
|
||||
val strictRes = res.flatMap(resolveTypeToken).collect { case dt: DataType =>
|
||||
dt
|
||||
}
|
||||
Option.when(strictRes.isDefined == res.isDefined && strictArgs.length == args.length)(
|
||||
ArrowType(strictArgs, strictRes)
|
||||
)
|
||||
}
|
||||
|
||||
def resolveArrowDef(ad: ArrowTypeToken[F]): ValidatedNec[(Token[F], String), ArrowType] =
|
||||
ad.resType.flatMap(resolveTypeToken) match {
|
||||
case resType if resType.isDefined == ad.resType.isDefined =>
|
||||
val (errs, argTypes) = ad.argTypes
|
||||
.map(tt => resolveTypeToken(tt).toRight(tt -> s"Type unresolved"))
|
||||
.foldLeft[(Chain[(Token[F], String)], Chain[Type])]((Chain.empty, Chain.empty)) {
|
||||
case ((errs, argTypes), Right(at)) => (errs, argTypes.append(at))
|
||||
case ((errs, argTypes), Left(e)) => (errs.append(e), argTypes)
|
||||
}
|
||||
|
||||
NonEmptyChain
|
||||
.fromChain(errs)
|
||||
.fold[ValidatedNec[(Token[F], String), ArrowType]](
|
||||
Valid(ArrowType(argTypes.toList, resType))
|
||||
)(Invalid(_))
|
||||
|
||||
case _ =>
|
||||
Invalid(NonEmptyChain.one(ad.resType.getOrElse(ad) -> "Cannot resolve the result type"))
|
||||
}
|
||||
|
||||
def resolveOps(rootT: Type, ops: List[LambdaOp[F]]): Either[(Token[F], String), Type] =
|
||||
ops.headOption.fold[Either[(Token[F], String), Type]](Right(rootT)) {
|
||||
case i @ IntoArray(f) =>
|
||||
rootT match {
|
||||
case ArrayType(intern) => resolveOps(intern, ops.tail).map[Type](ArrayType)
|
||||
case _ => Left(i -> s"Expected $rootT to be an array")
|
||||
}
|
||||
case i @ IntoField(name) =>
|
||||
rootT match {
|
||||
case pt @ ProductType(_, fields) =>
|
||||
fields(i.value)
|
||||
.toRight(i -> s"Field `${i.value}` not found in type `${pt.name}``")
|
||||
.flatMap(resolveOps(_, ops.tail))
|
||||
case _ => Left(i -> s"Expected product to resolve a field, got $rootT")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object TypesState {
|
||||
|
||||
implicit def typesStateMonoid[F[_]]: Monoid[TypesState[F]] = new Monoid[TypesState[F]] {
|
||||
override def empty: TypesState[F] = TypesState()
|
||||
|
||||
override def combine(x: TypesState[F], y: TypesState[F]): TypesState[F] =
|
||||
TypesState(
|
||||
strict = x.strict ++ y.strict,
|
||||
definitions = x.definitions ++ y.definitions
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user