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

* Refactor ServiceSem

* Refactor AbilityIdSem

* Remove tokens from state

* Refactor

* Add mangler

* Fix tests

* Refactor valueToRaw

* ServiceIdTag

* AbilityId -> ServiceId

* Add ServiceType

* Fix defineServiceType

* Refactor resolveArrowDef

* Refactor TypesHelper

* Add ServiceIdTag inlining

* Implement resolution

* Add service as ability passing

* Fix importing services

* Fix cli

* Implement default service

* Remove println

* Fix capture

* Add integration test

* Fix id

* Fix test

* Fix test

* Refactor test

* Do not resolve id

* Refactor FuncArrow creation

* Refactor FuncArrow wrapper creation

* Add named arguments

* Add comment

* ensureIsString -> valueToStringRaw, refactor OnSem

* Resolve services as abilities

* Add name to varNames

* Remove service hack

* Capture services, do not rename captured

* Rename arrows along with values

* Fix CallArrowRaw.map

* Fix unit tests

* Remove service case

* Refactor abilities state

* Propagate rootServiceIds

* Remove unused

* Add comments

* Refactor

* Refactor

* Add test

---------

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

View File

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

View File

@ -153,6 +153,7 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
val canonResult = VarModel("-" + results.name + "-fix-0", CanonStreamType(resultsType.element))
val 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)
),

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,10 +5,11 @@ import aqua.model.*
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.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
)

View File

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

View File

@ -59,65 +59,57 @@ 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 {
case IntoArrowRaw(arrowName, t, arguments) =>
val arrowType = abilityType.fields
.lookup(arrowName)
.collect { case at @ ArrowType(_, _) =>
at
}
.getOrElse {
logger.error(s"Inlining, cannot find arrow $arrowName in ability $varModel")
ArrowType(NilType, NilType)
}
for {
callArrow <- CallArrowRawInliner(
CallArrowRaw(
None,
AbilityType.fullName(varModel.name, arrowName),
arguments,
arrowType,
None
)
): State[S, (VarModel, Inline)] = p match {
case IntoArrowRaw(arrowName, t, arguments) =>
val arrowType = abilityType.fields
.lookup(arrowName)
.collect { case at @ ArrowType(_, _) =>
at
}
.getOrElse {
logger.error(s"Inlining, cannot find arrow $arrowName in $varModel")
ArrowType(NilType, NilType)
}
for {
callArrow <- CallArrowRawInliner(
CallArrowRaw.func(
funcName = AbilityType.fullName(varModel.name, arrowName),
baseType = arrowType,
arguments = arguments
)
result <- callArrow match {
case (vm: VarModel, inl) =>
State.pure((vm, inl))
case (lm: LiteralModel, inl) =>
flatLiteralWithProperties(lm, inl, Chain.empty).flatMap { case (vm, inline) =>
Exports[S].resolved(vm.name, vm).map(_ => (vm, inline))
}
}
} yield {
result
)
result <- callArrow match {
case (vm: VarModel, inl) =>
State.pure((vm, inl))
case (lm: LiteralModel, inl) =>
flatLiteralWithProperties(lm, inl, Chain.empty).flatMap { case (vm, inline) =>
Exports[S].resolved(vm.name, vm).map(_ => (vm, inline))
}
}
case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) =>
(VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure
case IntoFieldRaw(fieldName, t) =>
for {
abilityField <- Exports[S].getAbilityField(varModel.name, fieldName)
result <- abilityField match {
case Some(vm: VarModel) =>
State.pure((vm, Inline.empty))
case Some(lm: LiteralModel) =>
flatLiteralWithProperties(lm, Inline.empty, Chain.empty)
case _ =>
Exports[S].getKeys.flatMap { keys =>
logger.error(
s"Inlining, cannot find field ${AbilityType
.fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys"
)
flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty)
}
} yield result
case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) =>
(VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure
case IntoFieldRaw(fieldName, t) =>
for {
abilityField <- Exports[S].getAbilityField(varModel.name, fieldName)
result <- abilityField match {
case Some(vm: VarModel) =>
State.pure((vm, Inline.empty))
case Some(lm: LiteralModel) =>
flatLiteralWithProperties(lm, Inline.empty, Chain.empty)
case _ =>
Exports[S].getKeys.flatMap { keys =>
logger.error(
s"Inlining, cannot find field ${AbilityType
.fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys"
)
flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty)
}
}
} 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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 + ".")
}
.map(prefixFirst(prefix, _))
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(_.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)
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,15 +8,11 @@ import aqua.raw.{Raw, RawContext, RawPart}
import aqua.semantics.header.Picker
import aqua.semantics.header.Picker.*
import aqua.semantics.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

View File

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

View File

@ -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(
expr.name,
arrows,
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]
}
): 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,
arrowsDefs,
defaultId
).map(defined =>
Raw
.error("Service not created due to validation errors")
.asLeft
.whenA(!defined)
)
)
} yield ServiceRaw(
expr.name.value,
serviceType,
defaultId
)
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)
)
}

View File

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

View File

@ -13,11 +13,13 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.semantics.rules.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 {

View File

@ -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`)
}
case None => false.pure[Alg]
OptionT(V.valueToRaw(v)).filterF { vm =>
val expectedType = vm.`type` match {
case _: BoxType => OptionType(ScalarType.string)
case _ => ScalarType.string
}
T.ensureTypeMatches(v, expectedType, vm.`type`)
}
)
.map(_.flatten)
.getOrElse(List.empty)
}

View File

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

View File

@ -11,6 +11,7 @@ import aqua.semantics.rules.errors.ErrorsAlgebra
import aqua.types.*
import 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 =>
resolvedType match {
case struct @ StructType(_, _) =>
(
StructType(typeName.value, rf.map(_.`type`)),
Some(MakeStructRaw(rf, struct))
)
case scope @ AbilityType(_, _) =>
(
AbilityType(typeName.value, rf.map(_.`type`)),
Some(AbilityRaw(rf, scope))
)
}
)
.getOrElse(BottomType -> None)
(typeFromFields, data) = typeFromFieldsWithData
isTypesCompatible <- T.ensureTypeMatches(dvt, resolvedType, typeFromFields)
} yield data.filter(_ => isTypesCompatible)
case _ => None.pure[Alg]
}
(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 =>
(
struct.copy(fields = fieldsGivenTypes),
MakeStructRaw(fieldsGiven, struct)
).some
case ability: AbilityType =>
(
ability.copy(fields = fieldsGivenTypes),
AbilityRaw(fieldsGiven, ability)
).some
case _ => none
}
)
(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 Some(st: ServiceType) =>
OptionT(A.getServiceRename(ab))
.subflatMap(rename =>
callArrowFromAbility(
ab.asName.rename(rename),
st,
callArrow.funcName
)
)
.value
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
)
.some
case (Some(at), Left(true)) =>
A.getArrow(ab, callArrow.funcName).map {
case Some(at) =>
CallArrowRaw
.ability(
abilityName = ab.value,

View File

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

View File

@ -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,83 +69,50 @@ 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)
getAbility(name.value).flatMap {
case Some(abCtx) =>
abCtx.funcs
.get(arrow.value)
.fold(
report(
arrow,
Levenshtein.genMessage(
s"Service is found, but arrow '${arrow.value}' isn't found in scope",
s"Ability is found, but arrow '${arrow.value}' isn't found in scope",
arrow.value,
arrows.value.keys.toNonEmptyList.toList
abCtx.funcs.keys.toList
)
).as(Option.empty[ArrowType])
)(a => addServiceArrowLocation(name, arrow).as(Some(a)))
case None =>
getAbility(name.value).flatMap {
case Some(abCtx) =>
abCtx.funcs
.get(arrow.value)
.fold(
report(
arrow,
Levenshtein.genMessage(
s"Ability is found, but arrow '${arrow.value}' isn't found in scope",
arrow.value,
abCtx.funcs.keys.toList
)
).as(Option.empty[ArrowType])
) { fn =>
// TODO: add name and arrow separately
// TODO: find tokens somewhere
addServiceArrowLocation(name, arrow).as(Some(fn.arrow.`type`))
}
case None =>
report(name, "Ability with this name is undefined").as(Option.empty[ArrowType])
}
}
override def setServiceId(name: NamedTypeToken[S], id: ValueToken[S], vm: ValueRaw): SX[Boolean] =
getService(name.value).flatMap {
case Some(_) =>
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)
}
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))
).as(none)
) { fn =>
// TODO: add name and arrow separately
// TODO: find tokens somewhere
addServiceArrowLocation(name, arrow).as(fn.arrow.`type`.some)
}
)
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)
)
}
report(name, "Ability with this name is undefined").as(none)
}
override def renameService(name: NamedTypeToken[S]): SX[Option[String]] =
serviceExists(name.value).flatMap {
case true =>
mapStackHeadM(
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 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))

View File

@ -4,54 +4,81 @@ 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()
) {
implicit def abilitiesStateMonoid[S[_]]: Monoid[AbilitiesState[S]] =
new Monoid[AbilitiesState[S]] {
override def empty: AbilitiesState[S] = AbilitiesState()
override def combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] =
AbilitiesState(
Nil,
x.services ++ y.services,
x.abilities ++ y.abilities,
x.rootServiceIds ++ y.rootServiceIds,
x.definitions ++ y.definitions
def setServiceRename(name: String, rename: String): Frame[S] =
copy(services =
services.updated(
name,
Frame.ServiceState(rename)
)
}
)
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] =
AbilitiesState(
Nil,
x.services ++ y.services,
x.abilities ++ y.abilities,
x.rootServiceIds ++ y.rootServiceIds,
x.definitions ++ y.definitions
)
}
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?
)
}

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ package aqua.semantics.rules.types
import aqua.parser.lexer.*
import aqua.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)]

View File

@ -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,88 +59,111 @@ 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 =>
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)
)
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(_.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 =>
fields.toList.traverse {
case (field, (fieldName, t: DataType)) =>
t match {
case _: StreamType => report(fieldName, s"Field '$field' has stream type").as(none)
case _ => (field -> t).some.pure[ST]
}
case (field, (fieldName, t)) =>
report(
fieldName,
s"Field '$field' has unacceptable for struct field type '$t'"
).as(none)
}.map(_.sequence.map(_.toMap))
.flatMap(
_.map(SortedMap.from)
.flatMap(NonEmptyMap.fromMap)
.fold(
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)
)
)
}
ensureNameNotDefined(name.value, name, ifDefined = none)(
fields.toList.traverse {
case (field, (fieldName, t: DataType)) =>
t match {
case _: StreamType => report(fieldName, s"Field '$field' has stream type").as(none)
case _ => (field -> t).some.pure[ST]
}
case (field, (fieldName, t)) =>
report(
fieldName,
s"Field '$field' has unacceptable for struct field type '$t'"
).as(none)
}.map(_.sequence.map(_.toMap))
.flatMap(
_.map(SortedMap.from)
.flatMap(NonEmptyMap.fromMap)
.fold(
report(name, s"Struct `${name.value}` has no fields").as(none)
)(nonEmptyFields =>
val `type` = StructType(name.value, nonEmptyFields)
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
}
}

View File

@ -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))
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) -> _)
}
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")
.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)
}
(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)
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))),
ProductType(resTypes)
),
types.flatMap(_._2) ++ resTokens.flatten
)
}
)(Invalid(_))
}(Invalid(_))
val typ = ArrowType(
ProductType.maybeLabelled(argsLabeledTypes),
ProductType(resTypes)
)
val defs = (argsTokens ++ resTokens)
TypeResolution(typ, defs)
}
}
}
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] =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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