Skip to content

Commit

Permalink
277 implicit canonicalization for <<- stream (#339)
Browse files Browse the repository at this point in the history
* Create opaque names in NamesInterpreter

* Implicit canonicalization for <<- stream

* PR fixes

* Second part of #277
  • Loading branch information
alari committed Oct 26, 2021
1 parent dacf4d6 commit 1bd640e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 36 deletions.
30 changes: 27 additions & 3 deletions model/src/main/scala/aqua/model/func/FuncCallable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package aqua.model.func
import aqua.model.ValueModel.varName
import aqua.model.func.raw.*
import aqua.model.{Model, ValueModel, VarModel}
import aqua.types.{ArrowType, ProductType, StreamType, Type}
import aqua.types.{ArrayType, ArrowType, ProductType, StreamType, Type}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
Expand Down Expand Up @@ -155,9 +155,33 @@ case class FuncCallable(
val (ops, rets) = (call.exportTo zip resolvedResult)
.map[(Option[FuncOp], ValueModel)] {
case (exp @ Call.Export(_, StreamType(_)), res) if isStream(res) =>
None -> res
// TODO move this logic to ReturnSem
// Fix for https://github.com/fluencelabs/aqua/issues/277
val definedNames =
FuncOp(callableFuncBody).definesVarNames.value ++ resolvedExports.keySet

val resName = ValueModel.varName(res).getOrElse(exp.name)

val opaqueName = LazyList.from(0, 1).map(n => s"$resName-$n").collectFirst {
case n if !definedNames(n) => n
}
val opaqueType = res.`type` match {
case StreamType(s) => ArrayType(s)
case _ => res.`type`
}
opaqueName.map(opN =>
FuncOps.seq(
FuncOps.can(res, Call.Export(opN, opaqueType)),
FuncOps.ap(
VarModel(
opN,
opaqueType
),
exp
)
)
) -> exp.model
case (exp @ Call.Export(_, StreamType(_)), res) =>
res.`type`
// pass nested function results to a stream
Some(FuncOps.ap(res, exp)) -> exp.model
case (_, res) =>
Expand Down
11 changes: 10 additions & 1 deletion model/src/main/scala/aqua/model/func/raw/FuncOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ import cats.free.Cofree
object FuncOps {

def noop: FuncOp =
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, Nil)))
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "noop", Call(Nil, Nil)))

def ap(what: ValueModel, to: Call.Export): FuncOp =
FuncOp.leaf(
ApTag(what, to)
)

/**
* Canonicalizes [[what]] into [[to]], [[what]] is expected to be a stream.
* As we don't have canonicalization at the moment, op identity is used instead
*/
def can(what: ValueModel, to: Call.Export): FuncOp =
FuncOp.leaf(
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, to :: Nil))
)

def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
FuncOp.leaf(
CallServiceTag(
Expand Down
2 changes: 2 additions & 0 deletions parser/src/main/scala/aqua/parser/lexer/Name.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ case class Name[F[_]: Comonad](name: F[String]) extends Token[F] {

override def mapK[K[_]: Comonad](fk: F ~> K): Name[K] = copy(fk(name))

def rename(newName: String): Name[F] = copy(name.as(newName))

def value: String = name.extract
}

Expand Down
74 changes: 45 additions & 29 deletions semantics/src/main/scala/aqua/semantics/expr/PushToStreamSem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package aqua.semantics.expr
import aqua.model.ValueModel.varName
import aqua.model.func.Call
import aqua.model.func.raw.{ApTag, FuncOp, FuncOps}
import aqua.model.{LiteralModel, Model}
import aqua.model.{LiteralModel, Model, VarModel}
import aqua.parser.expr.PushToStreamExpr
import aqua.parser.lexer.Token
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{StreamType, Type}
import aqua.types.{ArrayType, StreamType, Type}
import cats.free.Free
import cats.syntax.apply.*

Expand All @@ -19,20 +19,24 @@ class PushToStreamSem[F[_]](val expr: PushToStreamExpr[F]) extends AnyVal {
private def ensureStreamElementMatches[Alg[_]](
streamToken: Token[F],
elementToken: Token[F],
streamOp: Option[Type],
elementOp: Option[Type]
stream: Type,
element: Type
)(implicit
T: TypesAlgebra[F, Alg]
): Free[Alg, Boolean] =
(streamOp, elementOp).mapN { case (stream, element) =>
stream match {
case StreamType(st) =>
T.ensureTypeMatches(elementToken, st, element)
case _ =>
T.ensureTypeMatches(streamToken, StreamType(element), stream)
}

}.getOrElse(Free.pure[Alg, Boolean](false))
stream match {
case StreamType(st) =>
T.ensureTypeMatches(elementToken, st, element)
case _ =>
T.ensureTypeMatches(
streamToken,
StreamType(element match {
case StreamType(e) => ArrayType(e)
case _ => element
}),
stream
)
}

def program[Alg[_]](implicit
N: NamesAlgebra[F, Alg],
Expand All @@ -41,22 +45,34 @@ class PushToStreamSem[F[_]](val expr: PushToStreamExpr[F]) extends AnyVal {
): Prog[Alg, Model] =
V.valueToModel(expr.value).flatMap {
case Some(vm) =>
for {
resolvedStreamTypeOp <- N.read(expr.stream)
valueType <- V.resolveType(expr.value)
ensure <- ensureStreamElementMatches(
expr.token,
expr.value,
resolvedStreamTypeOp,
valueType
)
} yield {
if (ensure)
resolvedStreamTypeOp
.map(t => FuncOps.ap(vm, Call.Export(expr.stream.value, t)): Model)
.getOrElse(Model.error("Cannot resolve stream type"))
else
Model.error("Stream and pushed element types are not matches")
N.read(expr.stream).flatMap {
case None => Free.pure[Alg, Model](Model.error("Cannot resolve stream type"))
case Some(t) =>
ensureStreamElementMatches(
expr.token,
expr.value,
t,
vm.lastType
).flatMap {
case false =>
Free.pure[Alg, Model](Model.error("Stream type and element type does not match"))
case true =>
vm.lastType match {
case StreamType(lt) =>
// https://github.com/fluencelabs/aqua/issues/277
// TODO: get Name from Value for the opaque name, as it points on Value, not on the stream
N.defineOpaque(expr.stream, ArrayType(lt)).map { n =>
val opaqueVar = VarModel(n.value, ArrayType(lt))
FuncOps.seq(
FuncOps.can(vm, Call.Export(opaqueVar.name, opaqueVar.lastType)),
FuncOps.ap(opaqueVar, Call.Export(expr.stream.value, t))
): Model
}

case _ =>
Free.pure[Alg, Model](FuncOps.ap(vm, Call.Export(expr.stream.value, t)): Model)
}
}
}

case _ => Free.pure[Alg, Model](Model.error("Cannot resolve value"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ case class ConstantDefined[F[_]](name: Name[F]) extends NameOp[F, Option[Type]]
case class ReadArrow[F[_]](name: Name[F]) extends NameOp[F, Option[ArrowType]]

case class DefineName[F[_]](name: Name[F], `type`: Type) extends NameOp[F, Boolean]
case class DefineOpaqueName[F[_]](name: Name[F], `type`: Type) extends NameOp[F, Name[F]]
case class DefineConstant[F[_]](name: Name[F], `type`: Type) extends NameOp[F, Boolean]

case class DefineArrow[F[_]](name: Name[F], gen: ArrowType, isRoot: Boolean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class NamesAlgebra[F[_], Alg[_]](implicit V: InjectK[NameOp[F, *], Alg]) {
def define(name: Name[F], `type`: Type): Free[Alg, Boolean] =
Free.liftInject[Alg](DefineName(name, `type`))

def defineOpaque(name: Name[F], `type`: Type): Free[Alg, Name[F]] =
Free.liftInject[Alg](DefineOpaqueName(name, `type`))

def defineConstant(name: Name[F], `type`: Type): Free[Alg, Boolean] =
Free.liftInject[Alg](DefineConstant(name, `type`))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re

def readName(name: String): S[Option[Type]] =
getState.map { st =>
st.constants.get(name) orElse st.stack.collectFirst {
st.constants.get(name) orElse st.opaque.get(name) orElse st.stack.collectFirst {
case frame if frame.names.contains(name) => frame.names(name)
case frame if frame.arrows.contains(name) => frame.arrows(name)
} orElse st.rootArrows.get(name)
Expand All @@ -43,7 +43,11 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re
report(
rn.name,
Levenshtein
.genMessage(s"Name '${rn.name.value}' isn't found in scope", rn.name.value, st.allNames.toList)
.genMessage(
s"Name '${rn.name.value}' isn't found in scope",
rn.name.value,
st.allNames.toList
)
)
)
case _ => State.pure(())
Expand All @@ -58,7 +62,11 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re
getState.flatMap(st =>
report(
ra.name,
Levenshtein.genMessage(s"Name '${ra.name.value}' not found in scope", ra.name.value, st.allNames.toList)
Levenshtein.genMessage(
s"Name '${ra.name.value}' not found in scope",
ra.name.value,
st.allNames.toList
)
)
.as(Option.empty[ArrowType])
)
Expand Down Expand Up @@ -88,6 +96,21 @@ class NamesInterpreter[F[_], X](implicit lens: Lens[X, NamesState[F]], error: Re
.as(false)
)(fr => fr.addName(dn.name.value, dn.`type`) -> true)
}
case dn: DefineOpaqueName[F] =>
getState.flatMap { st =>
def findUniqueName(i: Int): String = {
val n = dn.name.value + "-" + i
st.opaque.get(n).fold(n)(_ => findUniqueName(i + 1))
}

val n = findUniqueName(0)
modify(
_.copy(
opaque = st.opaque.updated(n, dn.`type`)
)
).as(dn.name.rename(n))
}

case da: DefineArrow[F] =>
readName(da.name.value).flatMap {
case Some(_) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ case class NamesState[F[_]](
stack: List[NamesState.Frame[F]] = Nil,
rootArrows: Map[String, ArrowType] = Map.empty,
constants: Map[String, Type] = Map.empty[String, Type],
opaque: Map[String, Type] = Map.empty[String, Type],
definitions: Map[String, Name[F]] = Map.empty[String, Name[F]]
) {

Expand Down

0 comments on commit 1bd640e

Please sign in to comment.