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:
Dmitry Kurinskiy 2021-04-19 16:22:45 +03:00 committed by GitHub
parent 2d1427b124
commit 7512648cd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 781 additions and 453 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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();
| });

View File

@ -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")

View File

@ -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")

View File

@ -1,3 +0,0 @@
package aqua.model
sealed trait AbilityModel extends Model

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View 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

View File

@ -0,0 +1,5 @@
package aqua.model
import aqua.types.Type
case class TypeModel(name: String, `type`: Type) extends Model

View File

@ -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

View 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)
}

View 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)))
}
}

View 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)
}

View 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))
}
}

View File

@ -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

View 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)
}

View File

@ -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)
})

View 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))
}

View File

@ -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

View 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))
}

View File

@ -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 + "\"")

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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("==")

View File

@ -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]])

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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"))
}
}

View File

@ -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"))
}

View File

@ -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))
)

View File

@ -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"))
}
)

View File

@ -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

View File

@ -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"))
}

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))
)
}
}

View File

@ -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"))

View File

@ -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]]
)

View File

@ -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
)
}
}

View File

@ -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))
}

View File

@ -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
)
}
}

View File

@ -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")
}
}
}

View File

@ -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
)
}
}