Streams support (#87)

* #29 going to support streams

* Added StreamName

* StreamName removed

* Streams support works

* Debug println removed

Co-authored-by: Dima <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
Dmitry Kurinskiy 2021-04-27 16:59:12 +03:00 committed by GitHub
parent 3b3ff24133
commit 27f2912c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 339 additions and 195 deletions

View File

@ -12,6 +12,7 @@ data User:
service Test("test"):
getUserList: -> []User
doSomething: -> bool
check: bool -> ()
func initAfterJoin(me: User) -> []User:
allUsers <- Test.getUserList()
@ -26,3 +27,18 @@ func initAfterJoin(me: User) -> []User:
Op.identity()
par Test.doSomething()
<- allUsers
func handleArr(arr: []bool):
for b <- arr:
Test.check(b)
func checkStreams(ch: []string) -> []bool:
stream: *bool
stream <- Peer.is_connected("write once")
stream <- Peer.is_connected("write twice")
for b <- ch:
on b:
stream <- Peer.is_connected(b)
handleArr(stream)
<- stream

View File

@ -3,6 +3,7 @@ package aqua.backend.air
import aqua.model._
import aqua.model.func.Call
import aqua.model.func.body._
import aqua.types.StreamType
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
@ -24,9 +25,13 @@ object AirGen {
def valueToData(vm: ValueModel): DataView = vm match {
case LiteralModel(value) => DataView.StringScalar(value)
case VarModel(name, lambda) =>
if (lambda.isEmpty) DataView.Variable(name)
else DataView.VarLens(name, lambdaToString(lambda.toList))
case VarModel(name, t, lambda) =>
val n = t match {
case _: StreamType => "$" + name
case _ => name
}
if (lambda.isEmpty) DataView.Variable(n)
else DataView.VarLens(n, lambdaToString(lambda.toList))
}
def opsToSingle(ops: Chain[AirGen]): AirGen = ops.toList match {
@ -76,8 +81,11 @@ object AirGen {
peerId.map(valueToData).getOrElse(DataView.InitPeerId),
valueToData(serviceId),
funcName,
args.map(_.model).map(valueToData),
exportTo
args.map(valueToData),
exportTo.map {
case Call.Export(name, _: StreamType) => "$" + name
case Call.Export(name, _) => name
}
)
)

View File

@ -39,7 +39,7 @@ case class TypescriptFunc(func: FuncCallable) {
}.mkString("\n")
val retType = func.ret
.map(_.`type`)
.map(_._2)
.fold("void")(typeToTs)
val returnVal =
@ -90,6 +90,7 @@ object TypescriptFunc {
def typeToTs(t: Type): String = t match {
case ArrayType(t) => typeToTs(t) + "[]"
case StreamType(t) => typeToTs(t) + "[]"
case pt: ProductType =>
s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
case st: ScalarType if ScalarType.number(st) => "number"

View File

@ -1,5 +1,6 @@
package aqua.model
import aqua.types.Type
import cats.data.Chain
sealed trait ValueModel {
@ -16,7 +17,8 @@ sealed trait LambdaModel
case object IntoArrayModel extends LambdaModel
case class IntoFieldModel(field: String) extends LambdaModel
case class VarModel(name: String, lambda: Chain[LambdaModel] = Chain.empty) extends ValueModel {
case class VarModel(name: String, `type`: Type, lambda: Chain[LambdaModel] = Chain.empty)
extends ValueModel {
def deriveFrom(vm: VarModel): VarModel = vm.copy(lambda = vm.lambda ++ lambda)
override def resolveWith(map: Map[String, ValueModel]): ValueModel = {
@ -41,7 +43,7 @@ case class VarModel(name: String, lambda: Chain[LambdaModel] = Chain.empty) exte
res <- two(variable)
<- variable
*/
case vm @ VarModel(nn, _) if nn == name => deriveFrom(vm)
case vm @ VarModel(nn, _, _) if nn == name => deriveFrom(vm)
// it couldn't go to a cycle as long as the semantics protects it
case _ => n.resolveWith(map)
}

View File

@ -9,20 +9,19 @@ import cats.syntax.functor._
* @param args Argument definitions
* @param callWith Values provided for arguments
*/
case class ArgsCall(args: List[ArgDef], callWith: List[Call.Arg]) {
case class ArgsCall(args: List[ArgDef], callWith: List[ValueModel]) {
// 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 zipped: List[(ArgDef, ValueModel)] = args zip callWith
lazy val dataArgs: Map[String, ValueModel] =
zipped.collect { case (ArgDef.Data(name, _), Call.Arg(value, _)) =>
zipped.collect { case (ArgDef.Data(name, _), 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) =>
case (ArgDef.Arrow(name, _), VarModel(value, _, _)) if arrowsInScope.contains(value) =>
name -> arrowsInScope(value)
}.toMap
}
@ -33,7 +32,7 @@ object ArgsCall {
arrow: ArrowType,
argPrefix: String = "arg",
retName: String = "init_call_res"
): (ArgsDef, Call, Option[Call.Arg]) = {
): (ArgsDef, Call, Option[Call.Export]) = {
val argNamesTypes = arrow.args.zipWithIndex.map(iv => iv.map(i => argPrefix + i).swap)
val argsDef = ArgsDef(argNamesTypes.map {
@ -43,12 +42,12 @@ object ArgsCall {
val call = Call(
argNamesTypes.map { case (a, t) =>
Call.Arg(VarModel(a), t)
VarModel(a, t)
},
arrow.res.as(retName)
arrow.res.map(Call.Export(retName, _))
)
(argsDef, call, arrow.res.map(t => Call.Arg(VarModel(retName), t)))
(argsDef, call, arrow.res.map(t => Call.Export(retName, t)))
}
}

View File

@ -11,10 +11,10 @@ case class ArgsDef(args: List[ArgDef]) {
def types: List[Type] = args.map(_.`type`)
def toCallArgs: List[Call.Arg] = args.map(ad => Call.Arg(VarModel(ad.name), ad.`type`))
def toCallArgs: List[VarModel] = args.map(ad => VarModel(ad.name, ad.`type`))
lazy val dataArgNames: Chain[String] = Chain.fromSeq(args.collect { case ArgDef.Data(n, _) =>
n
lazy val dataArgs: Chain[ArgDef.Data] = Chain.fromSeq(args.collect { case ad: ArgDef.Data =>
ad
})
lazy val arrowArgs: Chain[ArgDef.Arrow] = Chain.fromSeq(args.collect { case ad: ArgDef.Arrow =>

View File

@ -1,24 +1,24 @@
package aqua.model.func
import aqua.model.ValueModel
import aqua.model.{ValueModel, VarModel}
import aqua.types.Type
case class Call(args: List[Call.Arg], exportTo: Option[String]) {
case class Call(args: List[ValueModel], exportTo: Option[Call.Export]) {
def mapValues(f: ValueModel => ValueModel): Call =
Call(
args.map(_.mapValues(f)),
args.map(f),
exportTo
)
def mapExport(f: String => String): Call = copy(exportTo = exportTo.map(f))
def mapExport(f: String => String): Call = copy(exportTo = exportTo.map(_.mapName(f)))
}
object Call {
case class Arg(model: ValueModel, `type`: Type) {
case class Export(name: String, `type`: Type) {
def mapName(f: String => String): Export = copy(f(name))
def mapValues(f: ValueModel => ValueModel): Arg =
copy(f(model))
def model: ValueModel = VarModel(name, `type`)
}
}

View File

@ -2,7 +2,7 @@ package aqua.model.func
import aqua.model.func.body.{CallArrowTag, FuncOp, OpTag}
import aqua.model.{ValueModel, VarModel}
import aqua.types.ArrowType
import aqua.types.{ArrowType, DataType, Type}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
@ -11,7 +11,7 @@ case class FuncCallable(
funcName: String,
body: FuncOp,
args: ArgsDef,
ret: Option[Call.Arg],
ret: Option[(ValueModel, Type)],
capturedArrows: Map[String, FuncCallable],
capturedValues: Map[String, ValueModel]
) {
@ -19,7 +19,7 @@ case class FuncCallable(
def arrowType: ArrowType =
ArrowType(
args.types,
ret.map(_.`type`)
ret.map(_._2)
)
def findNewNames(forbidden: Set[String], introduce: Set[String]): Map[String, String] =
@ -52,7 +52,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 -- call.exportTo
val treeDefines = treeWithValues.definesValueNames.value -- call.exportTo.map(_.name)
// We have some names in scope (forbiddenNames), can't introduce them again; so find new names
val shouldRename = findNewNames(forbiddenNames, treeDefines)
@ -61,7 +61,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(_.model).map(_.resolveWith(argsToData)).map {
val result = ret.map(_._1).map(_.resolveWith(argsToData)).map {
case v: VarModel if shouldRename.contains(v.name) => v.copy(shouldRename(v.name))
case v => v
}
@ -79,8 +79,8 @@ case class FuncCallable(
case ((noNames, resolvedExports), CallArrowTag(fn, c)) if allArrows.contains(fn) =>
// Apply arguments to a function recursion
val callResolved = c.mapValues(_.resolveWith(resolvedExports))
val possibleArrowNames = callResolved.args.collect {
case Call.Arg(VarModel(m, _), _: ArrowType) => m
val possibleArrowNames = callResolved.args.collect { case VarModel(m, _: ArrowType, _) =>
m
}.toSet
val (appliedOp, value) =
@ -92,7 +92,10 @@ case class FuncCallable(
// TODO: actually it's done and dropped so keep and pass it instead
val newNames = appliedOp.definesValueNames.value
// At the very end, will need to resolve what is used as results with the result values
(noNames ++ newNames, resolvedExports ++ c.exportTo.zip(value)) -> appliedOp.tree
(
noNames ++ newNames,
resolvedExports ++ c.exportTo.map(_.name).zip(value)
) -> appliedOp.tree
case (acc @ (_, resolvedExports), tag) =>
tag match {
case CallArrowTag(fn, _) if !allArrows.contains(fn) =>

View File

@ -2,11 +2,12 @@ package aqua.model.func
import aqua.model.func.body.FuncOp
import aqua.model.{Model, ValueModel}
import aqua.types.Type
case class FuncModel(
name: String,
args: ArgsDef,
ret: Option[Call.Arg],
ret: Option[(ValueModel, Type)],
body: FuncOp
) extends Model {

View File

@ -23,9 +23,9 @@ case class FuncOp(tree: Cofree[Chain, OpTag]) extends Model {
def definesValueNames: Eval[Set[String]] = cata[Set[String]] {
case (CallArrowTag(_, Call(_, Some(export))), acc) =>
Eval.later(acc.foldLeft(Set(export))(_ ++ _))
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (CallServiceTag(_, _, Call(_, Some(export)), _), acc) =>
Eval.later(acc.foldLeft(Set(export))(_ ++ _))
Eval.later(acc.foldLeft(Set(export.name))(_ ++ _))
case (NextTag(export), acc) => Eval.later(acc.foldLeft(Set(export))(_ ++ _))
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
}

View File

@ -3,23 +3,25 @@ package aqua.model.transform
import aqua.model.ValueModel
import aqua.model.func.Call
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.types.DataType
trait ArgsProvider {
def transform(op: FuncOp): FuncOp
}
case class ArgsFromService(dataServiceId: ValueModel, names: Seq[String]) extends ArgsProvider {
case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataType)])
extends ArgsProvider {
def getDataOp(name: String): FuncOp =
def getDataOp(name: String, t: DataType): FuncOp =
FuncOps.callService(
dataServiceId,
name,
Call(Nil, Some(name))
Call(Nil, Some(Call.Export(name, t)))
)
def transform(op: FuncOp): FuncOp =
FuncOps.seq(
names.map(getDataOp) :+ op: _*
names.map((getDataOp _).tupled) :+ op: _*
)
}

View File

@ -3,7 +3,6 @@ package aqua.model.transform
import aqua.model.{LiteralModel, ValueModel}
import aqua.model.func.Call
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.types.ScalarType.string
case class ErrorsCatcher(
enabled: Boolean,
@ -28,7 +27,7 @@ case class ErrorsCatcher(
object ErrorsCatcher {
// TODO not a string
val lastErrorArg: Call.Arg = Call.Arg(LiteralModel("%last_error%"), string)
val lastErrorArg: ValueModel = LiteralModel("%last_error%")
val lastErrorCall: Call = Call(
lastErrorArg :: Nil,

View File

@ -5,6 +5,7 @@ import aqua.model.func.{ArgDef, ArgsCall, ArgsDef, Call, FuncCallable}
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.types.ArrowType
import cats.Eval
import cats.syntax.apply._
case class ResolveFunc(
transform: FuncOp => FuncOp,
@ -14,11 +15,11 @@ case class ResolveFunc(
arrowCallbackPrefix: String = "init_peer_callable_"
) {
def returnCallback(func: FuncCallable): Option[FuncOp] = func.ret.map { retArg =>
def returnCallback(func: FuncCallable): Option[FuncOp] = func.ret.map { case (retModel, _) =>
callback(
respFuncName,
Call(
retArg :: Nil,
retModel :: Nil,
None
)
)
@ -30,7 +31,7 @@ case class ResolveFunc(
arrowCallbackPrefix + name,
callback(name, call),
args,
ret,
(ret.map(_.model), arrowType.res).mapN(_ -> _),
Map.empty,
Map.empty
)
@ -63,7 +64,7 @@ case class ResolveFunc(
def resolve(func: FuncCallable, funcArgName: String = "_func"): Eval[FuncOp] =
wrap(func)
.resolve(
Call(Call.Arg(VarModel(funcArgName), func.arrowType) :: Nil, None),
Call(VarModel(funcArgName, func.arrowType) :: Nil, None),
Map(funcArgName -> func),
Set.empty
)

View File

@ -3,6 +3,7 @@ package aqua.model.transform
import aqua.model.func.body._
import aqua.model.func.FuncCallable
import aqua.model.VarModel
import aqua.types.ScalarType
import cats.data.Chain
import cats.free.Cofree
@ -10,7 +11,7 @@ object Transform {
def forClient(func: FuncCallable, conf: BodyConfig): Cofree[Chain, OpTag] = {
val initCallable: InitPeerCallable = InitViaRelayCallable(
Chain.one(VarModel(conf.relayVarName))
Chain.one(VarModel(conf.relayVarName, ScalarType.string))
)
val errorsCatcher = ErrorsCatcher(
enabled = conf.wrapWithXor,
@ -19,7 +20,12 @@ object Transform {
initCallable
)
val argsProvider: ArgsProvider =
ArgsFromService(conf.dataSrvId, conf.relayVarName +: func.args.dataArgNames.toList)
ArgsFromService(
conf.dataSrvId,
conf.relayVarName -> ScalarType.string :: func.args.dataArgs.toList.map(add =>
add.name -> add.dataType
)
)
val transform =
errorsCatcher.transform _ compose initCallable.transform compose argsProvider.transform

View File

@ -1,16 +1,7 @@
package aqua.model
import aqua.model.func.Call
import aqua.model.func.body.{
CallServiceTag,
FuncOp,
FuncOps,
MatchMismatchTag,
OnTag,
OpTag,
SeqTag,
XorTag
}
import aqua.model.func.body._
import aqua.model.transform.BodyConfig
import aqua.types.ScalarType
import cats.Eval
@ -30,10 +21,9 @@ case class Node(tag: OpTag, ops: List[Node] = Nil) {
else
Console.BLUE + left + Console.RED + " != " + Console.YELLOW + right)
private def diffArg(left: Call.Arg, right: Call.Arg): String =
private def diffArg(left: ValueModel, right: ValueModel): String =
Console.GREEN + "(" +
equalOrNot(left.model, right.model) + Console.GREEN + ", " +
equalOrNot(left.`type`, right.`type`) + Console.GREEN + ")"
equalOrNot(left, right) + Console.GREEN + ")"
private def diffCall(left: Call, right: Call): String =
if (left == right) Console.GREEN + left + Console.RESET
@ -95,7 +85,7 @@ object Node {
Cofree(tree.tag, Eval.later(Chain.fromSeq(tree.ops.map(nodeToCof))))
val relay = LiteralModel("relay")
val relayV = VarModel("relay")
val relayV = VarModel("relay", ScalarType.string)
val initPeer = LiteralModel.initPeerId
val emptyCall = Call(Nil, None)
val otherPeer = LiteralModel("other-peer")
@ -111,7 +101,7 @@ object Node {
CallServiceTag(
bc.errorHandlingCallback,
bc.errorFuncName,
Call(Call.Arg(LiteralModel("%last_error%"), ScalarType.string) :: Nil, None),
Call(LiteralModel("%last_error%") :: Nil, None),
Option(on)
)
)
@ -120,7 +110,7 @@ object Node {
CallServiceTag(
bc.callbackSrvId,
bc.respFuncName,
Call(Call.Arg(value, ScalarType.string) :: Nil, None),
Call(value :: Nil, None),
Option(on)
)
)
@ -129,7 +119,7 @@ object Node {
CallServiceTag(
bc.dataSrvId,
name,
Call(Nil, Some(name)),
Call(Nil, Some(Call.Export(name, ScalarType.string))),
Option(on)
)
)

View File

@ -19,7 +19,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
"ret",
FuncOp(on(otherPeer, Nil, call(1))),
ArgsDef.empty,
Some(Call.Arg(ret, ScalarType.string)),
Some((ret, ScalarType.string)),
Map.empty,
Map.empty
)
@ -57,7 +57,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
"ret",
FuncOp(seq(call(0), on(otherPeer, Nil, call(1)))),
ArgsDef.empty,
Some(Call.Arg(ret, ScalarType.string)),
Some((ret, ScalarType.string)),
Map.empty,
Map.empty
)
@ -104,9 +104,18 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val f1: FuncCallable =
FuncCallable(
"f1",
FuncOp(Node(CallServiceTag(LiteralModel("\"srv1\""), "foo", Call(Nil, Some("v")), None))),
FuncOp(
Node(
CallServiceTag(
LiteralModel("\"srv1\""),
"foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))),
None
)
)
),
ArgsDef.empty,
Some(Call.Arg(VarModel("v"), ScalarType.string)),
Some((VarModel("v", ScalarType.string), ScalarType.string)),
Map.empty,
Map.empty
)
@ -115,10 +124,10 @@ class TransformSpec extends AnyFlatSpec with Matchers {
FuncCallable(
"f2",
FuncOp(
Node(CallArrowTag("callable", Call(Nil, Some("v"))))
Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string)))))
),
ArgsDef.empty,
Some(Call.Arg(VarModel("v"), ScalarType.string)),
Some((VarModel("v", ScalarType.string), ScalarType.string)),
Map("callable" -> f1),
Map.empty
)
@ -134,12 +143,17 @@ class TransformSpec extends AnyFlatSpec with Matchers {
seq(
dataCall(bc, "relay", initPeer),
Node(
CallServiceTag(LiteralModel("\"srv1\""), "foo", Call(Nil, Some("v")), Some(initPeer))
CallServiceTag(
LiteralModel("\"srv1\""),
"foo",
Call(Nil, Some(Call.Export("v", ScalarType.string))),
Some(initPeer)
)
),
on(
initPeer,
relayV :: Nil,
respCall(bc, VarModel("v"), initPeer)
respCall(bc, VarModel("v", ScalarType.string), initPeer)
)
)
)

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]) | ArrowTypeToken.`arrowWithNames`)).map {
(Name
.p[F] ~ ((` : ` *> ArrowTypeToken.`arrowdef`[F]) | ArrowTypeToken.`arrowWithNames`)).map {
case (name, t) =>
ArrowTypeExpr(name, t)
}

View File

@ -0,0 +1,19 @@
package aqua.parser.expr
import aqua.parser.Expr
import aqua.parser.lexer.{Name, Token, TypeToken}
import aqua.parser.lift.LiftParser
import cats.Comonad
import cats.parse.Parser
import Token._
case class DeclareStreamExpr[F[_]](name: Name[F], `type`: TypeToken[F]) extends Expr[F]
object DeclareStreamExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: Parser[DeclareStreamExpr[F]] =
((Name.p[F] <* ` : `) ~ TypeToken.`typedef`[F]).map { case (name, t) =>
DeclareStreamExpr(name, t)
}
}

View File

@ -12,7 +12,7 @@ case class FieldTypeExpr[F[_]](name: Name[F], `type`: DataTypeToken[F]) extends
object FieldTypeExpr extends Expr.Leaf {
override def p[F[_]: LiftParser: Comonad]: Parser[FieldTypeExpr[F]] =
((Name.p[F] <* ` : `) ~ DataTypeToken.`datatypedef`[F]).map {
case (name, t) => FieldTypeExpr(name, t)
((Name.p[F] <* ` : `) ~ DataTypeToken.`datatypedef`[F]).map { case (name, t) =>
FieldTypeExpr(name, t)
}
}

View File

@ -25,7 +25,8 @@ object FuncExpr
ParExpr,
ForExpr,
IfExpr,
ElseOtherwiseExpr
ElseOtherwiseExpr,
DeclareStreamExpr
) {
override def p[F[_]: LiftParser: Comonad]: Parser[FuncExpr[F]] =

View File

@ -10,7 +10,7 @@ case class Arg[F[_]](name: Name[F], `type`: TypeToken[F])
object Arg {
def p[F[_]: LiftParser: Comonad]: P[Arg[F]] =
((Name.p[F] <* ` : `) ~ TypeToken.`typedef`[F]).map {
case (name, t) => Arg(name, t)
((Name.p[F] <* ` : `) ~ TypeToken.`typedef`[F]).map { case (name, t) =>
Arg(name, t)
}
}

View File

@ -43,9 +43,7 @@ object Token {
val `:` : P[Unit] = P.char(':')
val ` : ` : P[Unit] = P.char(':').surroundedBy(` `.?)
val `name`: P[String] = (P.charIn(az) ~ P.charsWhile(anum_).?).map { case (c, s)
c.toString ++ s.getOrElse("")
}
val `name`: P[String] = (P.charIn(az) ~ P.charsWhile(anum_).?).string
val `Class`: P[String] = (P.charIn(AZ) ~ P.charsWhile(anum_).?).map { case (c, s)
c.toString ++ s.getOrElse("")
@ -59,8 +57,10 @@ object Token {
val `.` : P[Unit] = P.char('.')
val `"` : P[Unit] = P.char('"')
val `*` : P[Unit] = P.char('*')
val `[]` : P[Unit] = P.string("[]")
val `(` : P[Unit] = ` `.?.with1 *> P.char('(') <* ` `.?
val `)` : P[Unit] = ` `.?.with1 *> P.char(')') <* ` `.?
val `()` : P[Unit] = P.string("()")
val ` -> ` : P[Unit] = ` `.?.with1 *> P.string("->") <* ` `.?
val ` <- ` : P[Unit] = (` `.?.with1 *> P.string("<-") <* ` `.?).backtrack
val `=` : P[Unit] = P.string("=")

View File

@ -17,6 +17,18 @@ case class ArrayTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataT
override def as[T](v: T): F[T] = unit.as(v)
}
case class StreamTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F])
extends DataTypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v)
}
object StreamTypeToken {
def `streamtypedef`[F[_]: LiftParser: Comonad]: P[StreamTypeToken[F]] =
(`*`.lift ~ DataTypeToken.`datatypedef`[F]).map(ud => StreamTypeToken(ud._1, ud._2))
}
case class CustomTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken[F] {
override def as[T](v: T): F[T] = name.as(v)
@ -64,7 +76,7 @@ object ArrowTypeToken {
def `arrowdef`[F[_]: LiftParser: Comonad]: P[ArrowTypeToken[F]] =
(comma0(DataTypeToken.`datatypedef`).with1 ~ ` -> `.lift ~
(DataTypeToken.`datatypedef`
.map(Some(_)) | P.string("()").as(None))).map { case ((args, point), res)
.map(Some(_)) | `()`.as(None))).map { case ((args, point), res)
ArrowTypeToken(point, args, res)
}
@ -75,27 +87,23 @@ object ArrowTypeToken {
}
}
case class AquaArrowType[F[_]](args: List[TypeToken[F]], res: Option[DataTypeToken[F]])
extends ArrowDef[F] {
override def argTypes: List[TypeToken[F]] = args
override def resType: Option[DataTypeToken[F]] = res
}
object DataTypeToken {
def `arraytypedef`[F[_]: LiftParser: Comonad]: P[ArrayTypeToken[F]] =
(P.string("[]").lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
(`[]`.lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf(
P.defer(`arraytypedef`[F]) :: BasicTypeToken.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: BasicTypeToken
.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil
)
}
object TypeToken {
def `typedef`[F[_]: LiftParser: Comonad]: P[TypeToken[F]] =
P.oneOf(ArrowTypeToken.`arrowdef`.backtrack :: DataTypeToken.`datatypedef` :: Nil)
P.oneOf(
ArrowTypeToken.`arrowdef`.backtrack :: DataTypeToken.`datatypedef` :: Nil
)
}

View File

@ -21,6 +21,7 @@ object ExprSem {
case expr: AbilityIdExpr[F] => new AbilityIdSem(expr).program[G]
case expr: AliasExpr[F] => new AliasSem(expr).program[G]
case expr: ConstantExpr[F] => new ConstantSem(expr).program[G]
case expr: DeclareStreamExpr[F] => new DeclareStreamSem(expr).program[G]
case expr: ArrowTypeExpr[F] => new ArrowTypeSem(expr).program[G]
case expr: CallArrowExpr[F] => new CallArrowSem(expr).program[G]
case expr: DataStructExpr[F] => new DataStructSem(expr).program[G]

View File

@ -1,6 +1,6 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.model.{Model, ValueModel}
import aqua.model.func.Call
import aqua.model.func.body.{CallArrowTag, CallServiceTag, FuncOp}
import aqua.parser.expr.CallArrowExpr
@ -9,7 +9,7 @@ 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
import aqua.types.{ArrowType, ScalarType, StreamType, Type}
import cats.free.Free
import cats.syntax.apply._
import cats.syntax.flatMap._
@ -25,16 +25,29 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
at: ArrowType
)(implicit
N: NamesAlgebra[F, Alg],
T: TypesAlgebra[F, Alg],
V: ValuesAlgebra[F, Alg]
): Free[Alg, List[Call.Arg]] =
): Free[Alg, (List[ValueModel], Option[Type])] =
V.checkArguments(expr.funcName, at, args) >> variable
.fold(freeUnit[Alg])(exportVar =>
.fold(freeUnit[Alg].as(Option.empty[Type]))(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[Call.Arg]](Nil)) { case (acc, v) =>
(acc, V.resolveType(v)).mapN((a, b) => a ++ b.map(Call.Arg(ValuesAlgebra.valueToModel(v), _)))
freeUnit[Alg].as(Option.empty[Type])
)(resType =>
N.read(exportVar, mustBeDefined = false).flatMap {
case Some(t @ StreamType(st)) =>
T.ensureTypeMatches(exportVar, st, resType).as(Option(t))
case _ => N.define(exportVar, resType).as(at.res)
}
)
) >>= { (v: Option[Type]) =>
args
.foldLeft(Free.pure[Alg, List[ValueModel]](Nil)) { case (acc, v) =>
(acc, V.resolveType(v)).mapN((a, b) =>
a ++ b.map(bt => ValuesAlgebra.valueToModel(v, bt))
)
}
.map(_ -> v)
}
private def toModel[Alg[_]](implicit
@ -50,30 +63,29 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
Option(at -> sid) // Here we assume that Ability is a Service that must be resolved
case _ => None
}.flatMap(_.fold(Free.pure[Alg, Option[FuncOp]](None)) { case (arrowType, serviceId) =>
checkArgsRes(arrowType)
.map(argsResolved =>
FuncOp.leaf(
CallServiceTag(
serviceId = ValuesAlgebra.valueToModel(serviceId),
funcName = funcName.value,
Call(argsResolved, variable.map(_.value))
)
checkArgsRes(arrowType).map { case (argsResolved, t) =>
FuncOp.leaf(
CallServiceTag(
// TODO service id type should not be hardcoded
serviceId = ValuesAlgebra.valueToModel(serviceId, ScalarType.string),
funcName = funcName.value,
Call(argsResolved, (variable.map(_.value), t).mapN(Call.Export))
)
)
}
.map(Option(_))
})
case None =>
N.readArrow(funcName)
.flatMap(_.fold(Free.pure[Alg, Option[FuncOp]](None)) { arrowType =>
checkArgsRes(arrowType)
.map(argsResolved =>
FuncOp.leaf(
CallArrowTag(
funcName = funcName.value,
Call(argsResolved, variable.map(_.value))
)
checkArgsRes(arrowType).map { case (argsResolved, t) =>
FuncOp.leaf(
CallArrowTag(
funcName = funcName.value,
Call(argsResolved, (variable.map(_.value), t).mapN(Call.Export))
)
)
}
.map(Option(_))
})
}
@ -84,6 +96,6 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
T: TypesAlgebra[F, Alg],
V: ValuesAlgebra[F, Alg]
): Prog[Alg, Model] =
toModel[Alg].map(_.getOrElse(Model.error("Coalgebra can't be converted to Model")))
toModel[Alg].map(_.getOrElse(Model.error("CallArrow can't be converted to Model")))
}

View File

@ -34,7 +34,7 @@ class ConstantSem[F[_]](val expr: ConstantExpr[F]) extends AnyVal {
case (_, Some(t), _) =>
N.defineConstant(expr.name, t) as (ConstantModel(
expr.name.value,
ValuesAlgebra.valueToModel(expr.value)
ValuesAlgebra.valueToModel(expr.value, t)
): Model)
}
} yield model

View File

@ -0,0 +1,35 @@
package aqua.semantics.expr
import aqua.model.Model
import aqua.parser.expr.DeclareStreamExpr
import aqua.semantics.Prog
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, StreamType}
import cats.free.Free
class DeclareStreamSem[F[_]](val expr: DeclareStreamExpr[F]) {
def program[Alg[_]](implicit
N: NamesAlgebra[F, Alg],
T: TypesAlgebra[F, Alg]
): Prog[Alg, Model] =
Prog.leaf(
T.resolveType(expr.`type`)
.flatMap {
case Some(t: StreamType) =>
N.define(expr.name, t)
case Some(at @ ArrayType(t)) =>
T.ensureTypeMatches(expr.`type`, StreamType(t), at)
case Some(t) =>
T.ensureTypeMatches(expr.`type`, StreamType(t), t)
case None =>
Free.pure[Alg, Boolean](false)
}
.map {
case true => Model.empty(s"Name `${expr.name.value}` defined successfully")
case false => Model.error(s"Name `${expr.name.value}` not defined")
}
)
}

View File

@ -7,7 +7,7 @@ import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, DataType}
import aqua.types.{ArrayType, StreamType, Type}
import cats.data.Chain
import cats.free.Free
import cats.syntax.flatMap._
@ -21,18 +21,20 @@ class ForSem[F[_]](val expr: ForExpr[F]) extends AnyVal {
T: TypesAlgebra[F, Alg]
): Prog[Alg, Model] =
Prog.around(
N.beginScope(expr.item) >> V.resolveType(expr.iterable).flatMap {
case Some(ArrayType(t)) =>
N.define(expr.item, t).void
case Some(dt: DataType) => T.ensureTypeMatches(expr.iterable, ArrayType(dt), dt).void
case _ => Free.pure[Alg, Unit](())
N.beginScope(expr.item) >> V.resolveType(expr.iterable).flatMap[Option[Type]] {
case Some(at @ ArrayType(t)) =>
N.define(expr.item, t).as(Option(at))
case Some(st @ StreamType(t)) =>
N.define(expr.item, t).as(Option(st))
case Some(dt: Type) =>
T.ensureTypeMatches(expr.iterable, ArrayType(dt), dt).as(Option.empty[Type])
case _ => Free.pure[Alg, Option[Type]](None)
},
(_: Unit, ops: Model) =>
// TODO streams should escape the scope
N.endScope() as (ops match {
case op: FuncOp =>
(stOpt: Option[Type], ops: Model) =>
N.endScope() as ((stOpt, ops) match {
case (Some(t), op: FuncOp) =>
FuncOp.wrap(
ForTag(expr.item.value, ValuesAlgebra.valueToModel(expr.iterable)),
ForTag(expr.item.value, ValuesAlgebra.valueToModel(expr.iterable, t)),
FuncOp.node(
expr.par.fold[OpTag](SeqTag)(_ => ParTag),
Chain(op, FuncOp.leaf(NextTag(expr.item.value)))

View File

@ -16,6 +16,7 @@ import cats.data.Chain
import cats.free.Free
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.apply._
class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
import expr._
@ -61,41 +62,43 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
// Check return value type
((funcArrow.res, retValue) match {
case (Some(t), Some(v)) =>
V.resolveType(v).flatMap {
V.resolveType(v).flatTap {
case Some(vt) => T.ensureTypeMatches(v, t, vt).void
case None => Free.pure[Alg, Unit](())
}
case _ =>
Free.pure[Alg, Unit](())
Free.pure[Alg, Option[Type]](None)
// Erase arguments and internal variables
}) >> A.endScope() >> N.endScope() >> (bodyGen match {
case bg: FuncOp if ret.isDefined == retValue.isDefined =>
val argNames = args.map(_.name.value)
}).flatMap(retType =>
A.endScope() >> N.endScope() >> (bodyGen match {
case bg: FuncOp if ret.isDefined == retValue.isDefined =>
val argNames = args.map(_.name.value)
val model = FuncModel(
name = name.value,
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
)
val model = FuncModel(
name = name.value,
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, retType, funcArrow.res).mapN { case (retV, retT, resT) =>
ValuesAlgebra.valueToModel(retV, retT) -> resT
},
body = bg
)
N.defineArrow(
name,
funcArrow,
isRoot = true
) as model
case m => Free.pure[Alg, Model](Model.error("Function body is not a funcOp, it's " + m))
})
N.defineArrow(
name,
funcArrow,
isRoot = true
) as model
case m => Free.pure[Alg, Model](Model.error("Function body is not a funcOp, it's " + m))
})
)
def program[Alg[_]](implicit
T: TypesAlgebra[F, Alg],

View File

@ -6,6 +6,7 @@ import aqua.parser.expr.IfExpr
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.semantics.Prog
import aqua.types.Type
import cats.free.Free
import cats.syntax.functor._
@ -21,26 +22,30 @@ class IfSem[F[_]](val expr: IfExpr[F]) extends AnyVal {
V.resolveType(expr.right).flatMap {
case Some(rt) =>
T.ensureTypeMatches(expr.right, lt, rt)
.map(m => Some(lt -> rt).filter(_ => m))
case None =>
Free.pure[Alg, Boolean](false)
Free.pure[Alg, Option[(Type, Type)]](None)
}
case None =>
V.resolveType(expr.right).as(false)
V.resolveType(expr.right).as[Option[(Type, Type)]](None)
},
(r: Boolean, ops: Model) =>
ops match {
case op: FuncOp if r =>
Free.pure[Alg, Model](
FuncOp.wrap(
MatchMismatchTag(
ValuesAlgebra.valueToModel(expr.left),
ValuesAlgebra.valueToModel(expr.right),
expr.eqOp.value
),
op
)
)
case _ => Free.pure[Alg, Model](Model.error("If expression errored"))
(r: Option[(Type, Type)], ops: Model) =>
r.fold(Free.pure[Alg, Model](Model.error("If expression errored in matching types"))) {
case (lt, rt) =>
ops match {
case op: FuncOp =>
Free.pure[Alg, Model](
FuncOp.wrap(
MatchMismatchTag(
ValuesAlgebra.valueToModel(expr.left, lt),
ValuesAlgebra.valueToModel(expr.right, rt),
expr.eqOp.value
),
op
)
)
case _ => Free.pure[Alg, Model](Model.error("Wrong body of the if expression"))
}
}
)
}

View File

@ -6,6 +6,7 @@ import aqua.parser.expr.OnExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.types.ScalarType
import cats.data.Chain
import cats.syntax.flatMap._
import cats.syntax.functor._
@ -28,8 +29,8 @@ class OnSem[F[_]](val expr: OnExpr[F]) extends AnyVal {
case op: FuncOp =>
FuncOp.wrap(
OnTag(
ValuesAlgebra.valueToModel(expr.peerId),
Chain.fromSeq(expr.via).map(ValuesAlgebra.valueToModel)
ValuesAlgebra.valueToModel(expr.peerId, ScalarType.string),
Chain.fromSeq(expr.via).map(ValuesAlgebra.valueToModel(_, ScalarType.string))
),
op
)

View File

@ -4,7 +4,7 @@ import aqua.model._
import aqua.parser.lexer._
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrowType, LiteralType, Type}
import aqua.types.{ArrowType, LiteralType, StreamType, Type}
import cats.data.Chain
import cats.free.Free
import cats.syntax.apply._
@ -82,10 +82,10 @@ object ValuesAlgebra {
case (f: IntoField[F]) :: tail => opsToModel(tail).prepend(IntoFieldModel(f.value))
}
def valueToModel[F[_]](v: Value[F]): ValueModel =
def valueToModel[F[_]](v: Value[F], t: Type): ValueModel =
v match {
case l: Literal[F] => LiteralModel(l.value)
case VarLambda(name, ops) => VarModel(name.value, opsToModel(ops))
case VarLambda(name, ops) => VarModel(name.value, t, opsToModel(ops))
}
implicit def deriveValuesAlgebra[F[_], Alg[_]](implicit

View File

@ -5,7 +5,7 @@ import aqua.types.{ArrowType, Type}
sealed trait NameOp[F[_], T]
case class ReadName[F[_]](name: Name[F]) extends NameOp[F, Option[Type]]
case class ReadName[F[_]](name: Name[F], mustBeDefined: Boolean) extends NameOp[F, Option[Type]]
case class ConstantDefined[F[_]](name: Name[F]) extends NameOp[F, Option[Type]]
case class ReadArrow[F[_]](name: Name[F]) extends NameOp[F, Option[ArrowType]]

View File

@ -7,9 +7,10 @@ import cats.free.Free
class NamesAlgebra[F[_], Alg[_]](implicit V: InjectK[NameOp[F, *], Alg]) {
def read(name: Name[F]): Free[Alg, Option[Type]] =
Free.liftInject[Alg](ReadName(name))
def read(name: Name[F], mustBeDefined: Boolean = true): Free[Alg, Option[Type]] =
Free.liftInject[Alg](ReadName(name, mustBeDefined))
// TODO can be implemented via read?
def constantDefined(name: Name[F]): Free[Alg, Option[Type]] =
Free.liftInject[Alg](ConstantDefined(name))

View File

@ -37,11 +37,11 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re
.orElseF(readName(rn.name.value))
.value
.flatTap {
case Some(_) => State.pure(())
case None =>
case None if rn.mustBeDefined =>
getState.flatMap(st =>
report(rn.name, "Undefined name, available: " + st.allNames.mkString(", "))
)
case _ => State.pure(())
}
case rn: ConstantDefined[F] =>
constantDefined(rn.name.value)

View File

@ -99,6 +99,7 @@ class TypesInterpreter[F[_], X](implicit lens: Lens[X, TypesState[F]], error: Re
}
case etm: EnsureTypeMatches[F] =>
// TODO in case of two literals, check for types intersection?
if (etm.expected.acceptsValueOf(etm.`given`)) State.pure(true)
else
report(etm.token, s"Types mismatch, expected: ${etm.expected}, given: ${etm.`given`}")

View File

@ -9,10 +9,11 @@ import aqua.parser.lexer.{
IntoField,
LambdaOp,
Name,
StreamTypeToken,
Token,
TypeToken
}
import aqua.types.{ArrayType, ArrowType, DataType, ProductType, Type}
import aqua.types.{ArrayType, ArrowType, DataType, ProductType, StreamType, Type}
import cats.data.Validated.{Invalid, Valid}
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
import cats.kernel.Monoid
@ -30,6 +31,10 @@ case class TypesState[F[_]](
resolveTypeToken(dtt).collect { case it: DataType =>
ArrayType(it)
}
case StreamTypeToken(_, dtt) =>
resolveTypeToken(dtt).collect { case it: DataType =>
StreamType(it)
}
case ctt: CustomTypeToken[F] => strict.get(ctt.value)
case btt: BasicTypeToken[F] => Some(btt.value)
case ArrowTypeToken(_, args, res) =>

View File

@ -73,15 +73,9 @@ case class ProductType(name: String, fields: NonEmptyMap[String, Type]) extends
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
}
sealed trait CallableType extends Type {
def acceptsAsArguments(valueTypes: List[Type]): Boolean
def args: List[Type]
def res: Option[Type]
}
case class ArrowType(args: List[Type], res: Option[Type]) extends Type {
case class ArrowType(args: List[Type], res: Option[Type]) extends CallableType {
override def acceptsAsArguments(valueTypes: List[Type]): Boolean =
def acceptsAsArguments(valueTypes: List[Type]): Boolean =
(args.length == valueTypes.length) && args
.zip(valueTypes)
.forall(av => av._1.acceptsValueOf(av._2))
@ -90,6 +84,8 @@ case class ArrowType(args: List[Type], res: Option[Type]) extends CallableType {
args.map(_.toString).mkString(", ") + " -> " + res.map(_.toString).getOrElse("()")
}
case class StreamType(element: Type) extends DataType
object Type {
import Double.NaN
@ -132,9 +128,11 @@ object Type {
case (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 1.0
case (x: ArrayType, y: ArrayType) => cmp(x.element, y.element)
case (x: ArrayType, y: StreamType) => cmp(x.element, y.element)
case (x: StreamType, y: StreamType) => cmp(x.element, y.element)
case (ProductType(_, xFields), ProductType(_, yFields)) =>
cmpProd(xFields, yFields)
case (l: CallableType, r: CallableType) =>
case (l: ArrowType, r: ArrowType) =>
val argL = l.args
val resL = l.res
val argR = r.args

View File

@ -94,4 +94,13 @@ class TypeSpec extends AnyFlatSpec with Matchers {
accepts(four, one) should be(false)
}
"streams" should "be accepted as an array, but not vice versa" in {
val stream: Type = StreamType(bool)
val array: Type = ArrayType(bool)
accepts(array, stream) should be(true)
accepts(stream, array) should be(false)
accepts(stream, stream) should be(true)
}
}