mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 14:40:17 +00:00
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:
parent
f8b5017918
commit
6be2a3d5da
@ -10,6 +10,7 @@ import aqua.raw.{RawContext, RawPart}
|
||||
import aqua.res.AquaRes
|
||||
import aqua.semantics.{CompilerState, Semantics}
|
||||
import aqua.semantics.header.{HeaderHandler, HeaderSem, Picker}
|
||||
|
||||
import cats.data.*
|
||||
import cats.data.Validated.{validNec, Invalid, Valid}
|
||||
import cats.parse.Parser0
|
||||
|
@ -153,6 +153,7 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
|
||||
val canonResult = VarModel("-" + results.name + "-fix-0", CanonStreamType(resultsType.element))
|
||||
val flatResult = VarModel("-results-flat-0", ArrayType(ScalarType.string))
|
||||
val initPeer = LiteralModel.fromRaw(ValueRaw.InitPeerId)
|
||||
val retVar = VarModel("ret", ScalarType.string)
|
||||
|
||||
val expected =
|
||||
SeqRes.wrap(
|
||||
@ -173,10 +174,11 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
|
||||
"identity",
|
||||
CallRes(
|
||||
LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil,
|
||||
Some(CallModel.Export(results.name, results.`type`))
|
||||
Some(CallModel.Export(retVar.name, retVar.`type`))
|
||||
),
|
||||
peer
|
||||
).leaf,
|
||||
ApRes(retVar, CallModel.Export(results.name, results.`type`)).leaf,
|
||||
through(ValueModel.fromRaw(relay)),
|
||||
through(initPeer)
|
||||
),
|
||||
|
95
integration-tests/aqua/examples/servicesAsAbilities.aqua
Normal file
95
integration-tests/aqua/examples/servicesAsAbilities.aqua
Normal 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
71
integration-tests/src/examples/servicesAsAbilities.ts
Normal file
71
integration-tests/src/examples/servicesAsAbilities.ts
Normal 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();
|
||||
}
|
@ -5,10 +5,11 @@ import aqua.model.*
|
||||
import aqua.model.inline.state.{Arrows, Exports, Mangler}
|
||||
import aqua.raw.ops.RawTag
|
||||
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.{Chain, IndexedStateT, State}
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.bifunctor.*
|
||||
import cats.syntax.foldable.*
|
||||
@ -123,22 +124,22 @@ object ArrowInliner extends Logging {
|
||||
/**
|
||||
* Get ability fields (vars or arrows) from exports
|
||||
*
|
||||
* @param abilityName ability current name in state
|
||||
* @param abilityNewName ability new name (for renaming)
|
||||
* @param abilityType ability type
|
||||
* @param name ability current name in state
|
||||
* @param newName ability new name (for renaming)
|
||||
* @param type ability type
|
||||
* @param exports exports state to resolve fields
|
||||
* @param fields fields selector
|
||||
* @return resolved ability fields (renamed if necessary)
|
||||
*/
|
||||
private def getAbilityFields[T <: Type](
|
||||
abilityName: String,
|
||||
abilityNewName: Option[String],
|
||||
abilityType: AbilityType,
|
||||
name: String,
|
||||
newName: Option[String],
|
||||
`type`: NamedType,
|
||||
exports: Map[String, ValueModel]
|
||||
)(fields: AbilityType => Map[String, T]): Map[String, ValueModel] =
|
||||
fields(abilityType).flatMap { case (fName, _) =>
|
||||
val fullName = AbilityType.fullName(abilityName, fName)
|
||||
val newFullName = AbilityType.fullName(abilityNewName.getOrElse(abilityName), fName)
|
||||
)(fields: NamedType => Map[String, T]): Map[String, ValueModel] =
|
||||
fields(`type`).flatMap { case (fName, _) =>
|
||||
val fullName = AbilityType.fullName(name, fName)
|
||||
val newFullName = AbilityType.fullName(newName.getOrElse(name), fName)
|
||||
|
||||
Exports
|
||||
.getLastValue(fullName, exports)
|
||||
@ -179,24 +180,24 @@ object ArrowInliner extends Logging {
|
||||
/**
|
||||
* Get ability arrows from arrows
|
||||
*
|
||||
* @param abilityName ability current name in state
|
||||
* @param abilityNewName ability new name (for renaming)
|
||||
* @param abilityType ability type
|
||||
* @param name ability current name in state
|
||||
* @param newName ability new name (for renaming)
|
||||
* @param type ability type
|
||||
* @param exports exports state to resolve fields
|
||||
* @param arrows arrows state to resolve arrows
|
||||
* @return resolved ability arrows (renamed if necessary)
|
||||
*/
|
||||
private def getAbilityArrows(
|
||||
abilityName: String,
|
||||
abilityNewName: Option[String],
|
||||
abilityType: AbilityType,
|
||||
name: String,
|
||||
newName: Option[String],
|
||||
`type`: NamedType,
|
||||
exports: Map[String, ValueModel],
|
||||
arrows: Map[String, FuncArrow]
|
||||
): Map[String, FuncArrow] = {
|
||||
val get = getAbilityFields(
|
||||
abilityName,
|
||||
abilityNewName,
|
||||
abilityType,
|
||||
name,
|
||||
newName,
|
||||
`type`,
|
||||
exports
|
||||
)
|
||||
|
||||
@ -210,12 +211,12 @@ object ArrowInliner extends Logging {
|
||||
}
|
||||
|
||||
private def getAbilityArrows[S: Arrows: Exports](
|
||||
abilityName: String,
|
||||
abilityType: AbilityType
|
||||
name: String,
|
||||
`type`: NamedType
|
||||
): State[S, Map[String, FuncArrow]] = for {
|
||||
exports <- Exports[S].exports
|
||||
arrows <- Arrows[S].arrows
|
||||
} yield getAbilityArrows(abilityName, None, abilityType, exports, arrows)
|
||||
} yield getAbilityArrows(name, None, `type`, exports, arrows)
|
||||
|
||||
final case class Renamed[T](
|
||||
renames: Map[String, String],
|
||||
@ -276,7 +277,22 @@ object ArrowInliner extends Logging {
|
||||
* to avoid collisions, then resolve them in context.
|
||||
*/
|
||||
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.
|
||||
@ -301,7 +317,12 @@ object ArrowInliner extends Logging {
|
||||
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
|
||||
|
||||
tree = fn.body.rename(renaming)
|
||||
@ -322,12 +343,13 @@ object ArrowInliner extends Logging {
|
||||
|
||||
exports <- Exports[S].exports
|
||||
streams <- getOutsideStreamNames
|
||||
arrows = passArrows ++ arrowsFromAbilities
|
||||
|
||||
inlineResult <- Exports[S].scope(
|
||||
Arrows[S].scope(
|
||||
for {
|
||||
// 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)
|
||||
} yield inlineResult
|
||||
)
|
||||
|
@ -413,6 +413,49 @@ object TagInliner extends Logging {
|
||||
} yield TagInlined.Empty(prefix = prefix)
|
||||
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 ParTag.Detach => pure(DetachModel)
|
||||
case _: ParGroupTag => pure(ParModel)
|
||||
|
@ -59,10 +59,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
|
||||
|
||||
private def unfoldAbilityProperty[S: Mangler: Exports: Arrows](
|
||||
varModel: VarModel,
|
||||
abilityType: AbilityType,
|
||||
abilityType: NamedType,
|
||||
p: PropertyRaw
|
||||
): State[S, (VarModel, Inline)] = {
|
||||
p match {
|
||||
): State[S, (VarModel, Inline)] = p match {
|
||||
case IntoArrowRaw(arrowName, t, arguments) =>
|
||||
val arrowType = abilityType.fields
|
||||
.lookup(arrowName)
|
||||
@ -70,17 +69,15 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
|
||||
at
|
||||
}
|
||||
.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)
|
||||
}
|
||||
for {
|
||||
callArrow <- CallArrowRawInliner(
|
||||
CallArrowRaw(
|
||||
None,
|
||||
AbilityType.fullName(varModel.name, arrowName),
|
||||
arguments,
|
||||
arrowType,
|
||||
None
|
||||
CallArrowRaw.func(
|
||||
funcName = AbilityType.fullName(varModel.name, arrowName),
|
||||
baseType = arrowType,
|
||||
arguments = arguments
|
||||
)
|
||||
)
|
||||
result <- callArrow match {
|
||||
@ -91,9 +88,7 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
|
||||
Exports[S].resolved(vm.name, vm).map(_ => (vm, inline))
|
||||
}
|
||||
}
|
||||
} yield {
|
||||
result
|
||||
}
|
||||
} yield result
|
||||
case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) =>
|
||||
(VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure
|
||||
case IntoFieldRaw(fieldName, t) =>
|
||||
@ -114,10 +109,7 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
|
||||
}
|
||||
|
||||
}
|
||||
} yield {
|
||||
result
|
||||
}
|
||||
}
|
||||
} yield result
|
||||
}
|
||||
|
||||
private[inline] def unfoldProperty[S: Mangler: Exports: Arrows](
|
||||
@ -284,9 +276,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
|
||||
case (_, _) =>
|
||||
unfold(raw).flatMap {
|
||||
case (vm: VarModel, prevInline) =>
|
||||
unfoldProperties(prevInline, vm, properties, propertiesAllowed).map { case (v, i) =>
|
||||
v -> i
|
||||
}
|
||||
unfoldProperties(prevInline, vm, properties, propertiesAllowed)
|
||||
// To coerce types
|
||||
.map(identity)
|
||||
case (l: LiteralModel, inline) =>
|
||||
flatLiteralWithProperties(
|
||||
l,
|
||||
|
@ -32,12 +32,8 @@ trait Arrows[S] extends Scoped[S] {
|
||||
for {
|
||||
exps <- Exports[S].exports
|
||||
arrs <- arrows
|
||||
// _ = println(s"Resolved arrow: ${arrow.name}")
|
||||
// _ = println(s"Captured var names: ${arrow.capturedVars}")
|
||||
captuedVars = exps.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)
|
||||
_ <- save(arrow.name, funcArrow)
|
||||
} yield ()
|
||||
|
@ -2,7 +2,8 @@ package aqua.model.inline.state
|
||||
|
||||
import aqua.model.{LiteralModel, ValueModel, VarModel}
|
||||
import aqua.model.ValueModel.Ability
|
||||
import aqua.types.AbilityType
|
||||
import aqua.types.{AbilityType, NamedType}
|
||||
|
||||
import cats.data.{NonEmptyList, State}
|
||||
|
||||
/**
|
||||
@ -122,7 +123,7 @@ trait Exports[S] extends Scoped[S] {
|
||||
}
|
||||
|
||||
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
|
||||
def getLastValue(name: String, state: Map[String, ValueModel]): Option[ValueModel] = {
|
||||
@ -143,7 +144,7 @@ object Exports {
|
||||
private def getAbilityPairs(
|
||||
oldName: String,
|
||||
newName: String,
|
||||
at: AbilityType,
|
||||
at: NamedType,
|
||||
state: Map[String, ValueModel]
|
||||
): NonEmptyList[(String, ValueModel)] = {
|
||||
at.fields.toNel.flatMap {
|
||||
|
@ -6,16 +6,16 @@ import aqua.types.{ArrowType, ProductType, ScalarType}
|
||||
object RawBuilder {
|
||||
|
||||
def add(l: ValueRaw, r: ValueRaw): ValueRaw =
|
||||
CallArrowRaw(
|
||||
ability = Some("math"),
|
||||
name = "add",
|
||||
arguments = List(l, r),
|
||||
CallArrowRaw.service(
|
||||
abilityName = "math",
|
||||
serviceId = LiteralRaw.quote("math"),
|
||||
funcName = "add",
|
||||
baseType = ArrowType(
|
||||
ProductType(List(ScalarType.i64, ScalarType.i64)),
|
||||
ProductType(
|
||||
List(l.`type` `∪` r.`type`)
|
||||
)
|
||||
),
|
||||
serviceId = Some(LiteralRaw.quote("math"))
|
||||
arguments = List(l, r)
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package aqua.raw
|
||||
|
||||
import aqua.raw.arrow.FuncRaw
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.types.{StructType, Type, AbilityType}
|
||||
import aqua.types.{AbilityType, StructType, Type}
|
||||
|
||||
import cats.Monoid
|
||||
import cats.Semigroup
|
||||
@ -61,8 +61,9 @@ case class RawContext(
|
||||
all(_.services)
|
||||
|
||||
lazy val types: Map[String, Type] =
|
||||
collectPartsMap { case t: TypeRaw =>
|
||||
t.`type`
|
||||
collectPartsMap {
|
||||
case t: TypeRaw => t.`type`
|
||||
case s: ServiceRaw => s.`type`
|
||||
}
|
||||
|
||||
lazy val allTypes: Map[String, Type] =
|
||||
|
@ -1,15 +1,14 @@
|
||||
package aqua.raw
|
||||
|
||||
import aqua.types.{ArrowType, StructType}
|
||||
import cats.data.NonEmptyMap
|
||||
import aqua.types.ServiceType
|
||||
import aqua.raw.value.ValueRaw
|
||||
|
||||
case class ServiceRaw(
|
||||
name: String,
|
||||
arrows: NonEmptyMap[String, ArrowType],
|
||||
`type`: ServiceType,
|
||||
defaultId: Option[ValueRaw]
|
||||
) extends RawPart {
|
||||
def rawPartType: StructType = StructType(name, arrows)
|
||||
def rawPartType: ServiceType = `type`
|
||||
|
||||
override def rename(s: String): RawPart = copy(name = s)
|
||||
|
||||
|
@ -4,7 +4,7 @@ import aqua.raw.arrow.FuncRaw
|
||||
import aqua.raw.ops.RawTag.Tree
|
||||
import aqua.raw.value.{CallArrowRaw, ValueRaw}
|
||||
import aqua.tree.{TreeNode, TreeNodeCompanion}
|
||||
import aqua.types.{ArrowType, DataType}
|
||||
import aqua.types.{ArrowType, DataType, ServiceType}
|
||||
|
||||
import cats.Show
|
||||
import cats.data.{Chain, NonEmptyList}
|
||||
@ -20,7 +20,7 @@ sealed trait RawTag extends TreeNode[RawTag] {
|
||||
def restrictsVarNames: Set[String] = Set.empty
|
||||
|
||||
// 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)
|
||||
def usesVarNames: Set[String] = Set.empty
|
||||
@ -208,6 +208,22 @@ case class 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(
|
||||
serviceId: ValueRaw,
|
||||
fnName: String,
|
||||
@ -231,12 +247,10 @@ object CallArrowRawTag {
|
||||
def func(fnName: String, call: Call): CallArrowRawTag =
|
||||
CallArrowRawTag(
|
||||
call.exportTo,
|
||||
CallArrowRaw(
|
||||
None,
|
||||
fnName,
|
||||
call.args,
|
||||
call.arrowType,
|
||||
None
|
||||
CallArrowRaw.func(
|
||||
funcName = fnName,
|
||||
baseType = call.arrowType,
|
||||
arguments = call.args
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -307,15 +321,27 @@ object EmptyTag extends NoExecTag {
|
||||
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,
|
||||
service: String
|
||||
serviceType: ServiceType,
|
||||
name: String
|
||||
) extends NoExecTag {
|
||||
|
||||
override def usesVarNames: Set[String] = value.varNames
|
||||
|
||||
override def exportsVarNames: Set[String] = Set(name)
|
||||
|
||||
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 {
|
||||
|
@ -250,12 +250,14 @@ case class CallArrowRaw(
|
||||
override def map(f: ValueRaw => ValueRaw): ValueRaw =
|
||||
f(
|
||||
copy(
|
||||
arguments = arguments.map(f),
|
||||
serviceId = serviceId.map(f)
|
||||
arguments = arguments.map(_.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 =
|
||||
copy(
|
||||
|
@ -11,7 +11,7 @@ object ServiceRes {
|
||||
def fromModel(sm: ServiceModel): ServiceRes =
|
||||
ServiceRes(
|
||||
name = sm.name,
|
||||
members = sm.arrows.toNel.toList,
|
||||
members = sm.`type`.arrows.toList,
|
||||
defaultId = sm.defaultId.collect {
|
||||
case LiteralModel(value, t) if ScalarType.string.acceptsValueOf(t) =>
|
||||
value
|
||||
|
@ -5,15 +5,19 @@ import aqua.raw.ops.CallArrowRawTag
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.raw.value.CallArrowRaw
|
||||
import aqua.raw.{ConstantRaw, RawContext, RawPart, ServiceRaw, TypeRaw}
|
||||
import aqua.types.{StructType, Type}
|
||||
import aqua.types.{AbilityType, StructType, Type}
|
||||
|
||||
import cats.Monoid
|
||||
import cats.data.NonEmptyMap
|
||||
import cats.data.Chain
|
||||
import cats.kernel.Semigroup
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.foldable.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.bifunctor.*
|
||||
import cats.syntax.monoid.*
|
||||
import cats.syntax.option.*
|
||||
import scribe.Logging
|
||||
|
||||
import scala.collection.immutable.SortedMap
|
||||
|
||||
case class AquaContext(
|
||||
@ -26,31 +30,76 @@ case class AquaContext(
|
||||
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
|
||||
private def all[T](what: AquaContext => Map[String, T], prefix: String = ""): Map[String, T] =
|
||||
abilities
|
||||
.foldLeft(what(this)) { case (ts, (k, v)) =>
|
||||
ts ++ v.all(what, k + ".")
|
||||
private def all[T](
|
||||
what: AquaContext => Map[String, T],
|
||||
prefix: String = ""
|
||||
): 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] =
|
||||
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] =
|
||||
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](
|
||||
name: String,
|
||||
newName: String,
|
||||
ctx: Map[String, T],
|
||||
add: (AquaContext, Map[String, T]) => AquaContext
|
||||
): AquaContext = {
|
||||
ctx.get(name).fold(AquaContext.blank)(t => add(AquaContext.blank, Map(newName -> t)))
|
||||
}
|
||||
): AquaContext = ctx
|
||||
.get(name)
|
||||
.fold(AquaContext.blank)(t =>
|
||||
add(
|
||||
AquaContext.blank,
|
||||
Map(newName -> t)
|
||||
)
|
||||
)
|
||||
|
||||
def pick(name: String, maybeRename: Option[String]): AquaContext = {
|
||||
val newName = maybeRename.getOrElse(name)
|
||||
@ -68,7 +117,7 @@ object AquaContext extends Logging {
|
||||
lazy val size: Long = data.size
|
||||
|
||||
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))
|
||||
}
|
||||
@ -91,11 +140,10 @@ object AquaContext extends Logging {
|
||||
x.services ++ y.services
|
||||
)
|
||||
|
||||
//
|
||||
def fromService(sm: ServiceRaw, serviceId: ValueRaw): AquaContext =
|
||||
blank.copy(
|
||||
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)
|
||||
fnName ->
|
||||
FuncArrow(
|
||||
@ -192,13 +240,16 @@ object AquaContext extends Logging {
|
||||
logger.trace("Adding service " + m.name)
|
||||
val (pctx, pcache) = fromRawContext(partContext, ctxCache)
|
||||
logger.trace("Got " + m.name + " from raw")
|
||||
val id = m.defaultId.map(ValueModel.fromRaw).map(_.resolveWith(pctx.allValues))
|
||||
val srv = ServiceModel(m.name, m.arrows, id)
|
||||
val id = m.defaultId
|
||||
.map(ValueModel.fromRaw)
|
||||
.map(_.resolveWith(pctx.allValues))
|
||||
val srv = ServiceModel(m.name, m.`type`, id)
|
||||
val add =
|
||||
blank
|
||||
.copy(
|
||||
abilities =
|
||||
m.defaultId.fold(Map.empty)(id => Map(m.name -> fromService(m, id))),
|
||||
abilities = m.defaultId
|
||||
.map(id => Map(m.name -> fromService(m, id)))
|
||||
.orEmpty,
|
||||
services = Map(m.name -> srv)
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.model.{ValueModel, VarModel}
|
||||
import aqua.model.ValueModel.Ability
|
||||
import aqua.raw.ops.Call
|
||||
import aqua.raw.value.{ValueRaw, VarRaw}
|
||||
import aqua.types.*
|
||||
@ -39,11 +40,11 @@ case class ArgsCall(args: ProductType, callWith: List[ValueModel]) {
|
||||
}.toMap
|
||||
|
||||
/**
|
||||
* Ability arguments as mapping
|
||||
* Name of argument -> (variable passed in the call, ability type)
|
||||
* Ability and service arguments as mapping
|
||||
* Name of argument -> (variable passed in the call, type)
|
||||
*/
|
||||
lazy val abilityArgs: Map[String, (VarModel, AbilityType)] =
|
||||
zipped.collect { case ((name, _), vr @ VarModel(_, t @ AbilityType(_, _), _)) =>
|
||||
lazy val abilityArgs: Map[String, (VarModel, NamedType)] =
|
||||
zipped.collect { case ((name, _), vr @ Ability(_, t, _)) =>
|
||||
name -> (vr, t)
|
||||
}.toMap
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
package aqua.model
|
||||
|
||||
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
|
||||
case class CallModel(args: List[ValueModel], exportTo: List[CallModel.Export]) {
|
||||
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
|
||||
}.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)
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ package aqua.model
|
||||
|
||||
import aqua.raw.Raw
|
||||
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.types.{ArrowType, Type}
|
||||
import aqua.types.{ArrowType, ServiceType, Type}
|
||||
|
||||
case class FuncArrow(
|
||||
funcName: String,
|
||||
@ -42,4 +42,49 @@ object FuncArrow {
|
||||
constants,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
package aqua.model
|
||||
|
||||
import aqua.types.ArrowType
|
||||
import cats.data.NonEmptyMap
|
||||
import aqua.types.ServiceType
|
||||
|
||||
case class ServiceModel(
|
||||
name: String,
|
||||
arrows: NonEmptyMap[String, ArrowType],
|
||||
`type`: ServiceType,
|
||||
defaultId: Option[ValueModel]
|
||||
)
|
||||
|
@ -51,11 +51,21 @@ object ValueModel {
|
||||
case _ => ???
|
||||
}
|
||||
|
||||
object Arrow {
|
||||
|
||||
def unapply(vm: VarModel): Option[(String, ArrowType)] =
|
||||
vm match {
|
||||
case VarModel(name, t: ArrowType, _) =>
|
||||
(name, t).some
|
||||
case _ => none
|
||||
}
|
||||
}
|
||||
|
||||
object Ability {
|
||||
|
||||
def unapply(vm: VarModel): Option[(String, AbilityType, Chain[PropertyModel])] =
|
||||
def unapply(vm: VarModel): Option[(String, NamedType, Chain[PropertyModel])] =
|
||||
vm match {
|
||||
case VarModel(name, t@AbilityType(_, _), properties) =>
|
||||
case VarModel(name, t: (AbilityType | ServiceType), properties) =>
|
||||
(name, t, properties).some
|
||||
case _ => none
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ case class ArrowExpr[F[_]](arrowTypeExpr: ArrowTypeToken[F])
|
||||
object ArrowExpr extends Expr.AndIndented {
|
||||
|
||||
val funcChildren: List[Expr.Lexem] =
|
||||
AbilityIdExpr ::
|
||||
ServiceIdExpr ::
|
||||
PushToStreamExpr ::
|
||||
ForExpr ::
|
||||
Expr.defer(OnExpr) ::
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.parser.expr.func
|
||||
|
||||
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.{NamedTypeToken, ValueToken}
|
||||
import aqua.parser.lift.LiftParser
|
||||
@ -10,19 +10,19 @@ import cats.{~>, Comonad}
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
|
||||
case class AbilityIdExpr[F[_]](ability: NamedTypeToken[F], id: ValueToken[F])
|
||||
extends Expr[F](AbilityIdExpr, ability) {
|
||||
case class ServiceIdExpr[F[_]](service: NamedTypeToken[F], id: ValueToken[F])
|
||||
extends Expr[F](ServiceIdExpr, service) {
|
||||
|
||||
def mapK[K[_]: Comonad](fk: F ~> K): AbilityIdExpr[K] =
|
||||
copy(ability.copy(fk(ability.name)), id.mapK(fk))
|
||||
def mapK[K[_]: Comonad](fk: F ~> K): ServiceIdExpr[K] =
|
||||
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) =>
|
||||
AbilityIdExpr(ability, id)
|
||||
ServiceIdExpr(ability, id)
|
||||
}
|
||||
|
||||
}
|
@ -118,8 +118,8 @@ trait AquaSpec extends EitherValues {
|
||||
def parseUse(str: String): UseFromExpr[Id] =
|
||||
UseFromExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
|
||||
def parseAbId(str: String): AbilityIdExpr[Id] =
|
||||
AbilityIdExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
def parseServiceId(str: String): ServiceIdExpr[Id] =
|
||||
ServiceIdExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
|
||||
def parseOn(str: String): OnExpr[Id] =
|
||||
OnExpr.p.parseAll(str).value.mapK(spanToId)
|
||||
|
@ -1,9 +1,10 @@
|
||||
package aqua.parser
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.AbilityIdExpr
|
||||
import aqua.parser.expr.func.ServiceIdExpr
|
||||
import aqua.parser.lexer.LiteralToken
|
||||
import aqua.types.LiteralType
|
||||
|
||||
import cats.Id
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
@ -12,20 +13,20 @@ class AbilityIdExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
import AquaSpec._
|
||||
|
||||
"abilities" should "be parsed" in {
|
||||
parseAbId("Ab a") should be(
|
||||
AbilityIdExpr[Id](toNamedType("Ab"), toVar("a"))
|
||||
parseServiceId("Ab a") should be(
|
||||
ServiceIdExpr[Id](toNamedType("Ab"), toVar("a"))
|
||||
)
|
||||
|
||||
parseAbId("Ab \"a\"") should be(
|
||||
AbilityIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string))
|
||||
parseServiceId("Ab \"a\"") should be(
|
||||
ServiceIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string))
|
||||
)
|
||||
|
||||
parseAbId("Ab 1") should be(
|
||||
AbilityIdExpr[Id](toNamedType("Ab"), toNumber(1))
|
||||
parseServiceId("Ab 1") should be(
|
||||
ServiceIdExpr[Id](toNamedType("Ab"), toNumber(1))
|
||||
)
|
||||
|
||||
parseAbId("Ab a.id") should be(
|
||||
AbilityIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id")))
|
||||
parseServiceId("Ab a.id") should be(
|
||||
ServiceIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id")))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,13 @@ package aqua.parser
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.*
|
||||
import aqua.parser.expr.func.{AbilityIdExpr, ArrowExpr, CallArrowExpr, IfExpr, OnExpr, ReturnExpr}
|
||||
import aqua.parser.lexer.{
|
||||
ArrowTypeToken,
|
||||
BasicTypeToken,
|
||||
CallArrowToken,
|
||||
LiteralToken,
|
||||
Token,
|
||||
VarToken
|
||||
}
|
||||
import aqua.parser.expr.func.*
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
import aqua.types.ScalarType.*
|
||||
|
||||
import cats.Id
|
||||
import cats.data.{Chain, NonEmptyList}
|
||||
import cats.data.Chain.*
|
||||
@ -27,11 +23,6 @@ import cats.Eval
|
||||
|
||||
import scala.collection.mutable
|
||||
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 {
|
||||
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(
|
||||
CallArrowExpr(Nil, CallArrowToken("call", List(toBool(true))))
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.parser.head
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.AbilityIdExpr
|
||||
import aqua.parser.expr.func.ServiceIdExpr
|
||||
import aqua.parser.lexer.{LiteralToken, Token}
|
||||
import aqua.parser.lift.LiftParser.Implicits.*
|
||||
import aqua.types.LiteralType
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.parser.head
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.AbilityIdExpr
|
||||
import aqua.parser.expr.func.ServiceIdExpr
|
||||
import aqua.parser.lexer.{LiteralToken, Token}
|
||||
import aqua.parser.lift.LiftParser.Implicits.*
|
||||
import aqua.types.LiteralType
|
||||
@ -23,8 +23,7 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
)
|
||||
)
|
||||
|
||||
HeadExpr
|
||||
.ast
|
||||
HeadExpr.ast
|
||||
.parseAll(s"""import MyModule, func as fn from "file.aqua"
|
||||
|""".stripMargin)
|
||||
.value
|
||||
@ -32,7 +31,8 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
.value
|
||||
.headOption
|
||||
.get
|
||||
.head.mapK(spanToId) should be(
|
||||
.head
|
||||
.mapK(spanToId) should be(
|
||||
ImportFromExpr(
|
||||
NonEmptyList.fromListUnsafe(
|
||||
Right(toAb("MyModule") -> None) :: Left(toName("func") -> Some(toName("fn"))) :: Nil
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.parser.head
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.AbilityIdExpr
|
||||
import aqua.parser.expr.func.ServiceIdExpr
|
||||
import aqua.parser.lexer.{LiteralToken, Token}
|
||||
import aqua.types.LiteralType
|
||||
import cats.Id
|
||||
@ -22,12 +22,12 @@ class ModuleSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
)
|
||||
)
|
||||
|
||||
HeadExpr
|
||||
.ast
|
||||
HeadExpr.ast
|
||||
.parseAll(s"""module MyModule declares *
|
||||
|""".stripMargin)
|
||||
.value
|
||||
.head.mapK(spanToId) should be(
|
||||
.head
|
||||
.mapK(spanToId) should be(
|
||||
ModuleExpr(
|
||||
toAb("MyModule"),
|
||||
Some(Token.lift[Id, Unit](())),
|
||||
|
@ -1,7 +1,7 @@
|
||||
package aqua.parser.head
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.AbilityIdExpr
|
||||
import aqua.parser.expr.func.ServiceIdExpr
|
||||
import aqua.parser.lexer.{LiteralToken, Token}
|
||||
import aqua.parser.lift.LiftParser.Implicits.*
|
||||
import aqua.types.LiteralType
|
||||
|
@ -8,6 +8,7 @@ import aqua.semantics.rules.definitions.DefinitionsState
|
||||
import aqua.semantics.rules.locations.LocationsState
|
||||
import aqua.semantics.rules.names.NamesState
|
||||
import aqua.semantics.rules.types.TypesState
|
||||
import aqua.semantics.rules.mangler.ManglerState
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
|
||||
import cats.Semigroup
|
||||
@ -19,6 +20,7 @@ import monocle.macros.GenLens
|
||||
|
||||
case class CompilerState[S[_]](
|
||||
errors: Chain[SemanticError[S]] = Chain.empty[SemanticError[S]],
|
||||
mangler: ManglerState = ManglerState(),
|
||||
names: NamesState[S] = NamesState[S](),
|
||||
abilities: AbilitiesState[S] = AbilitiesState[S](),
|
||||
types: TypesState[S] = TypesState[S](),
|
||||
@ -42,6 +44,9 @@ object CompilerState {
|
||||
given [S[_]]: Lens[CompilerState[S], AbilitiesState[S]] =
|
||||
GenLens[CompilerState[S]](_.abilities)
|
||||
|
||||
given [S[_]]: Lens[CompilerState[S], ManglerState] =
|
||||
GenLens[CompilerState[S]](_.mangler)
|
||||
|
||||
given [S[_]]: Lens[CompilerState[S], TypesState[S]] =
|
||||
GenLens[CompilerState[S]](_.types)
|
||||
|
||||
@ -60,7 +65,7 @@ object CompilerState {
|
||||
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 combine(x: St[S], y: St[S]): St[S] = for {
|
||||
@ -69,6 +74,7 @@ object CompilerState {
|
||||
_ <- State.set(
|
||||
CompilerState[S](
|
||||
a.errors ++ b.errors,
|
||||
a.mangler |+| b.mangler,
|
||||
a.names |+| b.names,
|
||||
a.abilities |+| b.abilities,
|
||||
a.types |+| b.types,
|
||||
|
@ -27,7 +27,7 @@ object ExprSem {
|
||||
L: LocationsAlgebra[S, G]
|
||||
): Prog[G, Raw] =
|
||||
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: PushToStreamExpr[S] => new PushToStreamSem(expr).program[G]
|
||||
case expr: AliasExpr[S] => new AliasSem(expr).program[G]
|
||||
|
@ -8,15 +8,11 @@ import aqua.raw.{Raw, RawContext, RawPart}
|
||||
import aqua.semantics.header.Picker
|
||||
import aqua.semantics.header.Picker.*
|
||||
import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
|
||||
import aqua.semantics.rules.definitions.{
|
||||
DefinitionsAlgebra,
|
||||
DefinitionsInterpreter,
|
||||
DefinitionsState
|
||||
}
|
||||
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.definitions.{DefinitionsAlgebra, DefinitionsInterpreter}
|
||||
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
|
||||
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter}
|
||||
import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter}
|
||||
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter}
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
import aqua.semantics.rules.errors.ErrorsAlgebra
|
||||
import aqua.raw.ops.*
|
||||
@ -306,17 +302,19 @@ object RawSemantics extends Logging {
|
||||
|
||||
type Interpreter[S[_], A] = State[CompilerState[S], A]
|
||||
|
||||
def transpile[S[_]](
|
||||
ast: Ast[S]
|
||||
)(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = {
|
||||
def transpile[S[_]](ast: Ast[S])(using
|
||||
LocationsAlgebra[S, Interpreter[S, *]]
|
||||
): Interpreter[S, Raw] = {
|
||||
|
||||
implicit val typesInterpreter: TypesInterpreter[S, CompilerState[S]] =
|
||||
given TypesAlgebra[S, Interpreter[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]]
|
||||
implicit val namesInterpreter: NamesInterpreter[S, CompilerState[S]] =
|
||||
given NamesAlgebra[S, Interpreter[S, *]] =
|
||||
new NamesInterpreter[S, CompilerState[S]]
|
||||
implicit val definitionsInterpreter: DefinitionsInterpreter[S, CompilerState[S]] =
|
||||
given DefinitionsAlgebra[S, Interpreter[S, *]] =
|
||||
new DefinitionsInterpreter[S, CompilerState[S]]
|
||||
|
||||
ast
|
||||
|
@ -1,9 +1,8 @@
|
||||
package aqua.semantics.expr
|
||||
|
||||
import aqua.parser.expr.AbilityExpr
|
||||
import aqua.raw.{Raw, ServiceRaw, TypeRaw}
|
||||
import aqua.raw.{Raw, TypeRaw}
|
||||
import aqua.parser.lexer.{Name, NamedTypeToken}
|
||||
import aqua.raw.{Raw, ServiceRaw}
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
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.types.TypesAlgebra
|
||||
import aqua.types.{AbilityType, ArrowType, Type}
|
||||
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
|
@ -8,49 +8,72 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
import aqua.semantics.rules.definitions.DefinitionsAlgebra
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
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.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.foldable.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.Monad
|
||||
|
||||
class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal {
|
||||
|
||||
def program[Alg[_]: Monad](implicit
|
||||
private def define[Alg[_]: Monad](using
|
||||
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(
|
||||
_ =>
|
||||
D.purgeArrows(expr.name).flatMap {
|
||||
case Some(nel) =>
|
||||
val arrows = nel.map(kv => kv._1.value -> (kv._1, kv._2)).toNem
|
||||
for {
|
||||
defaultId <- expr.id
|
||||
.map(v => V.valueToRaw(v))
|
||||
.getOrElse(None.pure[Alg])
|
||||
defineResult <- A.defineService(
|
||||
): EitherT[Alg, Raw, ServiceRaw] = for {
|
||||
arrows <- EitherT.fromOptionF(
|
||||
// TODO: Move to purgeDefs here, allow not only arrows
|
||||
// from parsing, throw errors here
|
||||
D.purgeArrows(expr.name),
|
||||
Raw.error("Service has no arrows")
|
||||
)
|
||||
arrowsByName = arrows.map { case (name, arrow) =>
|
||||
name.value -> (name, arrow)
|
||||
}.toNem
|
||||
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,
|
||||
arrows,
|
||||
arrowsDefs,
|
||||
defaultId
|
||||
).map(defined =>
|
||||
Raw
|
||||
.error("Service not created due to validation errors")
|
||||
.asLeft
|
||||
.whenA(!defined)
|
||||
)
|
||||
)
|
||||
} yield ServiceRaw(
|
||||
expr.name.value,
|
||||
serviceType,
|
||||
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 =>
|
||||
Raw.error("Service has no arrows, fails").pure[Alg]
|
||||
|
||||
}
|
||||
def program[Alg[_]: Monad](using
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
}
|
@ -13,11 +13,13 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.{ArrayType, ArrowType, CanonStreamType, ProductType, StreamType, Type}
|
||||
import aqua.types.*
|
||||
|
||||
import cats.Eval
|
||||
import cats.data.{Chain, NonEmptyList}
|
||||
import cats.free.{Cofree, Free}
|
||||
import cats.data.OptionT
|
||||
import cats.syntax.show.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.foldable.*
|
||||
@ -64,16 +66,15 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal {
|
||||
res = bodyGen match {
|
||||
case FuncOp(bodyModel) =>
|
||||
// TODO: wrap with local on...via...
|
||||
|
||||
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 {
|
||||
case (VarRaw(n, StreamType(_)), StreamType(_)) => n
|
||||
}.toSet
|
||||
|
||||
// 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
|
||||
val (bodyRets, retVals) = retsAndArgs.mapWithIndex {
|
||||
|
@ -10,7 +10,9 @@ import aqua.semantics.rules.ValuesAlgebra
|
||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.{BoxType, OptionType, ScalarType}
|
||||
|
||||
import cats.data.Chain
|
||||
import cats.data.OptionT
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.flatMap.*
|
||||
@ -49,24 +51,25 @@ class OnSem[S[_]](val expr: OnExpr[S]) extends AnyVal {
|
||||
|
||||
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],
|
||||
T: TypesAlgebra[S, Alg],
|
||||
A: AbilitiesAlgebra[S, Alg]
|
||||
): Alg[List[ValueRaw]] =
|
||||
// TODO: Remove ensureIsString, use valueToStringRaw
|
||||
V.ensureIsString(peerId) *> via
|
||||
.traverse(v =>
|
||||
V.valueToRaw(v).flatTap {
|
||||
case Some(vm) =>
|
||||
vm.`type` match {
|
||||
case _: BoxType =>
|
||||
T.ensureTypeMatches(v, OptionType(ScalarType.string), vm.`type`)
|
||||
case _ =>
|
||||
T.ensureTypeMatches(v, ScalarType.string, vm.`type`)
|
||||
OptionT(V.valueToRaw(v)).filterF { vm =>
|
||||
val expectedType = vm.`type` match {
|
||||
case _: BoxType => OptionType(ScalarType.string)
|
||||
case _ => ScalarType.string
|
||||
}
|
||||
case None => false.pure[Alg]
|
||||
|
||||
T.ensureTypeMatches(v, expectedType, vm.`type`)
|
||||
}
|
||||
)
|
||||
.map(_.flatten)
|
||||
|
||||
.getOrElse(List.empty)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -11,6 +11,7 @@ import aqua.semantics.rules.errors.ErrorsAlgebra
|
||||
import aqua.types.*
|
||||
|
||||
import cats.Monad
|
||||
import cats.data.OptionT
|
||||
import cats.data.Chain
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.apply.*
|
||||
@ -33,23 +34,6 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
A: AbilitiesAlgebra[S, Alg]
|
||||
) 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]] =
|
||||
op match {
|
||||
case op: IntoField[S] =>
|
||||
@ -83,11 +67,20 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
LiteralRaw(l.value, t).some.pure[Alg]
|
||||
|
||||
case VarToken(name) =>
|
||||
N.read(name).flatMap {
|
||||
N.read(name, mustBeDefined = false).flatMap {
|
||||
case Some(t) =>
|
||||
VarRaw(name.value, t).some.pure[Alg]
|
||||
VarRaw(name.value, t).some.pure
|
||||
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) =>
|
||||
@ -107,35 +100,30 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
)(valueToRaw)
|
||||
|
||||
case dvt @ NamedValueToken(typeName, fields) =>
|
||||
T.resolveType(typeName).flatMap {
|
||||
case Some(resolvedType) =>
|
||||
for {
|
||||
fieldsRawOp: NonEmptyMap[String, Option[ValueRaw]] <- fields.traverse(valueToRaw)
|
||||
fieldsRaw: List[(String, ValueRaw)] = fieldsRawOp.toSortedMap.toList.collect {
|
||||
case (n, Some(vr)) => n -> vr
|
||||
}
|
||||
rawFields = NonEmptyMap.fromMap(SortedMap.from(fieldsRaw))
|
||||
typeFromFieldsWithData = rawFields
|
||||
.map(rf =>
|
||||
(for {
|
||||
resolvedType <- OptionT(T.resolveType(typeName))
|
||||
fieldsGiven <- fields.traverse(value => OptionT(valueToRaw(value)))
|
||||
fieldsGivenTypes = fieldsGiven.map(_.`type`)
|
||||
generated <- OptionT.fromOption(
|
||||
resolvedType match {
|
||||
case struct @ StructType(_, _) =>
|
||||
case struct: StructType =>
|
||||
(
|
||||
StructType(typeName.value, rf.map(_.`type`)),
|
||||
Some(MakeStructRaw(rf, struct))
|
||||
)
|
||||
case scope @ AbilityType(_, _) =>
|
||||
struct.copy(fields = fieldsGivenTypes),
|
||||
MakeStructRaw(fieldsGiven, struct)
|
||||
).some
|
||||
case ability: AbilityType =>
|
||||
(
|
||||
AbilityType(typeName.value, rf.map(_.`type`)),
|
||||
Some(AbilityRaw(rf, scope))
|
||||
)
|
||||
ability.copy(fields = fieldsGivenTypes),
|
||||
AbilityRaw(fieldsGiven, ability)
|
||||
).some
|
||||
case _ => none
|
||||
}
|
||||
)
|
||||
.getOrElse(BottomType -> None)
|
||||
(typeFromFields, data) = typeFromFieldsWithData
|
||||
isTypesCompatible <- T.ensureTypeMatches(dvt, resolvedType, typeFromFields)
|
||||
} yield data.filter(_ => isTypesCompatible)
|
||||
case _ => None.pure[Alg]
|
||||
}
|
||||
(genType, genData) = generated
|
||||
data <- OptionT.whenM(
|
||||
T.ensureTypeMatches(dvt, resolvedType, genType)
|
||||
)(genData.pure)
|
||||
} yield data).value
|
||||
|
||||
case ct @ CollectionToken(_, values) =>
|
||||
for {
|
||||
@ -295,15 +283,15 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
} yield Option.when(
|
||||
leftChecked.isDefined && rightChecked.isDefined
|
||||
)(
|
||||
CallArrowRaw(
|
||||
ability = Some(id),
|
||||
name = fn,
|
||||
arguments = leftRaw :: rightRaw :: Nil,
|
||||
CallArrowRaw.service(
|
||||
abilityName = id,
|
||||
serviceId = LiteralRaw.quote(id),
|
||||
funcName = fn,
|
||||
baseType = ArrowType(
|
||||
ProductType(lType :: rType :: 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(
|
||||
ab: Name[S],
|
||||
at: AbilityType,
|
||||
at: NamedType,
|
||||
funcName: Name[S]
|
||||
): Option[CallArrowRaw] = at.arrows
|
||||
.get(funcName.value)
|
||||
@ -351,24 +354,23 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
|
||||
)
|
||||
)(ab =>
|
||||
N.read(ab.asName, mustBeDefined = false).flatMap {
|
||||
case Some(at: AbilityType) =>
|
||||
callArrowFromAbility(ab.asName, at, callArrow.funcName).pure
|
||||
case Some(nt: (AbilityType | ServiceType)) =>
|
||||
callArrowFromAbility(ab.asName, nt, callArrow.funcName).pure
|
||||
case _ =>
|
||||
T.getType(ab.value).flatMap {
|
||||
case Some(at: AbilityType) =>
|
||||
callArrowFromAbility(ab.asName, at, callArrow.funcName).pure
|
||||
case _ =>
|
||||
(A.getArrow(ab, callArrow.funcName), A.getServiceId(ab)).mapN {
|
||||
case (Some(at), Right(sid)) =>
|
||||
CallArrowRaw
|
||||
.service(
|
||||
abilityName = ab.value,
|
||||
serviceId = sid,
|
||||
funcName = callArrow.funcName.value,
|
||||
baseType = at
|
||||
case Some(st: ServiceType) =>
|
||||
OptionT(A.getServiceRename(ab))
|
||||
.subflatMap(rename =>
|
||||
callArrowFromAbility(
|
||||
ab.asName.rename(rename),
|
||||
st,
|
||||
callArrow.funcName
|
||||
)
|
||||
.some
|
||||
case (Some(at), Left(true)) =>
|
||||
)
|
||||
.value
|
||||
case _ =>
|
||||
A.getArrow(ab, callArrow.funcName).map {
|
||||
case Some(at) =>
|
||||
CallArrowRaw
|
||||
.ability(
|
||||
abilityName = ab.value,
|
||||
|
@ -2,7 +2,8 @@ package aqua.semantics.rules.abilities
|
||||
|
||||
import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken}
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.types.ArrowType
|
||||
import aqua.types.{ArrowType, ServiceType}
|
||||
|
||||
import cats.InjectK
|
||||
import cats.data.{NonEmptyList, NonEmptyMap}
|
||||
|
||||
@ -10,15 +11,15 @@ trait AbilitiesAlgebra[S[_], Alg[_]] {
|
||||
|
||||
def defineService(
|
||||
name: NamedTypeToken[S],
|
||||
arrows: NonEmptyMap[String, (Name[S], ArrowType)],
|
||||
arrowDefs: NonEmptyMap[String, Name[S]],
|
||||
defaultId: Option[ValueRaw]
|
||||
): Alg[Boolean]
|
||||
|
||||
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]
|
||||
|
||||
|
@ -5,27 +5,31 @@ import aqua.raw.value.ValueRaw
|
||||
import aqua.raw.{RawContext, ServiceRaw}
|
||||
import aqua.semantics.Levenshtein
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
import aqua.semantics.rules.mangler.ManglerAlgebra
|
||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.{abilities, StackInterpreter}
|
||||
import aqua.types.ArrowType
|
||||
import aqua.types.{ArrowType, ServiceType}
|
||||
|
||||
import cats.data.{NonEmptyMap, State}
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.foldable.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.option.*
|
||||
import monocle.Lens
|
||||
import monocle.macros.GenLens
|
||||
|
||||
class AbilitiesInterpreter[S[_], X](implicit
|
||||
class AbilitiesInterpreter[S[_], X](using
|
||||
lens: Lens[X, AbilitiesState[S]],
|
||||
error: ReportErrors[S, X],
|
||||
mangler: ManglerAlgebra[State[X, *]],
|
||||
locations: LocationsAlgebra[S, State[X, *]]
|
||||
) extends AbilitiesAlgebra[S, State[X, *]] {
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
@ -33,11 +37,11 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
|
||||
override def defineService(
|
||||
name: NamedTypeToken[S],
|
||||
arrows: NonEmptyMap[String, (Name[S], ArrowType)],
|
||||
arrowDefs: NonEmptyMap[String, Name[S]],
|
||||
defaultId: Option[ValueRaw]
|
||||
): SX[Boolean] =
|
||||
getService(name.value).flatMap {
|
||||
case Some(_) =>
|
||||
serviceExists(name.value).flatMap {
|
||||
case true =>
|
||||
getState
|
||||
.map(_.definitions.get(name.value).exists(_ == name))
|
||||
.flatMap(exists =>
|
||||
@ -47,23 +51,14 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
).whenA(!exists)
|
||||
)
|
||||
.as(false)
|
||||
case None =>
|
||||
case false =>
|
||||
for {
|
||||
_ <- arrows.toNel.traverse_ { case (_, (n, arr)) =>
|
||||
report(n, "Service functions cannot have multiple results")
|
||||
.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)
|
||||
)
|
||||
)
|
||||
_ <- modify(_.defineService(name, defaultId))
|
||||
// TODO: Is it used?
|
||||
_ <- locations.addTokenWithFields(
|
||||
name.value,
|
||||
name,
|
||||
arrows.toNel.toList.map(t => t._1 -> t._2._1)
|
||||
arrowDefs.toNel.toList
|
||||
)
|
||||
} yield true
|
||||
}
|
||||
@ -74,20 +69,6 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
}
|
||||
|
||||
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 {
|
||||
case Some(abCtx) =>
|
||||
abCtx.funcs
|
||||
@ -100,57 +81,38 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
arrow.value,
|
||||
abCtx.funcs.keys.toList
|
||||
)
|
||||
).as(Option.empty[ArrowType])
|
||||
).as(none)
|
||||
) { fn =>
|
||||
// TODO: add name and arrow separately
|
||||
// TODO: find tokens somewhere
|
||||
addServiceArrowLocation(name, arrow).as(Some(fn.arrow.`type`))
|
||||
addServiceArrowLocation(name, arrow).as(fn.arrow.`type`.some)
|
||||
}
|
||||
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] =
|
||||
getService(name.value).flatMap {
|
||||
case Some(_) =>
|
||||
override def renameService(name: NamedTypeToken[S]): SX[Option[String]] =
|
||||
serviceExists(name.value).flatMap {
|
||||
case true =>
|
||||
mapStackHeadM(
|
||||
modify(st => st.copy(rootServiceIds = st.rootServiceIds.updated(name.value, id -> vm)))
|
||||
.as(true)
|
||||
)(h => (h.copy(serviceIds = h.serviceIds.updated(name.value, id -> vm)) -> true).pure)
|
||||
case None =>
|
||||
report(name, "Service with this name is not registered, can't set its ID").as(false)
|
||||
name.value.pure
|
||||
)(h =>
|
||||
mangler
|
||||
.rename(name.value)
|
||||
.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]] =
|
||||
getService(name.value).flatMap {
|
||||
case Some(_) =>
|
||||
getState.flatMap(st =>
|
||||
st.stack
|
||||
.flatMap(_.serviceIds.get(name.value).map(_._2))
|
||||
.headOption orElse st.rootServiceIds
|
||||
.get(
|
||||
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 getServiceRename(name: NamedTypeToken[S]): State[X, Option[String]] =
|
||||
(
|
||||
serviceExists(name.value),
|
||||
getState.map(_.getServiceRename(name.value))
|
||||
).flatMapN {
|
||||
case (true, Some(rename)) => rename.some.pure
|
||||
case (false, _) => report(name, "Service with this name is undefined").as(none)
|
||||
case (_, None) => report(name, "Service ID is undefined").as(none)
|
||||
}
|
||||
|
||||
override def beginScope(token: Token[S]): SX[Unit] =
|
||||
@ -158,8 +120,8 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
|
||||
override def endScope(): SX[Unit] = stackInt.endScope
|
||||
|
||||
private def getService(name: String): SX[Option[ServiceRaw]] =
|
||||
getState.map(_.services.get(name))
|
||||
private def serviceExists(name: String): SX[Boolean] =
|
||||
getState.map(_.services(name))
|
||||
|
||||
private def getAbility(name: String): SX[Option[RawContext]] =
|
||||
getState.map(_.abilities.get(name))
|
||||
|
@ -4,39 +4,63 @@ import aqua.raw.{RawContext, ServiceRaw}
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken}
|
||||
import aqua.types.ArrowType
|
||||
|
||||
import cats.Monoid
|
||||
import cats.syntax.foldable.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.data.NonEmptyList
|
||||
import aqua.parser.lexer.Token.name
|
||||
|
||||
case class AbilitiesState[S[_]](
|
||||
stack: List[AbilitiesState.Frame[S]] = Nil,
|
||||
services: Map[String, ServiceRaw] = Map.empty,
|
||||
services: Set[String] = Set.empty,
|
||||
abilities: Map[String, RawContext] = Map.empty,
|
||||
rootServiceIds: Map[String, (ValueToken[S], ValueRaw)] =
|
||||
Map.empty[String, (ValueToken[S], ValueRaw)],
|
||||
definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]]
|
||||
rootServiceIds: Map[String, ValueRaw] = Map(),
|
||||
definitions: Map[String, NamedTypeToken[S]] = Map()
|
||||
) {
|
||||
|
||||
def purgeArrows: Option[(NonEmptyList[(Name[S], ArrowType)], AbilitiesState[S])] =
|
||||
stack match {
|
||||
case sc :: tail =>
|
||||
NonEmptyList
|
||||
.fromList(sc.arrows.values.toList)
|
||||
.map(_ -> copy[S](sc.copy(arrows = Map.empty) :: tail))
|
||||
case _ => None
|
||||
}
|
||||
def defineService(name: NamedTypeToken[S], defaultId: Option[ValueRaw]): AbilitiesState[S] =
|
||||
copy(
|
||||
services = services + name.value,
|
||||
definitions = definitions.updated(name.value, name),
|
||||
rootServiceIds = rootServiceIds ++ defaultId.map(name.value -> _)
|
||||
)
|
||||
|
||||
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 {
|
||||
|
||||
case class Frame[S[_]](
|
||||
token: Token[S],
|
||||
arrows: Map[String, (Name[S], ArrowType)] = Map.empty[String, (Name[S], ArrowType)],
|
||||
serviceIds: Map[String, (ValueToken[S], ValueRaw)] =
|
||||
Map.empty[String, (ValueToken[S], ValueRaw)]
|
||||
services: Map[String, Frame.ServiceState] = Map()
|
||||
) {
|
||||
|
||||
def setServiceRename(name: String, rename: String): Frame[S] =
|
||||
copy(services =
|
||||
services.updated(
|
||||
name,
|
||||
Frame.ServiceState(rename)
|
||||
)
|
||||
)
|
||||
|
||||
implicit def abilitiesStateMonoid[S[_]]: Monoid[AbilitiesState[S]] =
|
||||
new Monoid[AbilitiesState[S]] {
|
||||
def getServiceRename(name: String): Option[String] =
|
||||
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 combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] =
|
||||
@ -51,7 +75,10 @@ object AbilitiesState {
|
||||
|
||||
def init[S[_]](context: RawContext): AbilitiesState[S] =
|
||||
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?
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package aqua.semantics.rules.mangler
|
||||
|
||||
trait ManglerAlgebra[Alg[_]] {
|
||||
def rename(name: String): Alg[String]
|
||||
}
|
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package aqua.semantics.rules.types
|
||||
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.raw.value.{PropertyRaw, ValueRaw}
|
||||
import aqua.types.{AbilityType, ArrowType, NamedType, StructType, Type}
|
||||
import aqua.types.*
|
||||
|
||||
import cats.data.NonEmptyMap
|
||||
import cats.data.NonEmptyList
|
||||
@ -15,11 +15,18 @@ trait TypesAlgebra[S[_], Alg[_]] {
|
||||
|
||||
def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]]
|
||||
|
||||
def resolveServiceType(name: NamedTypeToken[S]): Alg[Option[ServiceType]]
|
||||
|
||||
def defineAbilityType(
|
||||
name: NamedTypeToken[S],
|
||||
fields: Map[String, (Name[S], Type)]
|
||||
): Alg[Option[AbilityType]]
|
||||
|
||||
def defineServiceType(
|
||||
name: NamedTypeToken[S],
|
||||
fields: Map[String, (Name[S], Type)]
|
||||
): Alg[Option[ServiceType]]
|
||||
|
||||
def defineStructType(
|
||||
name: NamedTypeToken[S],
|
||||
fields: Map[String, (Name[S], Type)]
|
||||
|
@ -13,16 +13,17 @@ import aqua.raw.value.{
|
||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.StackInterpreter
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
import aqua.semantics.rules.types.TypesStateHelper.{TypeResolution, TypeResolutionError}
|
||||
import aqua.types.*
|
||||
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{Chain, NonEmptyList, NonEmptyMap, State}
|
||||
import cats.instances.list.*
|
||||
import cats.data.{Chain, NonEmptyList, NonEmptyMap, OptionT, State}
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.foldable.*
|
||||
import cats.{~>, Applicative}
|
||||
import cats.syntax.option.*
|
||||
import monocle.Lens
|
||||
@ -44,18 +45,12 @@ class TypesInterpreter[S[_], X](implicit
|
||||
|
||||
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]] =
|
||||
getState.map(st => st.strict.get(name))
|
||||
|
||||
override def resolveType(token: TypeToken[S]): State[X, Option[Type]] =
|
||||
getState.map(st => TypesStateHelper.resolveTypeToken(token, st, resolver)).flatMap {
|
||||
case Some((typ, tokens)) =>
|
||||
getState.map(TypesStateHelper.resolveTypeToken(token)).flatMap {
|
||||
case Some(TypeResolution(typ, tokens)) =>
|
||||
val tokensLocs = tokens.map { case (t, n) => n.value -> t }
|
||||
locations.pointLocations(tokensLocs).as(typ.some)
|
||||
case None =>
|
||||
@ -64,48 +59,78 @@ class TypesInterpreter[S[_], X](implicit
|
||||
}
|
||||
|
||||
override def resolveArrowDef(arrowDef: ArrowTypeToken[S]): State[X, Option[ArrowType]] =
|
||||
getState.map(st => TypesStateHelper.resolveArrowDef(arrowDef, st, resolver)).flatMap {
|
||||
case Valid(t) =>
|
||||
val (tt, tokens) = t
|
||||
val tokensLocs = tokens.map { case (t, n) =>
|
||||
n.value -> t
|
||||
}
|
||||
locations.pointLocations(tokensLocs).map(_ => Some(tt))
|
||||
getState.map(TypesStateHelper.resolveArrowDef(arrowDef)).flatMap {
|
||||
case Valid(TypeResolution(tt, tokens)) =>
|
||||
val tokensLocs = tokens.map { case (t, n) => n.value -> t }
|
||||
locations.pointLocations(tokensLocs).as(tt.some)
|
||||
case Invalid(errs) =>
|
||||
errs
|
||||
.foldLeft[ST[Option[ArrowType]]](State.pure(None)) { case (n, (tkn, hint)) =>
|
||||
report(tkn, hint) >> n
|
||||
errs.traverse_ { case TypeResolutionError(token, hint) =>
|
||||
report(token, hint)
|
||||
}.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(
|
||||
name: NamedTypeToken[S],
|
||||
fields: Map[String, (Name[S], Type)]
|
||||
): State[X, Option[AbilityType]] =
|
||||
getState.map(_.definitions.get(name.value)).flatMap {
|
||||
case Some(_) => report(name, s"Ability `${name.value}` was already defined").as(none)
|
||||
case None =>
|
||||
ensureNameNotDefined(name.value, name, ifDefined = none) {
|
||||
val types = fields.view.mapValues { case (_, t) => t }.toMap
|
||||
NonEmptyMap
|
||||
.fromMap(SortedMap.from(types))
|
||||
.fold(report(name, s"Ability `${name.value}` has no fields").as(none))(nonEmptyFields =>
|
||||
val `type` = AbilityType(name.value, nonEmptyFields)
|
||||
modify { st =>
|
||||
st.copy(
|
||||
strict = st.strict.updated(name.value, `type`),
|
||||
definitions = st.definitions.updated(name.value, name)
|
||||
)
|
||||
}.as(`type`.some)
|
||||
|
||||
modify(_.defineType(name, `type`)).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(
|
||||
name: NamedTypeToken[S],
|
||||
fields: Map[String, (Name[S], Type)]
|
||||
): State[X, Option[StructType]] =
|
||||
getState.map(_.definitions.get(name.value)).flatMap {
|
||||
case Some(_) => report(name, s"Data `${name.value}` was already defined").as(none)
|
||||
case None =>
|
||||
ensureNameNotDefined(name.value, name, ifDefined = none)(
|
||||
fields.toList.traverse {
|
||||
case (field, (fieldName, t: DataType)) =>
|
||||
t match {
|
||||
@ -125,27 +150,20 @@ class TypesInterpreter[S[_], X](implicit
|
||||
report(name, s"Struct `${name.value}` has no fields").as(none)
|
||||
)(nonEmptyFields =>
|
||||
val `type` = StructType(name.value, nonEmptyFields)
|
||||
modify { st =>
|
||||
st.copy(
|
||||
strict = st.strict.updated(name.value, `type`),
|
||||
definitions = st.definitions.updated(name.value, name)
|
||||
)
|
||||
}.as(`type`.some)
|
||||
|
||||
modify(_.defineType(name, `type`)).as(`type`.some)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def defineAlias(name: NamedTypeToken[S], target: Type): State[X, Boolean] =
|
||||
getState.map(_.definitions.get(name.value)).flatMap {
|
||||
case Some(n) if n == name => State.pure(false)
|
||||
case Some(_) => report(name, s"Type `${name.value}` was already defined").as(false)
|
||||
case None =>
|
||||
modify(st =>
|
||||
st.copy(
|
||||
strict = st.strict.updated(name.value, target),
|
||||
definitions = st.definitions.updated(name.value, name)
|
||||
)
|
||||
).flatMap(_ => locations.addToken(name.value, name)).as(true)
|
||||
modify(_.defineType(name, target))
|
||||
.productL(locations.addToken(name.value, name))
|
||||
.as(true)
|
||||
}
|
||||
|
||||
override def resolveField(rootT: Type, op: IntoField[S]): State[X, Option[PropertyRaw]] = {
|
||||
@ -465,4 +483,22 @@ class TypesInterpreter[S[_], X](implicit
|
||||
).as(frame -> Nil)
|
||||
else (frame -> frame.retVals.getOrElse(Nil)).pure
|
||||
) <* 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
|
||||
}
|
||||
}
|
||||
|
@ -9,129 +9,139 @@ import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
|
||||
import cats.kernel.Monoid
|
||||
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[_]](
|
||||
fields: Map[String, (Name[S], Type)] = Map.empty[String, (Name[S], Type)],
|
||||
strict: Map[String, Type] = Map.empty[String, Type],
|
||||
definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]],
|
||||
fields: Map[String, (Name[S], Type)] = Map(),
|
||||
strict: Map[String, Type] = Map.empty,
|
||||
definitions: Map[String, NamedTypeToken[S]] = Map(),
|
||||
stack: List[TypesState.Frame[S]] = Nil
|
||||
) {
|
||||
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 {
|
||||
|
||||
// TODO: an ugly return type, refactoring
|
||||
// Returns type and a token with its definition
|
||||
def resolveTypeToken[S[_]](
|
||||
tt: TypeToken[S],
|
||||
state: TypesState[S],
|
||||
resolver: (
|
||||
TypesState[S],
|
||||
NamedTypeToken[S]
|
||||
) => Option[(Type, List[(Token[S], NamedTypeToken[S])])]
|
||||
): Option[(Type, List[(Token[S], NamedTypeToken[S])])] =
|
||||
final case class TypeResolution[S[_], +T](
|
||||
`type`: T,
|
||||
definitions: List[(Token[S], NamedTypeToken[S])]
|
||||
)
|
||||
|
||||
final case class TypeResolutionError[S[_]](
|
||||
token: Token[S],
|
||||
hint: String
|
||||
)
|
||||
|
||||
def resolveTypeToken[S[_]](tt: TypeToken[S])(
|
||||
state: TypesState[S]
|
||||
): Option[TypeResolution[S, Type]] =
|
||||
tt match {
|
||||
case TopBottomToken(_, isTop) =>
|
||||
(if (isTop) TopType else BottomType, Nil).some
|
||||
val `type` = if (isTop) TopType else BottomType
|
||||
|
||||
TypeResolution(`type`, Nil).some
|
||||
case ArrayTypeToken(_, dtt) =>
|
||||
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) =>
|
||||
(ArrayType(it), t)
|
||||
resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
|
||||
TypeResolution(ArrayType(it), t)
|
||||
}
|
||||
case StreamTypeToken(_, dtt) =>
|
||||
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) =>
|
||||
(StreamType(it), t)
|
||||
resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
|
||||
TypeResolution(StreamType(it), t)
|
||||
}
|
||||
case OptionTypeToken(_, dtt) =>
|
||||
resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) =>
|
||||
(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)
|
||||
resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) =>
|
||||
TypeResolution(OptionType(it), t)
|
||||
}
|
||||
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[_]](
|
||||
arrowTypeToken: ArrowTypeToken[S],
|
||||
state: TypesState[S],
|
||||
resolver: (
|
||||
TypesState[S],
|
||||
NamedTypeToken[S]
|
||||
) => Option[(Type, List[(Token[S], NamedTypeToken[S])])]
|
||||
): ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])] = {
|
||||
val resType = arrowTypeToken.res.map(resolveTypeToken(_, state, resolver))
|
||||
|
||||
NonEmptyChain
|
||||
.fromChain(Chain.fromSeq(arrowTypeToken.res.zip(resType).collect { case (dt, None) =>
|
||||
dt -> "Cannot resolve the result type"
|
||||
}))
|
||||
.fold[ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])]] {
|
||||
val (errs, argTypes) = arrowTypeToken.args.map { (argName, tt) =>
|
||||
resolveTypeToken(tt, state, resolver)
|
||||
.toRight(tt -> s"Type unresolved")
|
||||
def resolveArrowDef[S[_]](arrowTypeToken: ArrowTypeToken[S])(
|
||||
state: TypesState[S]
|
||||
): ValidatedNec[TypeResolutionError[S], TypeResolution[S, ArrowType]] = {
|
||||
val res = arrowTypeToken.res.traverse(typeToken =>
|
||||
resolveTypeToken(typeToken)(state)
|
||||
.toValidNec(
|
||||
TypeResolutionError(
|
||||
typeToken,
|
||||
"Can not resolve the result type"
|
||||
)
|
||||
)
|
||||
)
|
||||
val args = arrowTypeToken.args.traverse { case (argName, typeToken) =>
|
||||
resolveTypeToken(typeToken)(state)
|
||||
.toValidNec(
|
||||
TypeResolutionError(
|
||||
typeToken,
|
||||
"Can not resolve the argument type"
|
||||
)
|
||||
)
|
||||
.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
|
||||
.fromChain(errs)
|
||||
.fold[ValidatedNec[
|
||||
(Token[S], String),
|
||||
(ArrowType, List[(Token[S], NamedTypeToken[S])])
|
||||
]](
|
||||
Valid {
|
||||
val (labels, types) = argTypes.toList.unzip
|
||||
val (resTypes, resTokens) = resType.flatten.unzip
|
||||
(
|
||||
ArrowType(
|
||||
ProductType.maybeLabelled(labels.zip(types.map(_._1))),
|
||||
(args, res).mapN { (args, res) =>
|
||||
val (argsLabeledTypes, argsTokens) =
|
||||
args.map { case lbl -> TypeResolution(typ, tkn) =>
|
||||
(lbl, typ) -> tkn
|
||||
}.unzip.map(_.flatten)
|
||||
val (resTypes, resTokens) =
|
||||
res.map { case TypeResolution(typ, tkn) =>
|
||||
typ -> tkn
|
||||
}.unzip.map(_.flatten)
|
||||
|
||||
val typ = ArrowType(
|
||||
ProductType.maybeLabelled(argsLabeledTypes),
|
||||
ProductType(resTypes)
|
||||
),
|
||||
types.flatMap(_._2) ++ resTokens.flatten
|
||||
)
|
||||
val defs = (argsTokens ++ resTokens)
|
||||
|
||||
TypeResolution(typ, defs)
|
||||
}
|
||||
)(Invalid(_))
|
||||
}(Invalid(_))
|
||||
}
|
||||
}
|
||||
|
||||
object TypesState {
|
||||
|
||||
final case class TypeDefinition[S[_]](
|
||||
token: NamedTypeToken[S],
|
||||
`type`: Type
|
||||
)
|
||||
|
||||
case class Frame[S[_]](
|
||||
token: ArrowTypeToken[S],
|
||||
arrowType: ArrowType,
|
||||
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 combine(x: TypesState[S], y: TypesState[S]): TypesState[S] =
|
||||
|
@ -15,7 +15,8 @@ import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class ArrowSemSpec extends AnyFlatSpec with Matchers with EitherValues {
|
||||
import Utils.*
|
||||
|
||||
import Utils.{given, *}
|
||||
|
||||
def program(arrowStr: String): Prog[State[CompilerState[cats.Id], *], Raw] = {
|
||||
import CompilerState.*
|
||||
|
@ -29,7 +29,7 @@ import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class ClosureSemSpec extends AnyFlatSpec with Matchers {
|
||||
|
||||
import Utils.*
|
||||
import Utils.{given, *}
|
||||
|
||||
val program: Prog[State[CompilerState[cats.Id], *], Raw] = {
|
||||
import CompilerState.*
|
||||
|
@ -87,11 +87,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
def testServiceCallStr(str: String) =
|
||||
CallArrowRawTag
|
||||
.service(
|
||||
serviceId = LiteralRaw.quote("test"),
|
||||
fnName = "testCallStr",
|
||||
.ability(
|
||||
abilityName = "Test",
|
||||
funcName = "testCallStr",
|
||||
call = Call(LiteralRaw.quote(str) :: Nil, Nil),
|
||||
name = "Test",
|
||||
arrowType = ArrowType(
|
||||
ProductType.labelled(("s" -> ScalarType.string) :: Nil),
|
||||
ProductType(ScalarType.string :: Nil)
|
||||
@ -137,11 +136,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
insideBody(script) { body =>
|
||||
val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType))
|
||||
val serviceCall = CallArrowRawTag
|
||||
.service(
|
||||
serviceId = LiteralRaw.quote("srv1"),
|
||||
fnName = "fn1",
|
||||
.ability(
|
||||
abilityName = "A",
|
||||
funcName = "fn1",
|
||||
call = emptyCall,
|
||||
name = "A",
|
||||
arrowType = arrowType
|
||||
)
|
||||
.leaf
|
||||
|
@ -6,45 +6,35 @@ import aqua.parser.lift.Span
|
||||
import aqua.raw.{Raw, RawContext}
|
||||
import aqua.semantics.expr.func.ClosureSem
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
import aqua.semantics.rules.abilities.{AbilitiesInterpreter, AbilitiesState}
|
||||
import aqua.semantics.rules.locations.DummyLocationsInterpreter
|
||||
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
||||
import aqua.semantics.rules.types.{TypesInterpreter, TypesState}
|
||||
import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
|
||||
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
|
||||
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
|
||||
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
|
||||
import aqua.types.*
|
||||
|
||||
import cats.data.State
|
||||
import cats.{~>, Id}
|
||||
import monocle.Lens
|
||||
import monocle.macros.GenLens
|
||||
import monocle.syntax.all.*
|
||||
import aqua.semantics.rules.mangler.ManglerAlgebra
|
||||
import aqua.semantics.rules.mangler.ManglerInterpreter
|
||||
|
||||
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(
|
||||
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]] =
|
||||
given LocationsAlgebra[Id, State[CompilerState[Id], *]] =
|
||||
new DummyLocationsInterpreter[Id, CompilerState[Id]]()
|
||||
|
||||
implicit val ns: Lens[CompilerState[Id], NamesState[Id]] = GenLens[CompilerState[Id]](_.names)
|
||||
|
||||
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]] =
|
||||
given NamesAlgebra[Id, State[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]]
|
||||
|
||||
implicit val abilitiesInterpreter: AbilitiesInterpreter[Id, CompilerState[Id]] =
|
||||
given AbilitiesAlgebra[Id, State[CompilerState[Id], *]] =
|
||||
new AbilitiesInterpreter[Id, CompilerState[Id]]
|
||||
|
||||
def spanToId: Span.S ~> Id = new (Span.S ~> Id) {
|
||||
|
@ -1,32 +1,17 @@
|
||||
package aqua.semantics
|
||||
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
import aqua.semantics.rules.names.NamesState
|
||||
import aqua.semantics.rules.abilities.AbilitiesState
|
||||
import aqua.semantics.rules.types.TypesState
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.semantics.rules.abilities.AbilitiesInterpreter
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
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.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState}
|
||||
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
|
||||
import aqua.semantics.rules.definitions.{DefinitionsAlgebra, DefinitionsInterpreter}
|
||||
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
|
||||
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra}
|
||||
import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter}
|
||||
import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw}
|
||||
import aqua.raw.RawContext
|
||||
import aqua.types.*
|
||||
import aqua.parser.lexer.{
|
||||
CollectionToken,
|
||||
InfixToken,
|
||||
LiteralToken,
|
||||
Name,
|
||||
PrefixToken,
|
||||
ValueToken,
|
||||
VarToken
|
||||
}
|
||||
import aqua.raw.value.ApplyUnaryOpRaw
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.raw.value.*
|
||||
import aqua.parser.lexer.ValueToken.string
|
||||
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
@ -50,6 +35,8 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
given LocationsAlgebra[Id, Interpreter] =
|
||||
new DummyLocationsInterpreter[Id, CompilerState[Id]]
|
||||
|
||||
given ManglerAlgebra[Interpreter] =
|
||||
new ManglerInterpreter[CompilerState[Id]]
|
||||
given TypesAlgebra[Id, Interpreter] =
|
||||
new TypesInterpreter[Id, CompilerState[Id]]
|
||||
given AbilitiesAlgebra[Id, Interpreter] =
|
||||
|
@ -243,31 +243,19 @@ case class OptionType(element: Type) extends BoxType {
|
||||
sealed trait NamedType extends Type {
|
||||
def name: String
|
||||
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.
|
||||
* Paths to arrows are returned **without** ability name
|
||||
* Get all arrows defined in this type and its sub-abilities.
|
||||
* Paths to arrows are returned **without** type name
|
||||
* to allow renaming on call site.
|
||||
*/
|
||||
lazy val arrows: Map[String, ArrowType] = {
|
||||
def getArrowsEval(path: Option[String], ability: AbilityType): Eval[List[(String, ArrowType)]] =
|
||||
ability.fields.toNel.toList.flatTraverse {
|
||||
case (abName, abType: AbilityType) =>
|
||||
val newPath = path.fold(abName)(AbilityType.fullName(_, abName))
|
||||
getArrowsEval(newPath.some, abType)
|
||||
def getArrowsEval(path: Option[String], nt: NamedType): Eval[List[(String, ArrowType)]] =
|
||||
nt.fields.toNel.toList.flatTraverse {
|
||||
// sub-arrows could be in abilities or services
|
||||
case (innerName, innerType: (ServiceType | AbilityType)) =>
|
||||
val newPath = path.fold(innerName)(AbilityType.fullName(_, innerName))
|
||||
getArrowsEval(newPath.some, innerType)
|
||||
case (aName, aType: ArrowType) =>
|
||||
val newPath = path.fold(aName)(AbilityType.fullName(_, aName))
|
||||
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.
|
||||
* Paths to abilities are returned **without** ability name
|
||||
* Get all abilities defined in this type and its sub-abilities.
|
||||
* Paths to abilities are returned **without** type name
|
||||
* to allow renaming on call site.
|
||||
*/
|
||||
lazy val abilities: Map[String, AbilityType] = {
|
||||
def getAbilitiesEval(
|
||||
path: Option[String],
|
||||
ability: AbilityType
|
||||
nt: NamedType
|
||||
): 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) =>
|
||||
val fullName = path.fold(abName)(AbilityType.fullName(_, abName))
|
||||
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.
|
||||
* Paths to variables are returned **without** ability name
|
||||
* Get all variables defined in this type and its sub-abilities.
|
||||
* Paths to variables are returned **without** type name
|
||||
* to allow renaming on call site.
|
||||
*/
|
||||
lazy val variables: Map[String, DataType] = {
|
||||
def getVariablesEval(
|
||||
path: Option[String],
|
||||
ability: AbilityType
|
||||
nt: NamedType
|
||||
): 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) =>
|
||||
val newPath = path.fold(abName)(AbilityType.fullName(_, abName))
|
||||
getVariablesEval(newPath.some, abType)
|
||||
@ -321,6 +311,25 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends
|
||||
|
||||
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 =
|
||||
s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||
|
@ -170,7 +170,7 @@ class TypeSpec extends AnyFlatSpec with Matchers {
|
||||
accepts(four, one) should be(false)
|
||||
}
|
||||
|
||||
"labeled types" should "create correc labels" in {
|
||||
"labeled types" should "create correct labels" in {
|
||||
val cons = LabeledConsType(
|
||||
"arg1",
|
||||
ArrowType(
|
||||
|
Loading…
Reference in New Issue
Block a user