feat(compiler): Services as abilities [fixes LNG-206] (#873)

* Refactor ServiceSem

* Refactor AbilityIdSem

* Remove tokens from state

* Refactor

* Add mangler

* Fix tests

* Refactor valueToRaw

* ServiceIdTag

* AbilityId -> ServiceId

* Add ServiceType

* Fix defineServiceType

* Refactor resolveArrowDef

* Refactor TypesHelper

* Add ServiceIdTag inlining

* Implement resolution

* Add service as ability passing

* Fix importing services

* Fix cli

* Implement default service

* Remove println

* Fix capture

* Add integration test

* Fix id

* Fix test

* Fix test

* Refactor test

* Do not resolve id

* Refactor FuncArrow creation

* Refactor FuncArrow wrapper creation

* Add named arguments

* Add comment

* ensureIsString -> valueToStringRaw, refactor OnSem

* Resolve services as abilities

* Add name to varNames

* Remove service hack

* Capture services, do not rename captured

* Rename arrows along with values

* Fix CallArrowRaw.map

* Fix unit tests

* Remove service case

* Refactor abilities state

* Propagate rootServiceIds

* Remove unused

* Add comments

* Refactor

* Refactor

* Add test

---------

Co-authored-by: Dima <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
InversionSpaces 2023-09-15 10:34:21 +02:00 committed by GitHub
parent f8b5017918
commit 6be2a3d5da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2073 additions and 1458 deletions

View File

@ -10,6 +10,7 @@ import aqua.raw.{RawContext, RawPart}
import aqua.res.AquaRes import aqua.res.AquaRes
import aqua.semantics.{CompilerState, Semantics} import aqua.semantics.{CompilerState, Semantics}
import aqua.semantics.header.{HeaderHandler, HeaderSem, Picker} import aqua.semantics.header.{HeaderHandler, HeaderSem, Picker}
import cats.data.* import cats.data.*
import cats.data.Validated.{validNec, Invalid, Valid} import cats.data.Validated.{validNec, Invalid, Valid}
import cats.parse.Parser0 import cats.parse.Parser0

View File

@ -153,6 +153,7 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
val canonResult = VarModel("-" + results.name + "-fix-0", CanonStreamType(resultsType.element)) val canonResult = VarModel("-" + results.name + "-fix-0", CanonStreamType(resultsType.element))
val flatResult = VarModel("-results-flat-0", ArrayType(ScalarType.string)) val flatResult = VarModel("-results-flat-0", ArrayType(ScalarType.string))
val initPeer = LiteralModel.fromRaw(ValueRaw.InitPeerId) val initPeer = LiteralModel.fromRaw(ValueRaw.InitPeerId)
val retVar = VarModel("ret", ScalarType.string)
val expected = val expected =
SeqRes.wrap( SeqRes.wrap(
@ -173,10 +174,11 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
"identity", "identity",
CallRes( CallRes(
LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil, LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil,
Some(CallModel.Export(results.name, results.`type`)) Some(CallModel.Export(retVar.name, retVar.`type`))
), ),
peer peer
).leaf, ).leaf,
ApRes(retVar, CallModel.Export(results.name, results.`type`)).leaf,
through(ValueModel.fromRaw(relay)), through(ValueModel.fromRaw(relay)),
through(initPeer) through(initPeer)
), ),

View File

@ -0,0 +1,95 @@
aqua Test
export test, testCapture, TestService
service TestService("default-id"):
getId() -> string
concatId(s: string) -> string
ability MatchingAbility:
getId() -> string
concatId(s: string) -> string
func acceptClosure(closure: string -> string, arg: string) -> string:
<- closure(arg)
func acceptAbility{MatchingAbility}(arg: string) -> string:
<- MatchingAbility.concatId(arg)
func test() -> []string:
result: *string
-- Test service
result <- TestService.concatId("call")
capture = TestService.concatId
result <- capture("capture call")
result <- acceptClosure(TestService.concatId, "accept closure call")
result <- acceptAbility{TestService}("accept ability call")
-- Test renamed service
Renamed = TestService
result <- Renamed.concatId("call")
captureRenamed = Renamed.concatId
result <- captureRenamed("capture call")
result <- acceptClosure(Renamed.concatId, "accept closure call")
result <- acceptAbility{Renamed}("accept ability call")
-- Test resolved service
TestService "resolved-id-1"
result <- TestService.concatId("call")
captureResolved = TestService.concatId
result <- captureResolved("capture call")
result <- acceptClosure(TestService.concatId, "accept closure call")
result <- acceptAbility{TestService}("accept ability call")
-- Test renamed resolved service
Renamed1 = TestService
result <- Renamed1.concatId("call")
captureRenamed1 = Renamed1.concatId
result <- captureRenamed1("capture call")
result <- acceptClosure(Renamed1.concatId, "accept closure call")
result <- acceptAbility{Renamed1}("accept ability call")
-- Test renamed service again (should save id)
result <- Renamed.concatId("call")
captureRenamedAgain = Renamed.concatId
result <- captureRenamedAgain("capture call")
result <- acceptClosure(Renamed.concatId, "accept closure call")
result <- acceptAbility{Renamed}("accept ability call")
-- Test resolved in scope service
for i <- ["iter-id-1", "iter-id-2"]:
TestService i
RenamedI = TestService
result <- RenamedI.concatId("call")
captureI = RenamedI.concatId
result <- captureI("capture call")
result <- acceptClosure(RenamedI.concatId, "accept closure call")
result <- acceptAbility{RenamedI}("accept ability call")
-- Test resolved service again (should save id)
result <- TestService.concatId("call")
captureAgain = TestService.concatId
result <- captureAgain("capture call")
result <- acceptClosure(TestService.concatId, "accept closure call")
result <- acceptAbility{TestService}("accept ability call")
-- Test re resolved service in same scope
TestService "resolved-id-2"
result <- TestService.concatId("call")
captureReResolved = TestService.concatId
result <- captureReResolved("capture call")
result <- acceptClosure(TestService.concatId, "accept closure call")
result <- acceptAbility{TestService}("accept ability call")
<- result
func callCapture{MatchingAbility}() -> string, string:
TestService "resolved-id-in-capture"
res1 <- TestService.concatId("in capture")
res2 <- MatchingAbility.concatId("in capture")
<- res1, res2
func testCapture() -> string, string:
res1, res2 <- callCapture{TestService}()
<- res1, res2

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
import {
test,
testCapture,
registerTestService,
} from "../compiled/examples/servicesAsAbilities.js";
const serviceIds = {
default: "default-id",
resolved1: "resolved-id-1",
resolved2: "resolved-id-2",
iter1: "iter-id-1",
iter2: "iter-id-2",
};
const serviceIdsSequence = [
serviceIds.default,
serviceIds.default,
serviceIds.resolved1,
serviceIds.resolved1,
serviceIds.default,
serviceIds.iter1,
serviceIds.iter2,
serviceIds.resolved1,
serviceIds.resolved2,
];
const msgs = [
"call",
"capture call",
"accept closure call",
"accept ability call",
];
export const expectedServiceResults = serviceIdsSequence.flatMap((id) =>
msgs.map((msg) => `${id}: ${msg}`),
);
export async function servicesAsAbilitiesCall() {
Object.entries(serviceIds).forEach(([_key, id], _idx) =>
registerTestService(id, {
concatId: (s: string) => {
return `${id}: ${s}`;
},
getId: () => {
return id;
},
}),
);
return await test();
}
export const expectedServiceCaptureResults = [
"resolved-id-in-capture: in capture",
"default-id: in capture",
];
export async function servicesAsAbilitiesCaptureCall() {
["resolved-id-in-capture", "default-id"].forEach((id) =>
registerTestService(id, {
concatId: (s: string) => {
return `${id}: ${s}`;
},
getId: () => {
return id;
},
}),
);
return await testCapture();
}

View File

@ -5,10 +5,11 @@ import aqua.model.*
import aqua.model.inline.state.{Arrows, Exports, Mangler} import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.ops.RawTag import aqua.raw.ops.RawTag
import aqua.raw.value.{ValueRaw, VarRaw} import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.types.{AbilityType, ArrowType, BoxType, StreamType, Type} import aqua.types.{AbilityType, ArrowType, BoxType, NamedType, StreamType, Type}
import cats.data.StateT import cats.data.StateT
import cats.data.{Chain, IndexedStateT, State} import cats.data.{Chain, IndexedStateT, State}
import cats.syntax.functor.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.bifunctor.* import cats.syntax.bifunctor.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
@ -123,22 +124,22 @@ object ArrowInliner extends Logging {
/** /**
* Get ability fields (vars or arrows) from exports * Get ability fields (vars or arrows) from exports
* *
* @param abilityName ability current name in state * @param name ability current name in state
* @param abilityNewName ability new name (for renaming) * @param newName ability new name (for renaming)
* @param abilityType ability type * @param type ability type
* @param exports exports state to resolve fields * @param exports exports state to resolve fields
* @param fields fields selector * @param fields fields selector
* @return resolved ability fields (renamed if necessary) * @return resolved ability fields (renamed if necessary)
*/ */
private def getAbilityFields[T <: Type]( private def getAbilityFields[T <: Type](
abilityName: String, name: String,
abilityNewName: Option[String], newName: Option[String],
abilityType: AbilityType, `type`: NamedType,
exports: Map[String, ValueModel] exports: Map[String, ValueModel]
)(fields: AbilityType => Map[String, T]): Map[String, ValueModel] = )(fields: NamedType => Map[String, T]): Map[String, ValueModel] =
fields(abilityType).flatMap { case (fName, _) => fields(`type`).flatMap { case (fName, _) =>
val fullName = AbilityType.fullName(abilityName, fName) val fullName = AbilityType.fullName(name, fName)
val newFullName = AbilityType.fullName(abilityNewName.getOrElse(abilityName), fName) val newFullName = AbilityType.fullName(newName.getOrElse(name), fName)
Exports Exports
.getLastValue(fullName, exports) .getLastValue(fullName, exports)
@ -179,24 +180,24 @@ object ArrowInliner extends Logging {
/** /**
* Get ability arrows from arrows * Get ability arrows from arrows
* *
* @param abilityName ability current name in state * @param name ability current name in state
* @param abilityNewName ability new name (for renaming) * @param newName ability new name (for renaming)
* @param abilityType ability type * @param type ability type
* @param exports exports state to resolve fields * @param exports exports state to resolve fields
* @param arrows arrows state to resolve arrows * @param arrows arrows state to resolve arrows
* @return resolved ability arrows (renamed if necessary) * @return resolved ability arrows (renamed if necessary)
*/ */
private def getAbilityArrows( private def getAbilityArrows(
abilityName: String, name: String,
abilityNewName: Option[String], newName: Option[String],
abilityType: AbilityType, `type`: NamedType,
exports: Map[String, ValueModel], exports: Map[String, ValueModel],
arrows: Map[String, FuncArrow] arrows: Map[String, FuncArrow]
): Map[String, FuncArrow] = { ): Map[String, FuncArrow] = {
val get = getAbilityFields( val get = getAbilityFields(
abilityName, name,
abilityNewName, newName,
abilityType, `type`,
exports exports
) )
@ -210,12 +211,12 @@ object ArrowInliner extends Logging {
} }
private def getAbilityArrows[S: Arrows: Exports]( private def getAbilityArrows[S: Arrows: Exports](
abilityName: String, name: String,
abilityType: AbilityType `type`: NamedType
): State[S, Map[String, FuncArrow]] = for { ): State[S, Map[String, FuncArrow]] = for {
exports <- Exports[S].exports exports <- Exports[S].exports
arrows <- Arrows[S].arrows arrows <- Arrows[S].arrows
} yield getAbilityArrows(abilityName, None, abilityType, exports, arrows) } yield getAbilityArrows(name, None, `type`, exports, arrows)
final case class Renamed[T]( final case class Renamed[T](
renames: Map[String, String], renames: Map[String, String],
@ -276,7 +277,22 @@ object ArrowInliner extends Logging {
* to avoid collisions, then resolve them in context. * to avoid collisions, then resolve them in context.
*/ */
capturedValues <- findNewNames(fn.capturedValues) capturedValues <- findNewNames(fn.capturedValues)
capturedArrows <- findNewNames(fn.capturedArrows) /**
* If arrow correspond to a value,
* rename in accordingly to the value
*/
capturedArrowValues = fn.capturedArrows.flatMap { case (arrowName, arrow) =>
capturedValues.renames
.get(arrowName)
.orElse(fn.capturedValues.get(arrowName).as(arrowName))
.map(_ -> arrow)
}
/**
* Rename arrows that are not values
*/
capturedArrows <- findNewNames(fn.capturedArrows.filterNot { case (arrowName, _) =>
capturedArrowValues.contains(arrowName)
})
/** /**
* Function defines variables inside its body. * Function defines variables inside its body.
@ -301,7 +317,12 @@ object ArrowInliner extends Logging {
defineRenames defineRenames
) )
arrowsResolved = arrows ++ capturedArrows.renamed /**
* TODO: Optimize resolve.
* It seems that resolving whole `exports`
* and `arrows` is not necessary.
*/
arrowsResolved = arrows ++ capturedArrowValues ++ capturedArrows.renamed
exportsResolved = exports ++ data.renamed ++ capturedValues.renamed exportsResolved = exports ++ data.renamed ++ capturedValues.renamed
tree = fn.body.rename(renaming) tree = fn.body.rename(renaming)
@ -322,12 +343,13 @@ object ArrowInliner extends Logging {
exports <- Exports[S].exports exports <- Exports[S].exports
streams <- getOutsideStreamNames streams <- getOutsideStreamNames
arrows = passArrows ++ arrowsFromAbilities
inlineResult <- Exports[S].scope( inlineResult <- Exports[S].scope(
Arrows[S].scope( Arrows[S].scope(
for { for {
// Process renamings, prepare environment // Process renamings, prepare environment
fn <- ArrowInliner.prelude(arrow, call, exports, passArrows ++ arrowsFromAbilities) fn <- ArrowInliner.prelude(arrow, call, exports, arrows)
inlineResult <- ArrowInliner.inline(fn, call, streams) inlineResult <- ArrowInliner.inline(fn, call, streams)
} yield inlineResult } yield inlineResult
) )

View File

@ -413,6 +413,49 @@ object TagInliner extends Logging {
} yield TagInlined.Empty(prefix = prefix) } yield TagInlined.Empty(prefix = prefix)
case _ => none case _ => none
case ServiceIdTag(id, serviceType, name) =>
for {
idm <- valueToModel(id)
(idModel, idPrefix) = idm
// Make `FuncArrow` wrappers for service methods
methods <- serviceType.fields.toSortedMap.toList.traverse {
case (methodName, methodType) =>
for {
arrowName <- Mangler[S].findAndForbidName(s"$name-$methodName")
fn = FuncArrow.fromServiceMethod(
arrowName,
serviceType.name,
methodName,
methodType,
idModel
)
} yield methodName -> fn
}
// Resolve wrappers in arrows
_ <- Arrows[S].resolved(
methods.map { case (_, fn) =>
fn.funcName -> fn
}.toMap
)
// Resolve wrappers in exports
_ <- methods.traverse { case (methodName, fn) =>
Exports[S].resolveAbilityField(
name,
methodName,
VarModel(fn.funcName, fn.arrowType)
)
}
// Resolve service in exports
_ <- Exports[S].resolved(
name,
VarModel(name, serviceType)
)
} yield TagInlined.Empty(prefix = idPrefix)
case _: SeqGroupTag => pure(SeqModel) case _: SeqGroupTag => pure(SeqModel)
case ParTag.Detach => pure(DetachModel) case ParTag.Detach => pure(DetachModel)
case _: ParGroupTag => pure(ParModel) case _: ParGroupTag => pure(ParModel)

View File

@ -59,10 +59,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
private def unfoldAbilityProperty[S: Mangler: Exports: Arrows]( private def unfoldAbilityProperty[S: Mangler: Exports: Arrows](
varModel: VarModel, varModel: VarModel,
abilityType: AbilityType, abilityType: NamedType,
p: PropertyRaw p: PropertyRaw
): State[S, (VarModel, Inline)] = { ): State[S, (VarModel, Inline)] = p match {
p match {
case IntoArrowRaw(arrowName, t, arguments) => case IntoArrowRaw(arrowName, t, arguments) =>
val arrowType = abilityType.fields val arrowType = abilityType.fields
.lookup(arrowName) .lookup(arrowName)
@ -70,17 +69,15 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
at at
} }
.getOrElse { .getOrElse {
logger.error(s"Inlining, cannot find arrow $arrowName in ability $varModel") logger.error(s"Inlining, cannot find arrow $arrowName in $varModel")
ArrowType(NilType, NilType) ArrowType(NilType, NilType)
} }
for { for {
callArrow <- CallArrowRawInliner( callArrow <- CallArrowRawInliner(
CallArrowRaw( CallArrowRaw.func(
None, funcName = AbilityType.fullName(varModel.name, arrowName),
AbilityType.fullName(varModel.name, arrowName), baseType = arrowType,
arguments, arguments = arguments
arrowType,
None
) )
) )
result <- callArrow match { result <- callArrow match {
@ -91,9 +88,7 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
Exports[S].resolved(vm.name, vm).map(_ => (vm, inline)) Exports[S].resolved(vm.name, vm).map(_ => (vm, inline))
} }
} }
} yield { } yield result
result
}
case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) => case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) =>
(VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure (VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure
case IntoFieldRaw(fieldName, t) => case IntoFieldRaw(fieldName, t) =>
@ -114,10 +109,7 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
} }
} }
} yield { } yield result
result
}
}
} }
private[inline] def unfoldProperty[S: Mangler: Exports: Arrows]( private[inline] def unfoldProperty[S: Mangler: Exports: Arrows](
@ -284,9 +276,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
case (_, _) => case (_, _) =>
unfold(raw).flatMap { unfold(raw).flatMap {
case (vm: VarModel, prevInline) => case (vm: VarModel, prevInline) =>
unfoldProperties(prevInline, vm, properties, propertiesAllowed).map { case (v, i) => unfoldProperties(prevInline, vm, properties, propertiesAllowed)
v -> i // To coerce types
} .map(identity)
case (l: LiteralModel, inline) => case (l: LiteralModel, inline) =>
flatLiteralWithProperties( flatLiteralWithProperties(
l, l,

View File

@ -32,12 +32,8 @@ trait Arrows[S] extends Scoped[S] {
for { for {
exps <- Exports[S].exports exps <- Exports[S].exports
arrs <- arrows arrs <- arrows
// _ = println(s"Resolved arrow: ${arrow.name}")
// _ = println(s"Captured var names: ${arrow.capturedVars}")
captuedVars = exps.filterKeys(arrow.capturedVars).toMap captuedVars = exps.filterKeys(arrow.capturedVars).toMap
capturedArrows = arrs.filterKeys(arrow.capturedVars).toMap capturedArrows = arrs.filterKeys(arrow.capturedVars).toMap
// _ = println(s"Captured vars: ${captuedVars}")
// _ = println(s"Captured arrows: ${capturedArrows}")
funcArrow = FuncArrow.fromRaw(arrow, capturedArrows, captuedVars, topology) funcArrow = FuncArrow.fromRaw(arrow, capturedArrows, captuedVars, topology)
_ <- save(arrow.name, funcArrow) _ <- save(arrow.name, funcArrow)
} yield () } yield ()

View File

@ -2,7 +2,8 @@ package aqua.model.inline.state
import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.model.ValueModel.Ability import aqua.model.ValueModel.Ability
import aqua.types.AbilityType import aqua.types.{AbilityType, NamedType}
import cats.data.{NonEmptyList, State} import cats.data.{NonEmptyList, State}
/** /**
@ -122,7 +123,7 @@ trait Exports[S] extends Scoped[S] {
} }
object Exports { object Exports {
def apply[S](implicit exports: Exports[S]): Exports[S] = exports def apply[S](using exports: Exports[S]): Exports[S] = exports
// Get last linked VarModel // Get last linked VarModel
def getLastValue(name: String, state: Map[String, ValueModel]): Option[ValueModel] = { def getLastValue(name: String, state: Map[String, ValueModel]): Option[ValueModel] = {
@ -143,7 +144,7 @@ object Exports {
private def getAbilityPairs( private def getAbilityPairs(
oldName: String, oldName: String,
newName: String, newName: String,
at: AbilityType, at: NamedType,
state: Map[String, ValueModel] state: Map[String, ValueModel]
): NonEmptyList[(String, ValueModel)] = { ): NonEmptyList[(String, ValueModel)] = {
at.fields.toNel.flatMap { at.fields.toNel.flatMap {

View File

@ -6,16 +6,16 @@ import aqua.types.{ArrowType, ProductType, ScalarType}
object RawBuilder { object RawBuilder {
def add(l: ValueRaw, r: ValueRaw): ValueRaw = def add(l: ValueRaw, r: ValueRaw): ValueRaw =
CallArrowRaw( CallArrowRaw.service(
ability = Some("math"), abilityName = "math",
name = "add", serviceId = LiteralRaw.quote("math"),
arguments = List(l, r), funcName = "add",
baseType = ArrowType( baseType = ArrowType(
ProductType(List(ScalarType.i64, ScalarType.i64)), ProductType(List(ScalarType.i64, ScalarType.i64)),
ProductType( ProductType(
List(l.`type` `` r.`type`) List(l.`type` `` r.`type`)
) )
), ),
serviceId = Some(LiteralRaw.quote("math")) arguments = List(l, r)
) )
} }

View File

@ -2,7 +2,7 @@ package aqua.raw
import aqua.raw.arrow.FuncRaw import aqua.raw.arrow.FuncRaw
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
import aqua.types.{StructType, Type, AbilityType} import aqua.types.{AbilityType, StructType, Type}
import cats.Monoid import cats.Monoid
import cats.Semigroup import cats.Semigroup
@ -61,8 +61,9 @@ case class RawContext(
all(_.services) all(_.services)
lazy val types: Map[String, Type] = lazy val types: Map[String, Type] =
collectPartsMap { case t: TypeRaw => collectPartsMap {
t.`type` case t: TypeRaw => t.`type`
case s: ServiceRaw => s.`type`
} }
lazy val allTypes: Map[String, Type] = lazy val allTypes: Map[String, Type] =

View File

@ -1,15 +1,14 @@
package aqua.raw package aqua.raw
import aqua.types.{ArrowType, StructType} import aqua.types.ServiceType
import cats.data.NonEmptyMap
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
case class ServiceRaw( case class ServiceRaw(
name: String, name: String,
arrows: NonEmptyMap[String, ArrowType], `type`: ServiceType,
defaultId: Option[ValueRaw] defaultId: Option[ValueRaw]
) extends RawPart { ) extends RawPart {
def rawPartType: StructType = StructType(name, arrows) def rawPartType: ServiceType = `type`
override def rename(s: String): RawPart = copy(name = s) override def rename(s: String): RawPart = copy(name = s)

View File

@ -4,7 +4,7 @@ import aqua.raw.arrow.FuncRaw
import aqua.raw.ops.RawTag.Tree import aqua.raw.ops.RawTag.Tree
import aqua.raw.value.{CallArrowRaw, ValueRaw} import aqua.raw.value.{CallArrowRaw, ValueRaw}
import aqua.tree.{TreeNode, TreeNodeCompanion} import aqua.tree.{TreeNode, TreeNodeCompanion}
import aqua.types.{ArrowType, DataType} import aqua.types.{ArrowType, DataType, ServiceType}
import cats.Show import cats.Show
import cats.data.{Chain, NonEmptyList} import cats.data.{Chain, NonEmptyList}
@ -20,7 +20,7 @@ sealed trait RawTag extends TreeNode[RawTag] {
def restrictsVarNames: Set[String] = Set.empty def restrictsVarNames: Set[String] = Set.empty
// All variable names introduced by this tag // All variable names introduced by this tag
def definesVarNames: Set[String] = exportsVarNames ++ restrictsVarNames final def definesVarNames: Set[String] = exportsVarNames ++ restrictsVarNames
// Variable names used by this tag (not introduced by it) // Variable names used by this tag (not introduced by it)
def usesVarNames: Set[String] = Set.empty def usesVarNames: Set[String] = Set.empty
@ -208,6 +208,22 @@ case class CallArrowRawTag(
object CallArrowRawTag { object CallArrowRawTag {
def ability(
abilityName: String,
funcName: String,
call: Call,
arrowType: ArrowType
): CallArrowRawTag =
CallArrowRawTag(
call.exportTo,
CallArrowRaw.ability(
abilityName,
funcName,
arrowType,
call.args
)
)
def service( def service(
serviceId: ValueRaw, serviceId: ValueRaw,
fnName: String, fnName: String,
@ -231,12 +247,10 @@ object CallArrowRawTag {
def func(fnName: String, call: Call): CallArrowRawTag = def func(fnName: String, call: Call): CallArrowRawTag =
CallArrowRawTag( CallArrowRawTag(
call.exportTo, call.exportTo,
CallArrowRaw( CallArrowRaw.func(
None, funcName = fnName,
fnName, baseType = call.arrowType,
call.args, arguments = call.args
call.arrowType,
None
) )
) )
} }
@ -307,15 +321,27 @@ object EmptyTag extends NoExecTag {
override def mapValues(f: ValueRaw => ValueRaw): RawTag = this override def mapValues(f: ValueRaw => ValueRaw): RawTag = this
} }
case class AbilityIdTag( /**
* Tag for `Service "id"` expression.
* For each such expression new ability
* is created with unique name (@p name).
*
* @param value value of service ID
* @param serviceType type of service
* @param name **rename** of service
*/
case class ServiceIdTag(
value: ValueRaw, value: ValueRaw,
service: String serviceType: ServiceType,
name: String
) extends NoExecTag { ) extends NoExecTag {
override def usesVarNames: Set[String] = value.varNames override def usesVarNames: Set[String] = value.varNames
override def exportsVarNames: Set[String] = Set(name)
override def mapValues(f: ValueRaw => ValueRaw): RawTag = override def mapValues(f: ValueRaw => ValueRaw): RawTag =
AbilityIdTag(value.map(f), service) ServiceIdTag(value.map(f), serviceType, name)
} }
case class PushToStreamTag(operand: ValueRaw, exportTo: Call.Export) extends RawTag { case class PushToStreamTag(operand: ValueRaw, exportTo: Call.Export) extends RawTag {

View File

@ -250,12 +250,14 @@ case class CallArrowRaw(
override def map(f: ValueRaw => ValueRaw): ValueRaw = override def map(f: ValueRaw => ValueRaw): ValueRaw =
f( f(
copy( copy(
arguments = arguments.map(f), arguments = arguments.map(_.map(f)),
serviceId = serviceId.map(f) serviceId = serviceId.map(_.map(f))
) )
) )
override def varNames: Set[String] = arguments.flatMap(_.varNames).toSet override def varNames: Set[String] = name.some
.filterNot(_ => ability.isDefined || serviceId.isDefined)
.toSet ++ arguments.flatMap(_.varNames).toSet
override def renameVars(map: Map[String, String]): ValueRaw = override def renameVars(map: Map[String, String]): ValueRaw =
copy( copy(

View File

@ -11,7 +11,7 @@ object ServiceRes {
def fromModel(sm: ServiceModel): ServiceRes = def fromModel(sm: ServiceModel): ServiceRes =
ServiceRes( ServiceRes(
name = sm.name, name = sm.name,
members = sm.arrows.toNel.toList, members = sm.`type`.arrows.toList,
defaultId = sm.defaultId.collect { defaultId = sm.defaultId.collect {
case LiteralModel(value, t) if ScalarType.string.acceptsValueOf(t) => case LiteralModel(value, t) if ScalarType.string.acceptsValueOf(t) =>
value value

View File

@ -5,15 +5,19 @@ import aqua.raw.ops.CallArrowRawTag
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
import aqua.raw.value.CallArrowRaw import aqua.raw.value.CallArrowRaw
import aqua.raw.{ConstantRaw, RawContext, RawPart, ServiceRaw, TypeRaw} import aqua.raw.{ConstantRaw, RawContext, RawPart, ServiceRaw, TypeRaw}
import aqua.types.{StructType, Type} import aqua.types.{AbilityType, StructType, Type}
import cats.Monoid import cats.Monoid
import cats.data.NonEmptyMap import cats.data.NonEmptyMap
import cats.data.Chain import cats.data.Chain
import cats.kernel.Semigroup import cats.kernel.Semigroup
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.foldable.*
import cats.syntax.traverse.*
import cats.syntax.bifunctor.*
import cats.syntax.monoid.* import cats.syntax.monoid.*
import cats.syntax.option.*
import scribe.Logging import scribe.Logging
import scala.collection.immutable.SortedMap import scala.collection.immutable.SortedMap
case class AquaContext( case class AquaContext(
@ -26,31 +30,76 @@ case class AquaContext(
services: Map[String, ServiceModel] services: Map[String, ServiceModel]
) { ) {
private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) =
(prefix + pair._1, pair._2)
// TODO: it's a duplicate // TODO: it's a duplicate
private def all[T](what: AquaContext => Map[String, T], prefix: String = ""): Map[String, T] = private def all[T](
abilities what: AquaContext => Map[String, T],
.foldLeft(what(this)) { case (ts, (k, v)) => prefix: String = ""
ts ++ v.all(what, k + ".") ): Map[String, T] = (
what(this) ++ abilities.toList.foldMap { case (k, v) =>
v.all(what, k + ".").toList
} }
.map(prefixFirst(prefix, _)) ).map(_.leftMap(prefix + _)).toMap
lazy val allValues: Map[String, ValueModel] = lazy val allValues: Map[String, ValueModel] =
all(_.values) all(_.values) ++
/**
* Add values from services that have default ID
* So that they will be available in functions.
*/
services.flatMap { case (srvName, srv) =>
srv.defaultId.toList.flatMap(_ =>
srv.`type`.arrows.map { case (arrowName, arrowType) =>
val fullName = AbilityType.fullName(srvName, arrowName)
fullName -> VarModel(
fullName,
arrowType
)
}.updated(
srvName,
VarModel(
srvName,
srv.`type`
)
)
)
}
lazy val allFuncs: Map[String, FuncArrow] = lazy val allFuncs: Map[String, FuncArrow] =
all(_.funcs) all(_.funcs) ++
/**
* Add functions from services that have default ID
* So that they will be available in functions.
*/
services.flatMap { case (srvName, srv) =>
srv.defaultId.toList.flatMap(id =>
srv.`type`.arrows.map { case (arrowName, arrowType) =>
val fullName = AbilityType.fullName(srvName, arrowName)
fullName -> FuncArrow.fromServiceMethod(
fullName,
srvName,
arrowName,
arrowType,
id
)
}
)
}
private def pickOne[T]( private def pickOne[T](
name: String, name: String,
newName: String, newName: String,
ctx: Map[String, T], ctx: Map[String, T],
add: (AquaContext, Map[String, T]) => AquaContext add: (AquaContext, Map[String, T]) => AquaContext
): AquaContext = { ): AquaContext = ctx
ctx.get(name).fold(AquaContext.blank)(t => add(AquaContext.blank, Map(newName -> t))) .get(name)
} .fold(AquaContext.blank)(t =>
add(
AquaContext.blank,
Map(newName -> t)
)
)
def pick(name: String, maybeRename: Option[String]): AquaContext = { def pick(name: String, maybeRename: Option[String]): AquaContext = {
val newName = maybeRename.getOrElse(name) val newName = maybeRename.getOrElse(name)
@ -68,7 +117,7 @@ object AquaContext extends Logging {
lazy val size: Long = data.size lazy val size: Long = data.size
def get(ctx: RawContext): Option[AquaContext] = def get(ctx: RawContext): Option[AquaContext] =
data.find(_._1 eq ctx).map(_._2) data.collectFirst { case (rawCtx, aquaCtx) if rawCtx eq ctx => aquaCtx }
def updated(ctx: RawContext, aCtx: AquaContext): Cache = copy(data :+ (ctx -> aCtx)) def updated(ctx: RawContext, aCtx: AquaContext): Cache = copy(data :+ (ctx -> aCtx))
} }
@ -91,11 +140,10 @@ object AquaContext extends Logging {
x.services ++ y.services x.services ++ y.services
) )
//
def fromService(sm: ServiceRaw, serviceId: ValueRaw): AquaContext = def fromService(sm: ServiceRaw, serviceId: ValueRaw): AquaContext =
blank.copy( blank.copy(
module = Some(sm.name), module = Some(sm.name),
funcs = sm.arrows.toSortedMap.map { case (fnName, arrowType) => funcs = sm.`type`.arrows.map { case (fnName, arrowType) =>
val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType) val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType)
fnName -> fnName ->
FuncArrow( FuncArrow(
@ -192,13 +240,16 @@ object AquaContext extends Logging {
logger.trace("Adding service " + m.name) logger.trace("Adding service " + m.name)
val (pctx, pcache) = fromRawContext(partContext, ctxCache) val (pctx, pcache) = fromRawContext(partContext, ctxCache)
logger.trace("Got " + m.name + " from raw") logger.trace("Got " + m.name + " from raw")
val id = m.defaultId.map(ValueModel.fromRaw).map(_.resolveWith(pctx.allValues)) val id = m.defaultId
val srv = ServiceModel(m.name, m.arrows, id) .map(ValueModel.fromRaw)
.map(_.resolveWith(pctx.allValues))
val srv = ServiceModel(m.name, m.`type`, id)
val add = val add =
blank blank
.copy( .copy(
abilities = abilities = m.defaultId
m.defaultId.fold(Map.empty)(id => Map(m.name -> fromService(m, id))), .map(id => Map(m.name -> fromService(m, id)))
.orEmpty,
services = Map(m.name -> srv) services = Map(m.name -> srv)
) )

View File

@ -1,6 +1,7 @@
package aqua.model package aqua.model
import aqua.model.{ValueModel, VarModel} import aqua.model.{ValueModel, VarModel}
import aqua.model.ValueModel.Ability
import aqua.raw.ops.Call import aqua.raw.ops.Call
import aqua.raw.value.{ValueRaw, VarRaw} import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.types.* import aqua.types.*
@ -39,11 +40,11 @@ case class ArgsCall(args: ProductType, callWith: List[ValueModel]) {
}.toMap }.toMap
/** /**
* Ability arguments as mapping * Ability and service arguments as mapping
* Name of argument -> (variable passed in the call, ability type) * Name of argument -> (variable passed in the call, type)
*/ */
lazy val abilityArgs: Map[String, (VarModel, AbilityType)] = lazy val abilityArgs: Map[String, (VarModel, NamedType)] =
zipped.collect { case ((name, _), vr @ VarModel(_, t @ AbilityType(_, _), _)) => zipped.collect { case ((name, _), vr @ Ability(_, t, _)) =>
name -> (vr, t) name -> (vr, t)
}.toMap }.toMap

View File

@ -1,17 +1,18 @@
package aqua.model package aqua.model
import aqua.raw.ops.Call import aqua.raw.ops.Call
import aqua.types.{ArrowType, AbilityType, Type} import aqua.types.{ArrowType, NamedType, Type}
import aqua.model.ValueModel.{Ability, Arrow}
// TODO docs // TODO docs
case class CallModel(args: List[ValueModel], exportTo: List[CallModel.Export]) { case class CallModel(args: List[ValueModel], exportTo: List[CallModel.Export]) {
override def toString: String = s"[${args.mkString(" ")}] ${exportTo.mkString(" ")}" override def toString: String = s"[${args.mkString(" ")}] ${exportTo.mkString(" ")}"
def arrowArgNames: Set[String] = args.collect { case VarModel(m, _: ArrowType, _) => def arrowArgNames: Set[String] = args.collect { case Arrow(m, _) =>
m m
}.toSet }.toSet
def abilityArgs: List[(String, AbilityType)] = args.collect { case VarModel(m, t: AbilityType, _) => def abilityArgs: List[(String, NamedType)] = args.collect { case Ability(m, t, _) =>
(m, t) (m, t)
} }

View File

@ -2,9 +2,9 @@ package aqua.model
import aqua.raw.Raw import aqua.raw.Raw
import aqua.raw.arrow.FuncRaw import aqua.raw.arrow.FuncRaw
import aqua.raw.ops.RawTag import aqua.raw.ops.{Call, CallArrowRawTag, RawTag}
import aqua.raw.value.{ValueRaw, VarRaw} import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.types.{ArrowType, Type} import aqua.types.{ArrowType, ServiceType, Type}
case class FuncArrow( case class FuncArrow(
funcName: String, funcName: String,
@ -42,4 +42,49 @@ object FuncArrow {
constants, constants,
topology topology
) )
/**
* Create function - wrapper around a service method
*
* @param funcName name of the function
* @param serviceName name of the service
* @param methodName name of the service method to wrap
* @param methodType type of the service method to wrap
* @param idValue resolved value of the service id
* @return `FuncArrow` wrapper for the service method
*/
def fromServiceMethod(
funcName: String,
serviceName: String,
methodName: String,
methodType: ArrowType,
idValue: ValueModel
): FuncArrow = {
val id = VarRaw("id", idValue.`type`)
val retVar = methodType.res.map(t => VarRaw("ret", t))
val call = Call(
methodType.domain.toLabelledList().map(VarRaw.apply),
retVar.map(r => Call.Export(r.name, r.`type`)).toList
)
val body = CallArrowRawTag.service(
serviceId = id,
fnName = methodName,
call = call,
name = serviceName,
arrowType = methodType
)
FuncArrow(
funcName = funcName,
body = body.leaf,
arrowType = methodType,
ret = retVar.toList,
capturedArrows = Map.empty,
capturedValues = Map(
id.name -> idValue
),
capturedTopology = None
)
}
} }

View File

@ -1,10 +1,9 @@
package aqua.model package aqua.model
import aqua.types.ArrowType import aqua.types.ServiceType
import cats.data.NonEmptyMap
case class ServiceModel( case class ServiceModel(
name: String, name: String,
arrows: NonEmptyMap[String, ArrowType], `type`: ServiceType,
defaultId: Option[ValueModel] defaultId: Option[ValueModel]
) )

View File

@ -51,11 +51,21 @@ object ValueModel {
case _ => ??? case _ => ???
} }
object Arrow {
def unapply(vm: VarModel): Option[(String, ArrowType)] =
vm match {
case VarModel(name, t: ArrowType, _) =>
(name, t).some
case _ => none
}
}
object Ability { object Ability {
def unapply(vm: VarModel): Option[(String, AbilityType, Chain[PropertyModel])] = def unapply(vm: VarModel): Option[(String, NamedType, Chain[PropertyModel])] =
vm match { vm match {
case VarModel(name, t@AbilityType(_, _), properties) => case VarModel(name, t: (AbilityType | ServiceType), properties) =>
(name, t, properties).some (name, t, properties).some
case _ => none case _ => none
} }

View File

@ -20,7 +20,7 @@ case class ArrowExpr[F[_]](arrowTypeExpr: ArrowTypeToken[F])
object ArrowExpr extends Expr.AndIndented { object ArrowExpr extends Expr.AndIndented {
val funcChildren: List[Expr.Lexem] = val funcChildren: List[Expr.Lexem] =
AbilityIdExpr :: ServiceIdExpr ::
PushToStreamExpr :: PushToStreamExpr ::
ForExpr :: ForExpr ::
Expr.defer(OnExpr) :: Expr.defer(OnExpr) ::

View File

@ -1,7 +1,7 @@
package aqua.parser.expr.func package aqua.parser.expr.func
import aqua.parser.Expr import aqua.parser.Expr
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.Token.* import aqua.parser.lexer.Token.*
import aqua.parser.lexer.{NamedTypeToken, ValueToken} import aqua.parser.lexer.{NamedTypeToken, ValueToken}
import aqua.parser.lift.LiftParser import aqua.parser.lift.LiftParser
@ -10,19 +10,19 @@ import cats.{~>, Comonad}
import aqua.parser.lift.Span import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan} import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
case class AbilityIdExpr[F[_]](ability: NamedTypeToken[F], id: ValueToken[F]) case class ServiceIdExpr[F[_]](service: NamedTypeToken[F], id: ValueToken[F])
extends Expr[F](AbilityIdExpr, ability) { extends Expr[F](ServiceIdExpr, service) {
def mapK[K[_]: Comonad](fk: F ~> K): AbilityIdExpr[K] = def mapK[K[_]: Comonad](fk: F ~> K): ServiceIdExpr[K] =
copy(ability.copy(fk(ability.name)), id.mapK(fk)) copy(service.copy(fk(service.name)), id.mapK(fk))
} }
object AbilityIdExpr extends Expr.Leaf { object ServiceIdExpr extends Expr.Leaf {
override val p: P[AbilityIdExpr[Span.S]] = override val p: P[ServiceIdExpr[Span.S]] =
((NamedTypeToken.dotted <* ` `) ~ ValueToken.`value`).map { case (ability, id) => ((NamedTypeToken.dotted <* ` `) ~ ValueToken.`value`).map { case (ability, id) =>
AbilityIdExpr(ability, id) ServiceIdExpr(ability, id)
} }
} }

View File

@ -118,8 +118,8 @@ trait AquaSpec extends EitherValues {
def parseUse(str: String): UseFromExpr[Id] = def parseUse(str: String): UseFromExpr[Id] =
UseFromExpr.p.parseAll(str).value.mapK(spanToId) UseFromExpr.p.parseAll(str).value.mapK(spanToId)
def parseAbId(str: String): AbilityIdExpr[Id] = def parseServiceId(str: String): ServiceIdExpr[Id] =
AbilityIdExpr.p.parseAll(str).value.mapK(spanToId) ServiceIdExpr.p.parseAll(str).value.mapK(spanToId)
def parseOn(str: String): OnExpr[Id] = def parseOn(str: String): OnExpr[Id] =
OnExpr.p.parseAll(str).value.mapK(spanToId) OnExpr.p.parseAll(str).value.mapK(spanToId)

View File

@ -1,9 +1,10 @@
package aqua.parser package aqua.parser
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.LiteralToken import aqua.parser.lexer.LiteralToken
import aqua.types.LiteralType import aqua.types.LiteralType
import cats.Id import cats.Id
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
@ -12,20 +13,20 @@ class AbilityIdExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
import AquaSpec._ import AquaSpec._
"abilities" should "be parsed" in { "abilities" should "be parsed" in {
parseAbId("Ab a") should be( parseServiceId("Ab a") should be(
AbilityIdExpr[Id](toNamedType("Ab"), toVar("a")) ServiceIdExpr[Id](toNamedType("Ab"), toVar("a"))
) )
parseAbId("Ab \"a\"") should be( parseServiceId("Ab \"a\"") should be(
AbilityIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string)) ServiceIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string))
) )
parseAbId("Ab 1") should be( parseServiceId("Ab 1") should be(
AbilityIdExpr[Id](toNamedType("Ab"), toNumber(1)) ServiceIdExpr[Id](toNamedType("Ab"), toNumber(1))
) )
parseAbId("Ab a.id") should be( parseServiceId("Ab a.id") should be(
AbilityIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id"))) ServiceIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id")))
) )
} }

View File

@ -2,17 +2,13 @@ package aqua.parser
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.* import aqua.parser.expr.*
import aqua.parser.expr.func.{AbilityIdExpr, ArrowExpr, CallArrowExpr, IfExpr, OnExpr, ReturnExpr} import aqua.parser.expr.func.*
import aqua.parser.lexer.{ import aqua.parser.lexer.*
ArrowTypeToken,
BasicTypeToken,
CallArrowToken,
LiteralToken,
Token,
VarToken
}
import aqua.parser.lift.LiftParser.Implicits.idLiftParser import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
import aqua.types.ScalarType.* import aqua.types.ScalarType.*
import cats.Id import cats.Id
import cats.data.{Chain, NonEmptyList} import cats.data.{Chain, NonEmptyList}
import cats.data.Chain.* import cats.data.Chain.*
@ -27,11 +23,6 @@ import cats.Eval
import scala.collection.mutable import scala.collection.mutable
import scala.language.implicitConversions import scala.language.implicitConversions
import aqua.parser.lift.Span
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
import aqua.parser.lexer.PropertyToken
import aqua.parser.lexer.IntoArrow
import aqua.parser.expr.func.AssignmentExpr
class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors with AquaSpec { class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors with AquaSpec {
import AquaSpec.{given, *} import AquaSpec.{given, *}
@ -123,7 +114,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors
) )
) )
) )
ifBody(1).head.mapK(spanToId) should be(AbilityIdExpr(toNamedType("Peer"), toStr("some id"))) ifBody(1).head.mapK(spanToId) should be(ServiceIdExpr(toNamedType("Peer"), toStr("some id")))
ifBody(2).head.mapK(spanToId) should be( ifBody(2).head.mapK(spanToId) should be(
CallArrowExpr(Nil, CallArrowToken("call", List(toBool(true)))) CallArrowExpr(Nil, CallArrowToken("call", List(toBool(true))))
) )

View File

@ -1,7 +1,7 @@
package aqua.parser.head package aqua.parser.head
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.LiftParser.Implicits.* import aqua.parser.lift.LiftParser.Implicits.*
import aqua.types.LiteralType import aqua.types.LiteralType

View File

@ -1,7 +1,7 @@
package aqua.parser.head package aqua.parser.head
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.LiftParser.Implicits.* import aqua.parser.lift.LiftParser.Implicits.*
import aqua.types.LiteralType import aqua.types.LiteralType
@ -23,8 +23,7 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec {
) )
) )
HeadExpr HeadExpr.ast
.ast
.parseAll(s"""import MyModule, func as fn from "file.aqua" .parseAll(s"""import MyModule, func as fn from "file.aqua"
|""".stripMargin) |""".stripMargin)
.value .value
@ -32,7 +31,8 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec {
.value .value
.headOption .headOption
.get .get
.head.mapK(spanToId) should be( .head
.mapK(spanToId) should be(
ImportFromExpr( ImportFromExpr(
NonEmptyList.fromListUnsafe( NonEmptyList.fromListUnsafe(
Right(toAb("MyModule") -> None) :: Left(toName("func") -> Some(toName("fn"))) :: Nil Right(toAb("MyModule") -> None) :: Left(toName("func") -> Some(toName("fn"))) :: Nil

View File

@ -1,7 +1,7 @@
package aqua.parser.head package aqua.parser.head
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lexer.{LiteralToken, Token}
import aqua.types.LiteralType import aqua.types.LiteralType
import cats.Id import cats.Id
@ -22,12 +22,12 @@ class ModuleSpec extends AnyFlatSpec with Matchers with AquaSpec {
) )
) )
HeadExpr HeadExpr.ast
.ast
.parseAll(s"""module MyModule declares * .parseAll(s"""module MyModule declares *
|""".stripMargin) |""".stripMargin)
.value .value
.head.mapK(spanToId) should be( .head
.mapK(spanToId) should be(
ModuleExpr( ModuleExpr(
toAb("MyModule"), toAb("MyModule"),
Some(Token.lift[Id, Unit](())), Some(Token.lift[Id, Unit](())),

View File

@ -1,7 +1,7 @@
package aqua.parser.head package aqua.parser.head
import aqua.AquaSpec import aqua.AquaSpec
import aqua.parser.expr.func.AbilityIdExpr import aqua.parser.expr.func.ServiceIdExpr
import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.LiftParser.Implicits.* import aqua.parser.lift.LiftParser.Implicits.*
import aqua.types.LiteralType import aqua.types.LiteralType

View File

@ -8,6 +8,7 @@ import aqua.semantics.rules.definitions.DefinitionsState
import aqua.semantics.rules.locations.LocationsState import aqua.semantics.rules.locations.LocationsState
import aqua.semantics.rules.names.NamesState import aqua.semantics.rules.names.NamesState
import aqua.semantics.rules.types.TypesState import aqua.semantics.rules.types.TypesState
import aqua.semantics.rules.mangler.ManglerState
import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ReportErrors
import cats.Semigroup import cats.Semigroup
@ -19,6 +20,7 @@ import monocle.macros.GenLens
case class CompilerState[S[_]]( case class CompilerState[S[_]](
errors: Chain[SemanticError[S]] = Chain.empty[SemanticError[S]], errors: Chain[SemanticError[S]] = Chain.empty[SemanticError[S]],
mangler: ManglerState = ManglerState(),
names: NamesState[S] = NamesState[S](), names: NamesState[S] = NamesState[S](),
abilities: AbilitiesState[S] = AbilitiesState[S](), abilities: AbilitiesState[S] = AbilitiesState[S](),
types: TypesState[S] = TypesState[S](), types: TypesState[S] = TypesState[S](),
@ -42,6 +44,9 @@ object CompilerState {
given [S[_]]: Lens[CompilerState[S], AbilitiesState[S]] = given [S[_]]: Lens[CompilerState[S], AbilitiesState[S]] =
GenLens[CompilerState[S]](_.abilities) GenLens[CompilerState[S]](_.abilities)
given [S[_]]: Lens[CompilerState[S], ManglerState] =
GenLens[CompilerState[S]](_.mangler)
given [S[_]]: Lens[CompilerState[S], TypesState[S]] = given [S[_]]: Lens[CompilerState[S], TypesState[S]] =
GenLens[CompilerState[S]](_.types) GenLens[CompilerState[S]](_.types)
@ -60,7 +65,7 @@ object CompilerState {
st.focus(_.errors).modify(_.append(RulesViolated(token, hints))) st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
} }
implicit def compilerStateMonoid[S[_]]: Monoid[St[S]] = new Monoid[St[S]] { given [S[_]]: Monoid[St[S]] with {
override def empty: St[S] = State.pure(Raw.Empty("compiler state monoid empty")) override def empty: St[S] = State.pure(Raw.Empty("compiler state monoid empty"))
override def combine(x: St[S], y: St[S]): St[S] = for { override def combine(x: St[S], y: St[S]): St[S] = for {
@ -69,6 +74,7 @@ object CompilerState {
_ <- State.set( _ <- State.set(
CompilerState[S]( CompilerState[S](
a.errors ++ b.errors, a.errors ++ b.errors,
a.mangler |+| b.mangler,
a.names |+| b.names, a.names |+| b.names,
a.abilities |+| b.abilities, a.abilities |+| b.abilities,
a.types |+| b.types, a.types |+| b.types,

View File

@ -27,7 +27,7 @@ object ExprSem {
L: LocationsAlgebra[S, G] L: LocationsAlgebra[S, G]
): Prog[G, Raw] = ): Prog[G, Raw] =
expr match { expr match {
case expr: AbilityIdExpr[S] => new AbilityIdSem(expr).program[G] case expr: ServiceIdExpr[S] => new ServiceIdSem(expr).program[G]
case expr: AssignmentExpr[S] => new AssignmentSem(expr).program[G] case expr: AssignmentExpr[S] => new AssignmentSem(expr).program[G]
case expr: PushToStreamExpr[S] => new PushToStreamSem(expr).program[G] case expr: PushToStreamExpr[S] => new PushToStreamSem(expr).program[G]
case expr: AliasExpr[S] => new AliasSem(expr).program[G] case expr: AliasExpr[S] => new AliasSem(expr).program[G]

View File

@ -8,15 +8,11 @@ import aqua.raw.{Raw, RawContext, RawPart}
import aqua.semantics.header.Picker import aqua.semantics.header.Picker
import aqua.semantics.header.Picker.* import aqua.semantics.header.Picker.*
import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState} import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
import aqua.semantics.rules.definitions.{ import aqua.semantics.rules.definitions.{DefinitionsAlgebra, DefinitionsInterpreter}
DefinitionsAlgebra, import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
DefinitionsInterpreter, import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter}
DefinitionsState import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter}
} import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter}
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra, LocationsState}
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ReportErrors
import aqua.semantics.rules.errors.ErrorsAlgebra import aqua.semantics.rules.errors.ErrorsAlgebra
import aqua.raw.ops.* import aqua.raw.ops.*
@ -306,17 +302,19 @@ object RawSemantics extends Logging {
type Interpreter[S[_], A] = State[CompilerState[S], A] type Interpreter[S[_], A] = State[CompilerState[S], A]
def transpile[S[_]]( def transpile[S[_]](ast: Ast[S])(using
ast: Ast[S] LocationsAlgebra[S, Interpreter[S, *]]
)(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = { ): Interpreter[S, Raw] = {
implicit val typesInterpreter: TypesInterpreter[S, CompilerState[S]] = given TypesAlgebra[S, Interpreter[S, *]] =
new TypesInterpreter[S, CompilerState[S]] new TypesInterpreter[S, CompilerState[S]]
implicit val abilitiesInterpreter: AbilitiesInterpreter[S, CompilerState[S]] = given ManglerAlgebra[Interpreter[S, *]] =
new ManglerInterpreter[CompilerState[S]]
given AbilitiesAlgebra[S, Interpreter[S, *]] =
new AbilitiesInterpreter[S, CompilerState[S]] new AbilitiesInterpreter[S, CompilerState[S]]
implicit val namesInterpreter: NamesInterpreter[S, CompilerState[S]] = given NamesAlgebra[S, Interpreter[S, *]] =
new NamesInterpreter[S, CompilerState[S]] new NamesInterpreter[S, CompilerState[S]]
implicit val definitionsInterpreter: DefinitionsInterpreter[S, CompilerState[S]] = given DefinitionsAlgebra[S, Interpreter[S, *]] =
new DefinitionsInterpreter[S, CompilerState[S]] new DefinitionsInterpreter[S, CompilerState[S]]
ast ast

View File

@ -1,9 +1,8 @@
package aqua.semantics.expr package aqua.semantics.expr
import aqua.parser.expr.AbilityExpr import aqua.parser.expr.AbilityExpr
import aqua.raw.{Raw, ServiceRaw, TypeRaw} import aqua.raw.{Raw, TypeRaw}
import aqua.parser.lexer.{Name, NamedTypeToken} import aqua.parser.lexer.{Name, NamedTypeToken}
import aqua.raw.{Raw, ServiceRaw}
import aqua.semantics.Prog import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra
@ -11,6 +10,7 @@ import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{AbilityType, ArrowType, Type} import aqua.types.{AbilityType, ArrowType, Type}
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*

View File

@ -8,49 +8,72 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.definitions.DefinitionsAlgebra import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra
import cats.data.EitherT
import cats.syntax.either.*
import cats.syntax.option.*
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.syntax.foldable.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.Monad import cats.Monad
class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal { class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal {
def program[Alg[_]: Monad](implicit private def define[Alg[_]: Monad](using
A: AbilitiesAlgebra[S, Alg], A: AbilitiesAlgebra[S, Alg],
N: NamesAlgebra[S, Alg], N: NamesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg], T: TypesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg], V: ValuesAlgebra[S, Alg],
D: DefinitionsAlgebra[S, Alg] D: DefinitionsAlgebra[S, Alg]
): Prog[Alg, Raw] = ): EitherT[Alg, Raw, ServiceRaw] = for {
Prog.after( arrows <- EitherT.fromOptionF(
_ => // TODO: Move to purgeDefs here, allow not only arrows
D.purgeArrows(expr.name).flatMap { // from parsing, throw errors here
case Some(nel) => D.purgeArrows(expr.name),
val arrows = nel.map(kv => kv._1.value -> (kv._1, kv._2)).toNem Raw.error("Service has no arrows")
for { )
defaultId <- expr.id arrowsByName = arrows.map { case (name, arrow) =>
.map(v => V.valueToRaw(v)) name.value -> (name, arrow)
.getOrElse(None.pure[Alg]) }.toNem
defineResult <- A.defineService( defaultId <- expr.id.traverse(id =>
EitherT.fromOptionF(
V.valueToStringRaw(id),
Raw.error("Failed to resolve default service id")
)
)
serviceType <- EitherT.fromOptionF(
T.defineServiceType(expr.name, arrowsByName.toSortedMap.toMap),
Raw.error("Failed to define service type")
)
arrowsDefs = arrows.map { case (name, _) => name.value -> name }.toNem
_ <- EitherT(
A.defineService(
expr.name, expr.name,
arrows, arrowsDefs,
defaultId
).map(defined =>
Raw
.error("Service not created due to validation errors")
.asLeft
.whenA(!defined)
)
)
} yield ServiceRaw(
expr.name.value,
serviceType,
defaultId defaultId
) )
_ <- (expr.id zip defaultId)
.fold(().pure[Alg])(idV =>
(V.ensureIsString(idV._1) >> A.setServiceId(expr.name, idV._1, idV._2)).map(_ =>
()
)
)
} yield
if (defineResult) {
ServiceRaw(expr.name.value, arrows.map(_._2), defaultId)
} else Raw.empty("Service not created due to validation errors")
case None => def program[Alg[_]: Monad](using
Raw.error("Service has no arrows, fails").pure[Alg] A: AbilitiesAlgebra[S, Alg],
N: NamesAlgebra[S, Alg],
} T: TypesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg],
D: DefinitionsAlgebra[S, Alg]
): Prog[Alg, Raw] = Prog.after_(
define.value.map(_.merge)
) )
} }

View File

@ -1,30 +0,0 @@
package aqua.semantics.expr.func
import aqua.raw.Raw
import aqua.raw.ops.AbilityIdTag
import aqua.parser.expr.func.AbilityIdExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import cats.Monad
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
class AbilityIdSem[S[_]](val expr: AbilityIdExpr[S]) extends AnyVal {
def program[Alg[_]: Monad](implicit
A: AbilitiesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg]
): Prog[Alg, Raw] =
V.ensureIsString(expr.id) >> V.valueToRaw(
expr.id
) >>= {
case Some(id) =>
A.setServiceId(expr.ability, expr.id, id) as (AbilityIdTag(
id,
expr.ability.value
).funcOpLeaf: Raw)
case _ => Raw.error("Cannot resolve ability ID").pure[Alg]
}
}

View File

@ -13,11 +13,13 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, ArrowType, CanonStreamType, ProductType, StreamType, Type} import aqua.types.*
import cats.Eval import cats.Eval
import cats.data.{Chain, NonEmptyList} import cats.data.{Chain, NonEmptyList}
import cats.free.{Cofree, Free} import cats.free.{Cofree, Free}
import cats.data.OptionT
import cats.syntax.show.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
@ -64,16 +66,15 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal {
res = bodyGen match { res = bodyGen match {
case FuncOp(bodyModel) => case FuncOp(bodyModel) =>
// TODO: wrap with local on...via... // TODO: wrap with local on...via...
val retsAndArgs = retValues zip funcArrow.codomain.toList val retsAndArgs = retValues zip funcArrow.codomain.toList
val argNames = funcArrow.domain.labelledData.map { case (name, _) => name } val dataArgsNames = funcArrow.domain.labelledData.map { case (name, _) => name }
val streamsThatReturnAsStreams = retsAndArgs.collect { val streamsThatReturnAsStreams = retsAndArgs.collect {
case (VarRaw(n, StreamType(_)), StreamType(_)) => n case (VarRaw(n, StreamType(_)), StreamType(_)) => n
}.toSet }.toSet
// Remove arguments, and values returned as streams // Remove arguments, and values returned as streams
val localStreams = streamsInScope -- argNames -- streamsThatReturnAsStreams val localStreams = streamsInScope -- dataArgsNames -- streamsThatReturnAsStreams
// process stream that returns as not streams and all Apply*Raw // process stream that returns as not streams and all Apply*Raw
val (bodyRets, retVals) = retsAndArgs.mapWithIndex { val (bodyRets, retVals) = retsAndArgs.mapWithIndex {

View File

@ -10,7 +10,9 @@ import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{BoxType, OptionType, ScalarType} import aqua.types.{BoxType, OptionType, ScalarType}
import cats.data.Chain import cats.data.Chain
import cats.data.OptionT
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
@ -49,24 +51,25 @@ class OnSem[S[_]](val expr: OnExpr[S]) extends AnyVal {
object OnSem { object OnSem {
def beforeOn[S[_], Alg[_]: Monad](peerId: ValueToken[S], via: List[ValueToken[S]])(implicit def beforeOn[S[_], Alg[_]: Monad](
peerId: ValueToken[S],
via: List[ValueToken[S]]
)(using
V: ValuesAlgebra[S, Alg], V: ValuesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg], T: TypesAlgebra[S, Alg],
A: AbilitiesAlgebra[S, Alg] A: AbilitiesAlgebra[S, Alg]
): Alg[List[ValueRaw]] = ): Alg[List[ValueRaw]] =
// TODO: Remove ensureIsString, use valueToStringRaw
V.ensureIsString(peerId) *> via V.ensureIsString(peerId) *> via
.traverse(v => .traverse(v =>
V.valueToRaw(v).flatTap { OptionT(V.valueToRaw(v)).filterF { vm =>
case Some(vm) => val expectedType = vm.`type` match {
vm.`type` match { case _: BoxType => OptionType(ScalarType.string)
case _: BoxType => case _ => ScalarType.string
T.ensureTypeMatches(v, OptionType(ScalarType.string), vm.`type`)
case _ =>
T.ensureTypeMatches(v, ScalarType.string, vm.`type`)
} }
case None => false.pure[Alg]
T.ensureTypeMatches(v, expectedType, vm.`type`)
} }
) )
.map(_.flatten) .getOrElse(List.empty)
} }

View File

@ -0,0 +1,49 @@
package aqua.semantics.expr.func
import aqua.raw.Raw
import aqua.raw.ops.ServiceIdTag
import aqua.parser.expr.func.ServiceIdExpr
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import cats.Monad
import cats.data.EitherT
import cats.syntax.either.*
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
class ServiceIdSem[S[_]](val expr: ServiceIdExpr[S]) extends AnyVal {
def program[Alg[_]: Monad](using
A: AbilitiesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg],
N: NamesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg]
): Prog[Alg, Raw] = (
for {
id <- EitherT.fromOptionF(
V.valueToStringRaw(expr.id),
Raw.error("Can not resolve service ID")
)
serviceType <- EitherT.fromOptionF(
T.resolveServiceType(expr.service),
Raw.error("Can not resolve service type")
)
name <- EitherT.fromOptionF(
A.renameService(expr.service),
Raw.error("Can not set service ID")
)
_ <- EitherT.liftF(
N.derive(
expr.service.asName.rename(name),
serviceType,
id.varNames
)
)
} yield ServiceIdTag(id, serviceType, name).funcOpLeaf
).value.map(_.merge)
}

View File

@ -11,6 +11,7 @@ import aqua.semantics.rules.errors.ErrorsAlgebra
import aqua.types.* import aqua.types.*
import cats.Monad import cats.Monad
import cats.data.OptionT
import cats.data.Chain import cats.data.Chain
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*
@ -33,23 +34,6 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
A: AbilitiesAlgebra[S, Alg] A: AbilitiesAlgebra[S, Alg]
) extends Logging { ) extends Logging {
def ensureIsString(v: ValueToken[S]): Alg[Boolean] =
ensureTypeMatches(v, LiteralType.string)
def ensureTypeMatches(v: ValueToken[S], expected: Type): Alg[Boolean] =
resolveType(v).flatMap {
case Some(vt) =>
T.ensureTypeMatches(
v,
expected,
vt
)
case None => false.pure[Alg]
}
def resolveType(v: ValueToken[S]): Alg[Option[Type]] =
valueToRaw(v).map(_.map(_.`type`))
private def resolveSingleProperty(rootType: Type, op: PropertyOp[S]): Alg[Option[PropertyRaw]] = private def resolveSingleProperty(rootType: Type, op: PropertyOp[S]): Alg[Option[PropertyRaw]] =
op match { op match {
case op: IntoField[S] => case op: IntoField[S] =>
@ -83,11 +67,20 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
LiteralRaw(l.value, t).some.pure[Alg] LiteralRaw(l.value, t).some.pure[Alg]
case VarToken(name) => case VarToken(name) =>
N.read(name).flatMap { N.read(name, mustBeDefined = false).flatMap {
case Some(t) => case Some(t) =>
VarRaw(name.value, t).some.pure[Alg] VarRaw(name.value, t).some.pure
case None => case None =>
None.pure[Alg] (for {
t <- OptionT(
T.getType(name.value)
).collect { case st: ServiceType => st }
// A hack to report name error, better to refactor
.flatTapNone(N.read(name))
rename <- OptionT(
A.getServiceRename(name.asTypeToken)
)
} yield VarRaw(rename, t)).value
} }
case prop @ PropertyToken(value, properties) => case prop @ PropertyToken(value, properties) =>
@ -107,35 +100,30 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
)(valueToRaw) )(valueToRaw)
case dvt @ NamedValueToken(typeName, fields) => case dvt @ NamedValueToken(typeName, fields) =>
T.resolveType(typeName).flatMap { (for {
case Some(resolvedType) => resolvedType <- OptionT(T.resolveType(typeName))
for { fieldsGiven <- fields.traverse(value => OptionT(valueToRaw(value)))
fieldsRawOp: NonEmptyMap[String, Option[ValueRaw]] <- fields.traverse(valueToRaw) fieldsGivenTypes = fieldsGiven.map(_.`type`)
fieldsRaw: List[(String, ValueRaw)] = fieldsRawOp.toSortedMap.toList.collect { generated <- OptionT.fromOption(
case (n, Some(vr)) => n -> vr
}
rawFields = NonEmptyMap.fromMap(SortedMap.from(fieldsRaw))
typeFromFieldsWithData = rawFields
.map(rf =>
resolvedType match { resolvedType match {
case struct @ StructType(_, _) => case struct: StructType =>
( (
StructType(typeName.value, rf.map(_.`type`)), struct.copy(fields = fieldsGivenTypes),
Some(MakeStructRaw(rf, struct)) MakeStructRaw(fieldsGiven, struct)
) ).some
case scope @ AbilityType(_, _) => case ability: AbilityType =>
( (
AbilityType(typeName.value, rf.map(_.`type`)), ability.copy(fields = fieldsGivenTypes),
Some(AbilityRaw(rf, scope)) AbilityRaw(fieldsGiven, ability)
) ).some
case _ => none
} }
) )
.getOrElse(BottomType -> None) (genType, genData) = generated
(typeFromFields, data) = typeFromFieldsWithData data <- OptionT.whenM(
isTypesCompatible <- T.ensureTypeMatches(dvt, resolvedType, typeFromFields) T.ensureTypeMatches(dvt, resolvedType, genType)
} yield data.filter(_ => isTypesCompatible) )(genData.pure)
case _ => None.pure[Alg] } yield data).value
}
case ct @ CollectionToken(_, values) => case ct @ CollectionToken(_, values) =>
for { for {
@ -295,15 +283,15 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
} yield Option.when( } yield Option.when(
leftChecked.isDefined && rightChecked.isDefined leftChecked.isDefined && rightChecked.isDefined
)( )(
CallArrowRaw( CallArrowRaw.service(
ability = Some(id), abilityName = id,
name = fn, serviceId = LiteralRaw.quote(id),
arguments = leftRaw :: rightRaw :: Nil, funcName = fn,
baseType = ArrowType( baseType = ArrowType(
ProductType(lType :: rType :: Nil), ProductType(lType :: rType :: Nil),
ProductType(resType :: Nil) ProductType(resType :: Nil)
), ),
serviceId = Some(LiteralRaw.quote(id)) arguments = leftRaw :: rightRaw :: Nil
) )
) )
@ -321,9 +309,24 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
} }
) )
def valueToTypedRaw(v: ValueToken[S], expectedType: Type): Alg[Option[ValueRaw]] =
OptionT(valueToRaw(v))
.flatMap(raw =>
OptionT.whenM(
T.ensureTypeMatches(v, expectedType, raw.`type`)
)(raw.pure)
)
.value
def valueToStringRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] =
valueToTypedRaw(v, LiteralType.string)
def ensureIsString(v: ValueToken[S]): Alg[Boolean] =
valueToStringRaw(v).map(_.isDefined)
private def callArrowFromAbility( private def callArrowFromAbility(
ab: Name[S], ab: Name[S],
at: AbilityType, at: NamedType,
funcName: Name[S] funcName: Name[S]
): Option[CallArrowRaw] = at.arrows ): Option[CallArrowRaw] = at.arrows
.get(funcName.value) .get(funcName.value)
@ -351,24 +354,23 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
) )
)(ab => )(ab =>
N.read(ab.asName, mustBeDefined = false).flatMap { N.read(ab.asName, mustBeDefined = false).flatMap {
case Some(at: AbilityType) => case Some(nt: (AbilityType | ServiceType)) =>
callArrowFromAbility(ab.asName, at, callArrow.funcName).pure callArrowFromAbility(ab.asName, nt, callArrow.funcName).pure
case _ => case _ =>
T.getType(ab.value).flatMap { T.getType(ab.value).flatMap {
case Some(at: AbilityType) => case Some(st: ServiceType) =>
callArrowFromAbility(ab.asName, at, callArrow.funcName).pure OptionT(A.getServiceRename(ab))
case _ => .subflatMap(rename =>
(A.getArrow(ab, callArrow.funcName), A.getServiceId(ab)).mapN { callArrowFromAbility(
case (Some(at), Right(sid)) => ab.asName.rename(rename),
CallArrowRaw st,
.service( callArrow.funcName
abilityName = ab.value,
serviceId = sid,
funcName = callArrow.funcName.value,
baseType = at
) )
.some )
case (Some(at), Left(true)) => .value
case _ =>
A.getArrow(ab, callArrow.funcName).map {
case Some(at) =>
CallArrowRaw CallArrowRaw
.ability( .ability(
abilityName = ab.value, abilityName = ab.value,

View File

@ -2,7 +2,8 @@ package aqua.semantics.rules.abilities
import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken} import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken}
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
import aqua.types.ArrowType import aqua.types.{ArrowType, ServiceType}
import cats.InjectK import cats.InjectK
import cats.data.{NonEmptyList, NonEmptyMap} import cats.data.{NonEmptyList, NonEmptyMap}
@ -10,15 +11,15 @@ trait AbilitiesAlgebra[S[_], Alg[_]] {
def defineService( def defineService(
name: NamedTypeToken[S], name: NamedTypeToken[S],
arrows: NonEmptyMap[String, (Name[S], ArrowType)], arrowDefs: NonEmptyMap[String, Name[S]],
defaultId: Option[ValueRaw] defaultId: Option[ValueRaw]
): Alg[Boolean] ): Alg[Boolean]
def getArrow(name: NamedTypeToken[S], arrow: Name[S]): Alg[Option[ArrowType]] def getArrow(name: NamedTypeToken[S], arrow: Name[S]): Alg[Option[ArrowType]]
def setServiceId(name: NamedTypeToken[S], id: ValueToken[S], vm: ValueRaw): Alg[Boolean] def renameService(name: NamedTypeToken[S]): Alg[Option[String]]
def getServiceId(name: NamedTypeToken[S]): Alg[Either[Boolean, ValueRaw]] def getServiceRename(name: NamedTypeToken[S]): Alg[Option[String]]
def beginScope(token: Token[S]): Alg[Unit] def beginScope(token: Token[S]): Alg[Unit]

View File

@ -5,27 +5,31 @@ import aqua.raw.value.ValueRaw
import aqua.raw.{RawContext, ServiceRaw} import aqua.raw.{RawContext, ServiceRaw}
import aqua.semantics.Levenshtein import aqua.semantics.Levenshtein
import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ReportErrors
import aqua.semantics.rules.mangler.ManglerAlgebra
import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.{abilities, StackInterpreter} import aqua.semantics.rules.{abilities, StackInterpreter}
import aqua.types.ArrowType import aqua.types.{ArrowType, ServiceType}
import cats.data.{NonEmptyMap, State} import cats.data.{NonEmptyMap, State}
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.apply.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.option.*
import monocle.Lens import monocle.Lens
import monocle.macros.GenLens import monocle.macros.GenLens
class AbilitiesInterpreter[S[_], X](implicit class AbilitiesInterpreter[S[_], X](using
lens: Lens[X, AbilitiesState[S]], lens: Lens[X, AbilitiesState[S]],
error: ReportErrors[S, X], error: ReportErrors[S, X],
mangler: ManglerAlgebra[State[X, *]],
locations: LocationsAlgebra[S, State[X, *]] locations: LocationsAlgebra[S, State[X, *]]
) extends AbilitiesAlgebra[S, State[X, *]] { ) extends AbilitiesAlgebra[S, State[X, *]] {
type SX[A] = State[X, A] type SX[A] = State[X, A]
val stackInt = new StackInterpreter[S, X, AbilitiesState[S], AbilitiesState.Frame[S]]( private val stackInt = new StackInterpreter[S, X, AbilitiesState[S], AbilitiesState.Frame[S]](
GenLens[AbilitiesState[S]](_.stack) GenLens[AbilitiesState[S]](_.stack)
) )
@ -33,11 +37,11 @@ class AbilitiesInterpreter[S[_], X](implicit
override def defineService( override def defineService(
name: NamedTypeToken[S], name: NamedTypeToken[S],
arrows: NonEmptyMap[String, (Name[S], ArrowType)], arrowDefs: NonEmptyMap[String, Name[S]],
defaultId: Option[ValueRaw] defaultId: Option[ValueRaw]
): SX[Boolean] = ): SX[Boolean] =
getService(name.value).flatMap { serviceExists(name.value).flatMap {
case Some(_) => case true =>
getState getState
.map(_.definitions.get(name.value).exists(_ == name)) .map(_.definitions.get(name.value).exists(_ == name))
.flatMap(exists => .flatMap(exists =>
@ -47,23 +51,14 @@ class AbilitiesInterpreter[S[_], X](implicit
).whenA(!exists) ).whenA(!exists)
) )
.as(false) .as(false)
case None => case false =>
for { for {
_ <- arrows.toNel.traverse_ { case (_, (n, arr)) => _ <- modify(_.defineService(name, defaultId))
report(n, "Service functions cannot have multiple results") // TODO: Is it used?
.whenA(arr.codomain.length > 1)
}
_ <- modify(s =>
s.copy(
services = s.services
.updated(name.value, ServiceRaw(name.value, arrows.map(_._2), defaultId)),
definitions = s.definitions.updated(name.value, name)
)
)
_ <- locations.addTokenWithFields( _ <- locations.addTokenWithFields(
name.value, name.value,
name, name,
arrows.toNel.toList.map(t => t._1 -> t._2._1) arrowDefs.toNel.toList
) )
} yield true } yield true
} }
@ -74,20 +69,6 @@ class AbilitiesInterpreter[S[_], X](implicit
} }
override def getArrow(name: NamedTypeToken[S], arrow: Name[S]): SX[Option[ArrowType]] = override def getArrow(name: NamedTypeToken[S], arrow: Name[S]): SX[Option[ArrowType]] =
getService(name.value).map(_.map(_.arrows)).flatMap {
case Some(arrows) =>
arrows(arrow.value)
.fold(
report(
arrow,
Levenshtein.genMessage(
s"Service is found, but arrow '${arrow.value}' isn't found in scope",
arrow.value,
arrows.value.keys.toNonEmptyList.toList
)
).as(Option.empty[ArrowType])
)(a => addServiceArrowLocation(name, arrow).as(Some(a)))
case None =>
getAbility(name.value).flatMap { getAbility(name.value).flatMap {
case Some(abCtx) => case Some(abCtx) =>
abCtx.funcs abCtx.funcs
@ -100,57 +81,38 @@ class AbilitiesInterpreter[S[_], X](implicit
arrow.value, arrow.value,
abCtx.funcs.keys.toList abCtx.funcs.keys.toList
) )
).as(Option.empty[ArrowType]) ).as(none)
) { fn => ) { fn =>
// TODO: add name and arrow separately // TODO: add name and arrow separately
// TODO: find tokens somewhere // TODO: find tokens somewhere
addServiceArrowLocation(name, arrow).as(Some(fn.arrow.`type`)) addServiceArrowLocation(name, arrow).as(fn.arrow.`type`.some)
} }
case None => case None =>
report(name, "Ability with this name is undefined").as(Option.empty[ArrowType]) report(name, "Ability with this name is undefined").as(none)
}
} }
override def setServiceId(name: NamedTypeToken[S], id: ValueToken[S], vm: ValueRaw): SX[Boolean] = override def renameService(name: NamedTypeToken[S]): SX[Option[String]] =
getService(name.value).flatMap { serviceExists(name.value).flatMap {
case Some(_) => case true =>
mapStackHeadM( mapStackHeadM(
modify(st => st.copy(rootServiceIds = st.rootServiceIds.updated(name.value, id -> vm))) name.value.pure
.as(true) )(h =>
)(h => (h.copy(serviceIds = h.serviceIds.updated(name.value, id -> vm)) -> true).pure) mangler
case None => .rename(name.value)
report(name, "Service with this name is not registered, can't set its ID").as(false) .map(newName => h.setServiceRename(name.value, newName) -> newName)
).map(_.some)
case false =>
report(name, "Service with this name is not registered").as(none)
} }
override def getServiceId(name: NamedTypeToken[S]): SX[Either[Boolean, ValueRaw]] = override def getServiceRename(name: NamedTypeToken[S]): State[X, Option[String]] =
getService(name.value).flatMap { (
case Some(_) => serviceExists(name.value),
getState.flatMap(st => getState.map(_.getServiceRename(name.value))
st.stack ).flatMapN {
.flatMap(_.serviceIds.get(name.value).map(_._2)) case (true, Some(rename)) => rename.some.pure
.headOption orElse st.rootServiceIds case (false, _) => report(name, "Service with this name is undefined").as(none)
.get( case (_, None) => report(name, "Service ID is undefined").as(none)
name.value
)
.map(_._2) orElse st.services.get(name.value).flatMap(_.defaultId) match {
case None =>
report(
name,
s"Service ID unresolved, use `${name.value} id` expression to set it"
)
.as(Left[Boolean, ValueRaw](false))
case Some(v) => State.pure(Right(v))
}
)
case None =>
getAbility(name.value).flatMap {
case Some(_) => State.pure(Left[Boolean, ValueRaw](true))
case None =>
report(name, "Ability with this name is undefined").as(
Left[Boolean, ValueRaw](false)
)
}
} }
override def beginScope(token: Token[S]): SX[Unit] = override def beginScope(token: Token[S]): SX[Unit] =
@ -158,8 +120,8 @@ class AbilitiesInterpreter[S[_], X](implicit
override def endScope(): SX[Unit] = stackInt.endScope override def endScope(): SX[Unit] = stackInt.endScope
private def getService(name: String): SX[Option[ServiceRaw]] = private def serviceExists(name: String): SX[Boolean] =
getState.map(_.services.get(name)) getState.map(_.services(name))
private def getAbility(name: String): SX[Option[RawContext]] = private def getAbility(name: String): SX[Option[RawContext]] =
getState.map(_.abilities.get(name)) getState.map(_.abilities.get(name))

View File

@ -4,39 +4,63 @@ import aqua.raw.{RawContext, ServiceRaw}
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken} import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken}
import aqua.types.ArrowType import aqua.types.ArrowType
import cats.Monoid import cats.Monoid
import cats.syntax.foldable.*
import cats.syntax.functor.*
import cats.data.NonEmptyList import cats.data.NonEmptyList
import aqua.parser.lexer.Token.name
case class AbilitiesState[S[_]]( case class AbilitiesState[S[_]](
stack: List[AbilitiesState.Frame[S]] = Nil, stack: List[AbilitiesState.Frame[S]] = Nil,
services: Map[String, ServiceRaw] = Map.empty, services: Set[String] = Set.empty,
abilities: Map[String, RawContext] = Map.empty, abilities: Map[String, RawContext] = Map.empty,
rootServiceIds: Map[String, (ValueToken[S], ValueRaw)] = rootServiceIds: Map[String, ValueRaw] = Map(),
Map.empty[String, (ValueToken[S], ValueRaw)], definitions: Map[String, NamedTypeToken[S]] = Map()
definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]]
) { ) {
def purgeArrows: Option[(NonEmptyList[(Name[S], ArrowType)], AbilitiesState[S])] = def defineService(name: NamedTypeToken[S], defaultId: Option[ValueRaw]): AbilitiesState[S] =
stack match { copy(
case sc :: tail => services = services + name.value,
NonEmptyList definitions = definitions.updated(name.value, name),
.fromList(sc.arrows.values.toList) rootServiceIds = rootServiceIds ++ defaultId.map(name.value -> _)
.map(_ -> copy[S](sc.copy(arrows = Map.empty) :: tail)) )
case _ => None
} def getServiceRename(name: String): Option[String] =
stack.collectFirstSome(_.getServiceRename(name)) orElse
// Suppose that services without id
// resolved in scope are not renamed
rootServiceIds.get(name).as(name)
} }
object AbilitiesState { object AbilitiesState {
case class Frame[S[_]]( case class Frame[S[_]](
token: Token[S], token: Token[S],
arrows: Map[String, (Name[S], ArrowType)] = Map.empty[String, (Name[S], ArrowType)], services: Map[String, Frame.ServiceState] = Map()
serviceIds: Map[String, (ValueToken[S], ValueRaw)] = ) {
Map.empty[String, (ValueToken[S], ValueRaw)]
def setServiceRename(name: String, rename: String): Frame[S] =
copy(services =
services.updated(
name,
Frame.ServiceState(rename)
)
) )
implicit def abilitiesStateMonoid[S[_]]: Monoid[AbilitiesState[S]] = def getServiceRename(name: String): Option[String] =
new Monoid[AbilitiesState[S]] { services.get(name).map(_.rename)
}
object Frame {
final case class ServiceState(
rename: String
)
}
given [S[_]]: Monoid[AbilitiesState[S]] with {
override def empty: AbilitiesState[S] = AbilitiesState() override def empty: AbilitiesState[S] = AbilitiesState()
override def combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] = override def combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] =
@ -51,7 +75,10 @@ object AbilitiesState {
def init[S[_]](context: RawContext): AbilitiesState[S] = def init[S[_]](context: RawContext): AbilitiesState[S] =
AbilitiesState( AbilitiesState(
services = context.allServices, services = context.allServices.keySet,
rootServiceIds = context.allServices.flatMap { case (name, service) =>
service.defaultId.map(name -> _)
},
abilities = context.abilities // TODO is it the right way to collect abilities? Why? abilities = context.abilities // TODO is it the right way to collect abilities? Why?
) )
} }

View File

@ -0,0 +1,5 @@
package aqua.semantics.rules.mangler
trait ManglerAlgebra[Alg[_]] {
def rename(name: String): Alg[String]
}

View File

@ -0,0 +1,27 @@
package aqua.semantics.rules.mangler
import cats.data.State
import monocle.Lens
import monocle.macros.GenLens
class ManglerInterpreter[X](using
lens: Lens[X, ManglerState]
) extends ManglerAlgebra[State[X, *]] {
override def rename(name: String): State[X, String] =
for {
s <- get
newName = LazyList
.from(0)
.map(i => s"$name-$i")
.dropWhile(s.isForbidden)
.head
_ <- modify(_.forbid(newName))
} yield newName
private lazy val get: State[X, ManglerState] =
State.get[X].map(lens.get)
private def modify(f: ManglerState => ManglerState): State[X, Unit] =
State.modify[X](lens.modify(f))
}

View File

@ -0,0 +1,24 @@
package aqua.semantics.rules.mangler
import cats.kernel.Monoid
final case class ManglerState(
forbidden: Set[String] = Set.empty
) {
def isForbidden(name: String): Boolean =
forbidden.contains(name)
def forbid(name: String): ManglerState =
copy(forbidden = forbidden + name)
}
object ManglerState {
given Monoid[ManglerState] with {
override val empty: ManglerState = ManglerState()
override def combine(x: ManglerState, y: ManglerState): ManglerState =
ManglerState(forbidden = x.forbidden ++ y.forbidden)
}
}

View File

@ -2,7 +2,7 @@ package aqua.semantics.rules.types
import aqua.parser.lexer.* import aqua.parser.lexer.*
import aqua.raw.value.{PropertyRaw, ValueRaw} import aqua.raw.value.{PropertyRaw, ValueRaw}
import aqua.types.{AbilityType, ArrowType, NamedType, StructType, Type} import aqua.types.*
import cats.data.NonEmptyMap import cats.data.NonEmptyMap
import cats.data.NonEmptyList import cats.data.NonEmptyList
@ -15,11 +15,18 @@ trait TypesAlgebra[S[_], Alg[_]] {
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]] def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]]
def resolveServiceType(name: NamedTypeToken[S]): Alg[Option[ServiceType]]
def defineAbilityType( def defineAbilityType(
name: NamedTypeToken[S], name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)] fields: Map[String, (Name[S], Type)]
): Alg[Option[AbilityType]] ): Alg[Option[AbilityType]]
def defineServiceType(
name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)]
): Alg[Option[ServiceType]]
def defineStructType( def defineStructType(
name: NamedTypeToken[S], name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)] fields: Map[String, (Name[S], Type)]

View File

@ -13,16 +13,17 @@ import aqua.raw.value.{
import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.StackInterpreter import aqua.semantics.rules.StackInterpreter
import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ReportErrors
import aqua.semantics.rules.types.TypesStateHelper.{TypeResolution, TypeResolutionError}
import aqua.types.* import aqua.types.*
import cats.data.Validated.{Invalid, Valid} import cats.data.Validated.{Invalid, Valid}
import cats.data.{Chain, NonEmptyList, NonEmptyMap, State} import cats.data.{Chain, NonEmptyList, NonEmptyMap, OptionT, State}
import cats.instances.list.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.syntax.foldable.*
import cats.{~>, Applicative} import cats.{~>, Applicative}
import cats.syntax.option.* import cats.syntax.option.*
import monocle.Lens import monocle.Lens
@ -44,18 +45,12 @@ class TypesInterpreter[S[_], X](implicit
type ST[A] = State[X, A] type ST[A] = State[X, A]
val resolver: (TypesState[S], NamedTypeToken[S]) => Option[
(Type, List[(Token[S], NamedTypeToken[S])])
] = { (state, ctt) =>
state.strict.get(ctt.value).map(t => (t, state.definitions.get(ctt.value).toList.map(ctt -> _)))
}
override def getType(name: String): State[X, Option[Type]] = override def getType(name: String): State[X, Option[Type]] =
getState.map(st => st.strict.get(name)) getState.map(st => st.strict.get(name))
override def resolveType(token: TypeToken[S]): State[X, Option[Type]] = override def resolveType(token: TypeToken[S]): State[X, Option[Type]] =
getState.map(st => TypesStateHelper.resolveTypeToken(token, st, resolver)).flatMap { getState.map(TypesStateHelper.resolveTypeToken(token)).flatMap {
case Some((typ, tokens)) => case Some(TypeResolution(typ, tokens)) =>
val tokensLocs = tokens.map { case (t, n) => n.value -> t } val tokensLocs = tokens.map { case (t, n) => n.value -> t }
locations.pointLocations(tokensLocs).as(typ.some) locations.pointLocations(tokensLocs).as(typ.some)
case None => case None =>
@ -64,48 +59,78 @@ class TypesInterpreter[S[_], X](implicit
} }
override def resolveArrowDef(arrowDef: ArrowTypeToken[S]): State[X, Option[ArrowType]] = override def resolveArrowDef(arrowDef: ArrowTypeToken[S]): State[X, Option[ArrowType]] =
getState.map(st => TypesStateHelper.resolveArrowDef(arrowDef, st, resolver)).flatMap { getState.map(TypesStateHelper.resolveArrowDef(arrowDef)).flatMap {
case Valid(t) => case Valid(TypeResolution(tt, tokens)) =>
val (tt, tokens) = t val tokensLocs = tokens.map { case (t, n) => n.value -> t }
val tokensLocs = tokens.map { case (t, n) => locations.pointLocations(tokensLocs).as(tt.some)
n.value -> t
}
locations.pointLocations(tokensLocs).map(_ => Some(tt))
case Invalid(errs) => case Invalid(errs) =>
errs errs.traverse_ { case TypeResolutionError(token, hint) =>
.foldLeft[ST[Option[ArrowType]]](State.pure(None)) { case (n, (tkn, hint)) => report(token, hint)
report(tkn, hint) >> n }.as(none)
} }
override def resolveServiceType(name: NamedTypeToken[S]): State[X, Option[ServiceType]] =
resolveType(name).flatMap {
case Some(serviceType: ServiceType) =>
serviceType.some.pure
case Some(t) =>
report(name, s"Type `$t` is not a service").as(none)
case None =>
report(name, s"Type `${name.value}` is not defined").as(none)
} }
override def defineAbilityType( override def defineAbilityType(
name: NamedTypeToken[S], name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)] fields: Map[String, (Name[S], Type)]
): State[X, Option[AbilityType]] = ): State[X, Option[AbilityType]] =
getState.map(_.definitions.get(name.value)).flatMap { ensureNameNotDefined(name.value, name, ifDefined = none) {
case Some(_) => report(name, s"Ability `${name.value}` was already defined").as(none)
case None =>
val types = fields.view.mapValues { case (_, t) => t }.toMap val types = fields.view.mapValues { case (_, t) => t }.toMap
NonEmptyMap NonEmptyMap
.fromMap(SortedMap.from(types)) .fromMap(SortedMap.from(types))
.fold(report(name, s"Ability `${name.value}` has no fields").as(none))(nonEmptyFields => .fold(report(name, s"Ability `${name.value}` has no fields").as(none))(nonEmptyFields =>
val `type` = AbilityType(name.value, nonEmptyFields) val `type` = AbilityType(name.value, nonEmptyFields)
modify { st =>
st.copy( modify(_.defineType(name, `type`)).as(`type`.some)
strict = st.strict.updated(name.value, `type`),
definitions = st.definitions.updated(name.value, name)
)
}.as(`type`.some)
) )
} }
override def defineServiceType(
name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)]
): State[X, Option[ServiceType]] =
ensureNameNotDefined(name.value, name, ifDefined = none)(
fields.toList.traverse {
case (field, (fieldName, t: ArrowType)) =>
OptionT
.when(t.codomain.length <= 1)(field -> t)
.flatTapNone(
report(fieldName, "Service functions cannot have multiple results")
)
case (field, (fieldName, t)) =>
OptionT(
report(
fieldName,
s"Field '$field' has unacceptable for service field type '$t'"
).as(none)
)
}.flatMapF(arrows =>
NonEmptyMap
.fromMap(SortedMap.from(arrows))
.fold(
report(name, s"Service `${name.value}` has no fields").as(none)
)(_.some.pure)
).semiflatMap(nonEmptyArrows =>
val `type` = ServiceType(name.value, nonEmptyArrows)
modify(_.defineType(name, `type`)).as(`type`)
).value
)
override def defineStructType( override def defineStructType(
name: NamedTypeToken[S], name: NamedTypeToken[S],
fields: Map[String, (Name[S], Type)] fields: Map[String, (Name[S], Type)]
): State[X, Option[StructType]] = ): State[X, Option[StructType]] =
getState.map(_.definitions.get(name.value)).flatMap { ensureNameNotDefined(name.value, name, ifDefined = none)(
case Some(_) => report(name, s"Data `${name.value}` was already defined").as(none)
case None =>
fields.toList.traverse { fields.toList.traverse {
case (field, (fieldName, t: DataType)) => case (field, (fieldName, t: DataType)) =>
t match { t match {
@ -125,27 +150,20 @@ class TypesInterpreter[S[_], X](implicit
report(name, s"Struct `${name.value}` has no fields").as(none) report(name, s"Struct `${name.value}` has no fields").as(none)
)(nonEmptyFields => )(nonEmptyFields =>
val `type` = StructType(name.value, nonEmptyFields) val `type` = StructType(name.value, nonEmptyFields)
modify { st =>
st.copy( modify(_.defineType(name, `type`)).as(`type`.some)
strict = st.strict.updated(name.value, `type`), )
definitions = st.definitions.updated(name.value, name)
)
}.as(`type`.some)
) )
) )
}
override def defineAlias(name: NamedTypeToken[S], target: Type): State[X, Boolean] = override def defineAlias(name: NamedTypeToken[S], target: Type): State[X, Boolean] =
getState.map(_.definitions.get(name.value)).flatMap { getState.map(_.definitions.get(name.value)).flatMap {
case Some(n) if n == name => State.pure(false) case Some(n) if n == name => State.pure(false)
case Some(_) => report(name, s"Type `${name.value}` was already defined").as(false) case Some(_) => report(name, s"Type `${name.value}` was already defined").as(false)
case None => case None =>
modify(st => modify(_.defineType(name, target))
st.copy( .productL(locations.addToken(name.value, name))
strict = st.strict.updated(name.value, target), .as(true)
definitions = st.definitions.updated(name.value, name)
)
).flatMap(_ => locations.addToken(name.value, name)).as(true)
} }
override def resolveField(rootT: Type, op: IntoField[S]): State[X, Option[PropertyRaw]] = { override def resolveField(rootT: Type, op: IntoField[S]): State[X, Option[PropertyRaw]] = {
@ -465,4 +483,22 @@ class TypesInterpreter[S[_], X](implicit
).as(frame -> Nil) ).as(frame -> Nil)
else (frame -> frame.retVals.getOrElse(Nil)).pure else (frame -> frame.retVals.getOrElse(Nil)).pure
) <* stack.endScope ) <* stack.endScope
private def ensureNameNotDefined[A](
name: String,
token: Token[S],
ifDefined: => A
)(
ifNotDefined: => State[X, A]
): State[X, A] = getState
.map(_.definitions.get(name))
.flatMap {
case Some(_) =>
// TODO: Point to both locations here
report(
token,
s"Name `${name}` was already defined here"
).as(ifDefined)
case None => ifNotDefined
}
} }

View File

@ -9,129 +9,139 @@ import cats.data.Validated.{Invalid, Valid}
import cats.data.{Chain, NonEmptyChain, ValidatedNec} import cats.data.{Chain, NonEmptyChain, ValidatedNec}
import cats.kernel.Monoid import cats.kernel.Monoid
import cats.syntax.option.* import cats.syntax.option.*
import cats.syntax.traverse.*
import cats.syntax.validated.*
import cats.syntax.apply.*
import cats.syntax.bifunctor.*
import cats.syntax.functor.*
import cats.syntax.apply.*
case class TypesState[S[_]]( case class TypesState[S[_]](
fields: Map[String, (Name[S], Type)] = Map.empty[String, (Name[S], Type)], fields: Map[String, (Name[S], Type)] = Map(),
strict: Map[String, Type] = Map.empty[String, Type], strict: Map[String, Type] = Map.empty,
definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]], definitions: Map[String, NamedTypeToken[S]] = Map(),
stack: List[TypesState.Frame[S]] = Nil stack: List[TypesState.Frame[S]] = Nil
) { ) {
def isDefined(t: String): Boolean = strict.contains(t) def isDefined(t: String): Boolean = strict.contains(t)
def defineType(name: NamedTypeToken[S], `type`: Type): TypesState[S] =
copy(
strict = strict.updated(name.value, `type`),
definitions = definitions.updated(name.value, name)
)
def getType(name: String): Option[Type] =
strict.get(name)
def getTypeDefinition(name: String): Option[NamedTypeToken[S]] =
definitions.get(name)
} }
object TypesStateHelper { object TypesStateHelper {
// TODO: an ugly return type, refactoring final case class TypeResolution[S[_], +T](
// Returns type and a token with its definition `type`: T,
def resolveTypeToken[S[_]]( definitions: List[(Token[S], NamedTypeToken[S])]
tt: TypeToken[S], )
state: TypesState[S],
resolver: ( final case class TypeResolutionError[S[_]](
TypesState[S], token: Token[S],
NamedTypeToken[S] hint: String
) => Option[(Type, List[(Token[S], NamedTypeToken[S])])] )
): Option[(Type, List[(Token[S], NamedTypeToken[S])])] =
def resolveTypeToken[S[_]](tt: TypeToken[S])(
state: TypesState[S]
): Option[TypeResolution[S, Type]] =
tt match { tt match {
case TopBottomToken(_, isTop) => case TopBottomToken(_, isTop) =>
(if (isTop) TopType else BottomType, Nil).some val `type` = if (isTop) TopType else BottomType
TypeResolution(`type`, Nil).some
case ArrayTypeToken(_, dtt) => case ArrayTypeToken(_, dtt) =>
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
(ArrayType(it), t) TypeResolution(ArrayType(it), t)
} }
case StreamTypeToken(_, dtt) => case StreamTypeToken(_, dtt) =>
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
(StreamType(it), t) TypeResolution(StreamType(it), t)
} }
case OptionTypeToken(_, dtt) => case OptionTypeToken(_, dtt) =>
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
(OptionType(it), t) TypeResolution(OptionType(it), t)
}
case ctt: NamedTypeToken[S] =>
resolver(state, ctt)
case btt: BasicTypeToken[S] => (btt.value, Nil).some
case ArrowTypeToken(_, args, res) =>
val strictArgs =
args.map(_._2).map(resolveTypeToken(_, state, resolver)).collect {
case Some((dt: DataType, t)) =>
(dt, t)
}
val strictRes =
res.map(resolveTypeToken(_, state, resolver)).collect { case Some((dt: DataType, t)) =>
(dt, t)
}
Option.when(strictRes.length == res.length && strictArgs.length == args.length) {
val (sArgs, argTokens) = strictArgs.unzip
val (sRes, resTokens) = strictRes.unzip
(ArrowType(ProductType(sArgs), ProductType(sRes)), argTokens.flatten ++ resTokens.flatten)
} }
case ntt: NamedTypeToken[S] =>
val defs = state
.getTypeDefinition(ntt.value)
.toList
.map(ntt -> _)
state
.getType(ntt.value)
.map(typ => TypeResolution(typ, defs))
case btt: BasicTypeToken[S] =>
TypeResolution(btt.value, Nil).some
case att: ArrowTypeToken[S] =>
resolveArrowDef(att)(state).toOption
} }
def resolveArrowDef[S[_]]( def resolveArrowDef[S[_]](arrowTypeToken: ArrowTypeToken[S])(
arrowTypeToken: ArrowTypeToken[S], state: TypesState[S]
state: TypesState[S], ): ValidatedNec[TypeResolutionError[S], TypeResolution[S, ArrowType]] = {
resolver: ( val res = arrowTypeToken.res.traverse(typeToken =>
TypesState[S], resolveTypeToken(typeToken)(state)
NamedTypeToken[S] .toValidNec(
) => Option[(Type, List[(Token[S], NamedTypeToken[S])])] TypeResolutionError(
): ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])] = { typeToken,
val resType = arrowTypeToken.res.map(resolveTypeToken(_, state, resolver)) "Can not resolve the result type"
)
NonEmptyChain )
.fromChain(Chain.fromSeq(arrowTypeToken.res.zip(resType).collect { case (dt, None) => )
dt -> "Cannot resolve the result type" val args = arrowTypeToken.args.traverse { case (argName, typeToken) =>
})) resolveTypeToken(typeToken)(state)
.fold[ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])]] { .toValidNec(
val (errs, argTypes) = arrowTypeToken.args.map { (argName, tt) => TypeResolutionError(
resolveTypeToken(tt, state, resolver) typeToken,
.toRight(tt -> s"Type unresolved") "Can not resolve the argument type"
)
)
.map(argName.map(_.value) -> _) .map(argName.map(_.value) -> _)
} }
.foldLeft[
(
Chain[(Token[S], String)],
Chain[(Option[String], (Type, List[(Token[S], NamedTypeToken[S])]))]
)
](
(
Chain.empty,
Chain.empty[(Option[String], (Type, List[(Token[S], NamedTypeToken[S])]))]
)
) {
case ((errs, argTypes), Right(at)) => (errs, argTypes.append(at))
case ((errs, argTypes), Left(e)) => (errs.append(e), argTypes)
}
NonEmptyChain (args, res).mapN { (args, res) =>
.fromChain(errs) val (argsLabeledTypes, argsTokens) =
.fold[ValidatedNec[ args.map { case lbl -> TypeResolution(typ, tkn) =>
(Token[S], String), (lbl, typ) -> tkn
(ArrowType, List[(Token[S], NamedTypeToken[S])]) }.unzip.map(_.flatten)
]]( val (resTypes, resTokens) =
Valid { res.map { case TypeResolution(typ, tkn) =>
val (labels, types) = argTypes.toList.unzip typ -> tkn
val (resTypes, resTokens) = resType.flatten.unzip }.unzip.map(_.flatten)
(
ArrowType( val typ = ArrowType(
ProductType.maybeLabelled(labels.zip(types.map(_._1))), ProductType.maybeLabelled(argsLabeledTypes),
ProductType(resTypes) ProductType(resTypes)
),
types.flatMap(_._2) ++ resTokens.flatten
) )
val defs = (argsTokens ++ resTokens)
TypeResolution(typ, defs)
} }
)(Invalid(_))
}(Invalid(_))
} }
} }
object TypesState { object TypesState {
final case class TypeDefinition[S[_]](
token: NamedTypeToken[S],
`type`: Type
)
case class Frame[S[_]]( case class Frame[S[_]](
token: ArrowTypeToken[S], token: ArrowTypeToken[S],
arrowType: ArrowType, arrowType: ArrowType,
retVals: Option[List[ValueRaw]] retVals: Option[List[ValueRaw]]
) )
implicit def typesStateMonoid[S[_]]: Monoid[TypesState[S]] = new Monoid[TypesState[S]] { given [S[_]]: Monoid[TypesState[S]] with {
override def empty: TypesState[S] = TypesState() override def empty: TypesState[S] = TypesState()
override def combine(x: TypesState[S], y: TypesState[S]): TypesState[S] = override def combine(x: TypesState[S], y: TypesState[S]): TypesState[S] =

View File

@ -15,7 +15,8 @@ import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
class ArrowSemSpec extends AnyFlatSpec with Matchers with EitherValues { class ArrowSemSpec extends AnyFlatSpec with Matchers with EitherValues {
import Utils.*
import Utils.{given, *}
def program(arrowStr: String): Prog[State[CompilerState[cats.Id], *], Raw] = { def program(arrowStr: String): Prog[State[CompilerState[cats.Id], *], Raw] = {
import CompilerState.* import CompilerState.*

View File

@ -29,7 +29,7 @@ import org.scalatest.matchers.should.Matchers
class ClosureSemSpec extends AnyFlatSpec with Matchers { class ClosureSemSpec extends AnyFlatSpec with Matchers {
import Utils.* import Utils.{given, *}
val program: Prog[State[CompilerState[cats.Id], *], Raw] = { val program: Prog[State[CompilerState[cats.Id], *], Raw] = {
import CompilerState.* import CompilerState.*

View File

@ -87,11 +87,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
def testServiceCallStr(str: String) = def testServiceCallStr(str: String) =
CallArrowRawTag CallArrowRawTag
.service( .ability(
serviceId = LiteralRaw.quote("test"), abilityName = "Test",
fnName = "testCallStr", funcName = "testCallStr",
call = Call(LiteralRaw.quote(str) :: Nil, Nil), call = Call(LiteralRaw.quote(str) :: Nil, Nil),
name = "Test",
arrowType = ArrowType( arrowType = ArrowType(
ProductType.labelled(("s" -> ScalarType.string) :: Nil), ProductType.labelled(("s" -> ScalarType.string) :: Nil),
ProductType(ScalarType.string :: Nil) ProductType(ScalarType.string :: Nil)
@ -137,11 +136,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
insideBody(script) { body => insideBody(script) { body =>
val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType)) val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType))
val serviceCall = CallArrowRawTag val serviceCall = CallArrowRawTag
.service( .ability(
serviceId = LiteralRaw.quote("srv1"), abilityName = "A",
fnName = "fn1", funcName = "fn1",
call = emptyCall, call = emptyCall,
name = "A",
arrowType = arrowType arrowType = arrowType
) )
.leaf .leaf

View File

@ -6,45 +6,35 @@ import aqua.parser.lift.Span
import aqua.raw.{Raw, RawContext} import aqua.raw.{Raw, RawContext}
import aqua.semantics.expr.func.ClosureSem import aqua.semantics.expr.func.ClosureSem
import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ReportErrors
import aqua.semantics.rules.abilities.{AbilitiesInterpreter, AbilitiesState} import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
import aqua.semantics.rules.locations.DummyLocationsInterpreter import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
import aqua.semantics.rules.names.{NamesInterpreter, NamesState} import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
import aqua.semantics.rules.types.{TypesInterpreter, TypesState} import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
import aqua.types.* import aqua.types.*
import cats.data.State import cats.data.State
import cats.{~>, Id} import cats.{~>, Id}
import monocle.Lens import monocle.Lens
import monocle.macros.GenLens import monocle.macros.GenLens
import monocle.syntax.all.* import monocle.syntax.all.*
import aqua.semantics.rules.mangler.ManglerAlgebra
import aqua.semantics.rules.mangler.ManglerInterpreter
object Utils { object Utils {
implicit val re: ReportErrors[Id, CompilerState[Id]] = new ReportErrors[Id, CompilerState[Id]] { given ManglerAlgebra[State[CompilerState[Id], *]] =
new ManglerInterpreter[CompilerState[Id]]
override def apply( given LocationsAlgebra[Id, State[CompilerState[Id], *]] =
st: CompilerState[Id],
token: Token[Id],
hints: List[String]
): CompilerState[Id] =
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
}
implicit val locationsInterpreter: DummyLocationsInterpreter[Id, CompilerState[Id]] =
new DummyLocationsInterpreter[Id, CompilerState[Id]]() new DummyLocationsInterpreter[Id, CompilerState[Id]]()
implicit val ns: Lens[CompilerState[Id], NamesState[Id]] = GenLens[CompilerState[Id]](_.names) given NamesAlgebra[Id, State[CompilerState[Id], *]] =
implicit val as: Lens[CompilerState[Id], AbilitiesState[Id]] =
GenLens[CompilerState[Id]](_.abilities)
implicit val ts: Lens[CompilerState[Id], TypesState[Id]] = GenLens[CompilerState[Id]](_.types)
implicit val alg: NamesInterpreter[Id, CompilerState[Id]] =
new NamesInterpreter[Id, CompilerState[Id]] new NamesInterpreter[Id, CompilerState[Id]]
implicit val typesInterpreter: TypesInterpreter[Id, CompilerState[Id]] = given TypesAlgebra[Id, State[CompilerState[Id], *]] =
new TypesInterpreter[Id, CompilerState[Id]] new TypesInterpreter[Id, CompilerState[Id]]
implicit val abilitiesInterpreter: AbilitiesInterpreter[Id, CompilerState[Id]] = given AbilitiesAlgebra[Id, State[CompilerState[Id], *]] =
new AbilitiesInterpreter[Id, CompilerState[Id]] new AbilitiesInterpreter[Id, CompilerState[Id]]
def spanToId: Span.S ~> Id = new (Span.S ~> Id) { def spanToId: Span.S ~> Id = new (Span.S ~> Id) {

View File

@ -1,32 +1,17 @@
package aqua.semantics package aqua.semantics
import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesState import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
import aqua.semantics.rules.abilities.AbilitiesState import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
import aqua.semantics.rules.types.TypesState import aqua.semantics.rules.definitions.{DefinitionsAlgebra, DefinitionsInterpreter}
import aqua.semantics.rules.types.TypesAlgebra import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
import aqua.semantics.rules.abilities.AbilitiesInterpreter import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter}
import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.names.NamesInterpreter
import aqua.semantics.rules.definitions.DefinitionsInterpreter
import aqua.semantics.rules.types.TypesInterpreter
import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.locations.DummyLocationsInterpreter
import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw} import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.types.* import aqua.types.*
import aqua.parser.lexer.{ import aqua.parser.lexer.*
CollectionToken, import aqua.raw.value.*
InfixToken,
LiteralToken,
Name,
PrefixToken,
ValueToken,
VarToken
}
import aqua.raw.value.ApplyUnaryOpRaw
import aqua.parser.lexer.ValueToken.string import aqua.parser.lexer.ValueToken.string
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.flatspec.AnyFlatSpec
@ -50,6 +35,8 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
given LocationsAlgebra[Id, Interpreter] = given LocationsAlgebra[Id, Interpreter] =
new DummyLocationsInterpreter[Id, CompilerState[Id]] new DummyLocationsInterpreter[Id, CompilerState[Id]]
given ManglerAlgebra[Interpreter] =
new ManglerInterpreter[CompilerState[Id]]
given TypesAlgebra[Id, Interpreter] = given TypesAlgebra[Id, Interpreter] =
new TypesInterpreter[Id, CompilerState[Id]] new TypesInterpreter[Id, CompilerState[Id]]
given AbilitiesAlgebra[Id, Interpreter] = given AbilitiesAlgebra[Id, Interpreter] =

View File

@ -243,31 +243,19 @@ case class OptionType(element: Type) extends BoxType {
sealed trait NamedType extends Type { sealed trait NamedType extends Type {
def name: String def name: String
def fields: NonEmptyMap[String, Type] def fields: NonEmptyMap[String, Type]
}
// Struct is an unordered collection of labelled types
// TODO: Make fields type `DataType`
case class StructType(name: String, fields: NonEmptyMap[String, Type])
extends DataType with NamedType {
override def toString: String =
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
}
// Ability is an unordered collection of labelled types and arrows
case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType {
/** /**
* Get all arrows defined in this ability and its sub-abilities. * Get all arrows defined in this type and its sub-abilities.
* Paths to arrows are returned **without** ability name * Paths to arrows are returned **without** type name
* to allow renaming on call site. * to allow renaming on call site.
*/ */
lazy val arrows: Map[String, ArrowType] = { lazy val arrows: Map[String, ArrowType] = {
def getArrowsEval(path: Option[String], ability: AbilityType): Eval[List[(String, ArrowType)]] = def getArrowsEval(path: Option[String], nt: NamedType): Eval[List[(String, ArrowType)]] =
ability.fields.toNel.toList.flatTraverse { nt.fields.toNel.toList.flatTraverse {
case (abName, abType: AbilityType) => // sub-arrows could be in abilities or services
val newPath = path.fold(abName)(AbilityType.fullName(_, abName)) case (innerName, innerType: (ServiceType | AbilityType)) =>
getArrowsEval(newPath.some, abType) val newPath = path.fold(innerName)(AbilityType.fullName(_, innerName))
getArrowsEval(newPath.some, innerType)
case (aName, aType: ArrowType) => case (aName, aType: ArrowType) =>
val newPath = path.fold(aName)(AbilityType.fullName(_, aName)) val newPath = path.fold(aName)(AbilityType.fullName(_, aName))
List(newPath -> aType).pure List(newPath -> aType).pure
@ -278,16 +266,17 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends
} }
/** /**
* Get all abilities defined in this ability and its sub-abilities. * Get all abilities defined in this type and its sub-abilities.
* Paths to abilities are returned **without** ability name * Paths to abilities are returned **without** type name
* to allow renaming on call site. * to allow renaming on call site.
*/ */
lazy val abilities: Map[String, AbilityType] = { lazy val abilities: Map[String, AbilityType] = {
def getAbilitiesEval( def getAbilitiesEval(
path: Option[String], path: Option[String],
ability: AbilityType nt: NamedType
): Eval[List[(String, AbilityType)]] = ): Eval[List[(String, AbilityType)]] =
ability.fields.toNel.toList.flatTraverse { nt.fields.toNel.toList.flatTraverse {
// sub-abilities could be only in abilities
case (abName, abType: AbilityType) => case (abName, abType: AbilityType) =>
val fullName = path.fold(abName)(AbilityType.fullName(_, abName)) val fullName = path.fold(abName)(AbilityType.fullName(_, abName))
getAbilitiesEval(fullName.some, abType).map( getAbilitiesEval(fullName.some, abType).map(
@ -300,16 +289,17 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends
} }
/** /**
* Get all variables defined in this ability and its sub-abilities. * Get all variables defined in this type and its sub-abilities.
* Paths to variables are returned **without** ability name * Paths to variables are returned **without** type name
* to allow renaming on call site. * to allow renaming on call site.
*/ */
lazy val variables: Map[String, DataType] = { lazy val variables: Map[String, DataType] = {
def getVariablesEval( def getVariablesEval(
path: Option[String], path: Option[String],
ability: AbilityType nt: NamedType
): Eval[List[(String, DataType)]] = ): Eval[List[(String, DataType)]] =
ability.fields.toNel.toList.flatTraverse { nt.fields.toNel.toList.flatTraverse {
// sub-variables could be only in abilities
case (abName, abType: AbilityType) => case (abName, abType: AbilityType) =>
val newPath = path.fold(abName)(AbilityType.fullName(_, abName)) val newPath = path.fold(abName)(AbilityType.fullName(_, abName))
getVariablesEval(newPath.some, abType) getVariablesEval(newPath.some, abType)
@ -321,6 +311,25 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends
getVariablesEval(None, this).value.toMap getVariablesEval(None, this).value.toMap
} }
}
// Struct is an unordered collection of labelled types
// TODO: Make fields type `DataType`
case class StructType(name: String, fields: NonEmptyMap[String, Type])
extends DataType with NamedType {
override def toString: String =
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
}
case class ServiceType(name: String, fields: NonEmptyMap[String, ArrowType]) extends NamedType {
override def toString: String =
s"service $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
}
// Ability is an unordered collection of labelled types and arrows
case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType {
override def toString: String = override def toString: String =
s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"

View File

@ -170,7 +170,7 @@ class TypeSpec extends AnyFlatSpec with Matchers {
accepts(four, one) should be(false) accepts(four, one) should be(false)
} }
"labeled types" should "create correc labels" in { "labeled types" should "create correct labels" in {
val cons = LabeledConsType( val cons = LabeledConsType(
"arg1", "arg1",
ArrowType( ArrowType(