mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Introducing Product type (#225)
* Introducing Product type * Main codebase compiles with Arrow(domain, codomain) * Tests compile * Tests passed * Tiny fixes: use argument labels in js/ts generators * Fix for return type in .ts * Typescript fix * Fix for option return in JS * Arrow variance fix * separated CompareTypes * Added deprecation notices to ArrowType functions * Compile error fixed * Types doc comments * Multi-value return is supported in the model * Tests compilation fixes wip * Test compiles * Bugfix * Bugfix
This commit is contained in:
parent
4ccac9bf0e
commit
cd30ff8e8c
@ -1,5 +0,0 @@
|
|||||||
service Println("println-service-id"):
|
|
||||||
print: string -> ()
|
|
||||||
|
|
||||||
func print(str: string):
|
|
||||||
Println.print(str)
|
|
16
aqua-src/ret.aqua
Normal file
16
aqua-src/ret.aqua
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
data DT:
|
||||||
|
field: string
|
||||||
|
|
||||||
|
service DTGetter("get-dt"):
|
||||||
|
get_dt(s: string) -> DT
|
||||||
|
|
||||||
|
func use_name1(name: string) -> string:
|
||||||
|
results <- DTGetter.get_dt(name)
|
||||||
|
<- results.field
|
||||||
|
|
||||||
|
func use_name2(name: string) -> []string:
|
||||||
|
results: *string
|
||||||
|
results <- use_name1(name)
|
||||||
|
results <- use_name1(name)
|
||||||
|
results <- use_name1(name)
|
||||||
|
<- results
|
@ -1,11 +0,0 @@
|
|||||||
service Op("op"):
|
|
||||||
noop: -> ()
|
|
||||||
bz: string -> string
|
|
||||||
|
|
||||||
func return_none() -> string:
|
|
||||||
<- "some result in string"
|
|
||||||
|
|
||||||
func use() -> string:
|
|
||||||
res <- return_none()
|
|
||||||
res2 <- Op.bz(res)
|
|
||||||
<- res
|
|
@ -80,7 +80,7 @@ object AirGen extends LogSupport {
|
|||||||
|
|
||||||
case FoldRes(item, iterable) =>
|
case FoldRes(item, iterable) =>
|
||||||
Eval later ForGen(valueToData(iterable), item, opsToSingle(ops))
|
Eval later ForGen(valueToData(iterable), item, opsToSingle(ops))
|
||||||
case CallServiceRes(serviceId, funcName, Call(args, exportTo), peerId) =>
|
case CallServiceRes(serviceId, funcName, CallRes(args, exportTo), peerId) =>
|
||||||
Eval.later(
|
Eval.later(
|
||||||
ServiceCallGen(
|
ServiceCallGen(
|
||||||
valueToData(peerId),
|
valueToData(peerId),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package aqua.backend.js
|
package aqua.backend.js
|
||||||
|
|
||||||
import aqua.backend.air.FuncAirGen
|
import aqua.backend.air.FuncAirGen
|
||||||
import aqua.model.func.{ArgDef, FuncCallable}
|
import aqua.model.func.FuncCallable
|
||||||
import aqua.model.transform.GenerationConfig
|
import aqua.model.transform.GenerationConfig
|
||||||
import aqua.types._
|
import aqua.types._
|
||||||
import cats.syntax.show._
|
import cats.syntax.show._
|
||||||
@ -11,7 +11,7 @@ case class JavaScriptFunc(func: FuncCallable) {
|
|||||||
import JavaScriptFunc._
|
import JavaScriptFunc._
|
||||||
|
|
||||||
def argsJavaScript: String =
|
def argsJavaScript: String =
|
||||||
func.args.args.map(ad => s"${ad.name}").mkString(", ")
|
func.argNames.mkString(", ")
|
||||||
|
|
||||||
// TODO: use common functions between TypeScript and JavaScript backends
|
// TODO: use common functions between TypeScript and JavaScript backends
|
||||||
private def genReturnCallback(
|
private def genReturnCallback(
|
||||||
@ -42,26 +42,29 @@ case class JavaScriptFunc(func: FuncCallable) {
|
|||||||
|
|
||||||
val tsAir = FuncAirGen(func).generateAir(conf)
|
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||||
|
|
||||||
val setCallbacks = func.args.args.map {
|
val setCallbacks = func.args.collect {
|
||||||
case ArgDef.Data(argName, OptionType(_)) =>
|
case (argName, OptionType(_)) =>
|
||||||
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
||||||
case ArgDef.Data(argName, _) =>
|
case (argName, _: DataType) =>
|
||||||
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
|
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
|
||||||
case ArgDef.Arrow(argName, at) =>
|
case (argName, at: ArrowType) =>
|
||||||
val value = s"$argName(${argsCallToJs(
|
val value = s"$argName(${argsCallToJs(
|
||||||
at
|
at
|
||||||
)})"
|
)})"
|
||||||
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value")
|
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value")
|
||||||
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});"""
|
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});"""
|
||||||
}.mkString("\n")
|
}
|
||||||
|
.mkString("\n")
|
||||||
|
|
||||||
val returnCallback = func.ret
|
// TODO support multi-return
|
||||||
.map(_._2)
|
val returnCallback = func.arrowType.codomain.uncons
|
||||||
|
.map(_._1)
|
||||||
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
|
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
|
||||||
.getOrElse("")
|
.getOrElse("")
|
||||||
|
|
||||||
|
// TODO support multi-return
|
||||||
val returnVal =
|
val returnVal =
|
||||||
func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
func.ret.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||||
|
|
||||||
// TODO: it could be non-unique
|
// TODO: it could be non-unique
|
||||||
val configArgName = "config"
|
val configArgName = "config"
|
||||||
@ -113,12 +116,9 @@ case class JavaScriptFunc(func: FuncCallable) {
|
|||||||
object JavaScriptFunc {
|
object JavaScriptFunc {
|
||||||
|
|
||||||
def argsToTs(at: ArrowType): String =
|
def argsToTs(at: ArrowType): String =
|
||||||
at.args.zipWithIndex
|
at.domain.toLabelledList().map(_._1).mkString(", ")
|
||||||
.map(_.swap)
|
|
||||||
.map(kv => "arg" + kv._1)
|
|
||||||
.mkString(", ")
|
|
||||||
|
|
||||||
def argsCallToJs(at: ArrowType): String =
|
def argsCallToJs(at: ArrowType): String =
|
||||||
at.args.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
|
at.domain.toList.zipWithIndex.map(_._2).map(idx => s"args[$idx]").mkString(", ")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package aqua.backend.ts
|
package aqua.backend.ts
|
||||||
|
|
||||||
import aqua.backend.air.FuncAirGen
|
import aqua.backend.air.FuncAirGen
|
||||||
import aqua.model.func.{ArgDef, FuncCallable}
|
import aqua.model.func.FuncCallable
|
||||||
import aqua.model.transform.GenerationConfig
|
import aqua.model.transform.GenerationConfig
|
||||||
import aqua.types._
|
import aqua.types._
|
||||||
import cats.syntax.show._
|
import cats.syntax.show._
|
||||||
@ -11,7 +11,7 @@ case class TypeScriptFunc(func: FuncCallable) {
|
|||||||
import TypeScriptFunc._
|
import TypeScriptFunc._
|
||||||
|
|
||||||
def argsTypescript: String =
|
def argsTypescript: String =
|
||||||
func.args.args.map(ad => s"${ad.name}: " + typeToTs(ad.`type`)).mkString(", ")
|
func.arrowType.domain.toLabelledList().map(ad => s"${ad._1}: " + typeToTs(ad._2)).mkString(", ")
|
||||||
|
|
||||||
def generateUniqueArgName(args: List[String], basis: String, attempt: Int): String = {
|
def generateUniqueArgName(args: List[String], basis: String, attempt: Int): String = {
|
||||||
val name = if (attempt == 0) {
|
val name = if (attempt == 0) {
|
||||||
@ -50,42 +50,45 @@ case class TypeScriptFunc(func: FuncCallable) {
|
|||||||
|
|
||||||
val tsAir = FuncAirGen(func).generateAir(conf)
|
val tsAir = FuncAirGen(func).generateAir(conf)
|
||||||
|
|
||||||
val retType = func.ret
|
// TODO: support multi return
|
||||||
.map(_._2)
|
val retType = func.arrowType.codomain.uncons
|
||||||
|
.map(_._1)
|
||||||
|
val retTypeTs = retType
|
||||||
.fold("void")(typeToTs)
|
.fold("void")(typeToTs)
|
||||||
|
|
||||||
val returnCallback = func.ret
|
val returnCallback = retType
|
||||||
.map(_._2)
|
|
||||||
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
|
.map(t => genReturnCallback(t, conf.callbackService, conf.respFuncName))
|
||||||
.getOrElse("")
|
.getOrElse("")
|
||||||
|
|
||||||
val setCallbacks = func.args.args.map {
|
val setCallbacks = func.args.collect { // Product types are not handled
|
||||||
case ArgDef.Data(argName, OptionType(_)) =>
|
case (argName, OptionType(_)) =>
|
||||||
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});"""
|
||||||
case ArgDef.Data(argName, _) =>
|
case (argName, _: DataType) =>
|
||||||
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
|
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
|
||||||
case ArgDef.Arrow(argName, at) =>
|
case (argName, at: ArrowType) =>
|
||||||
val value = s"$argName(${argsCallToTs(
|
val value = s"$argName(${argsCallToTs(
|
||||||
at
|
at
|
||||||
)})"
|
)})"
|
||||||
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value")
|
val expr = at.res.fold(s"$value; return {}")(_ => s"return $value")
|
||||||
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});"""
|
s"""h.on('${conf.callbackService}', '$argName', (args) => {$expr;});"""
|
||||||
}.mkString("\n")
|
}
|
||||||
|
.mkString("\n")
|
||||||
|
|
||||||
|
// TODO support multi return
|
||||||
val returnVal =
|
val returnVal =
|
||||||
func.ret.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
func.ret.headOption.fold("Promise.race([promise, Promise.resolve()])")(_ => "promise")
|
||||||
|
|
||||||
val clientArgName = generateUniqueArgName(func.args.args.map(_.name), "client", 0)
|
val clientArgName = generateUniqueArgName(func.argNames, "client", 0)
|
||||||
val configArgName = generateUniqueArgName(func.args.args.map(_.name), "config", 0)
|
val configArgName = generateUniqueArgName(func.argNames, "config", 0)
|
||||||
|
|
||||||
val configType = "{ttl?: number}"
|
val configType = "{ttl?: number}"
|
||||||
|
|
||||||
s"""
|
s"""
|
||||||
|export async function ${func.funcName}($clientArgName: FluenceClient${if (func.args.isEmpty)
|
|export async function ${func.funcName}($clientArgName: FluenceClient${if (func.args.isEmpty)
|
||||||
""
|
""
|
||||||
else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retType> {
|
else ", "}${argsTypescript}, $configArgName?: $configType): Promise<$retTypeTs> {
|
||||||
| let request: RequestFlow;
|
| let request: RequestFlow;
|
||||||
| const promise = new Promise<$retType>((resolve, reject) => {
|
| const promise = new Promise<$retTypeTs>((resolve, reject) => {
|
||||||
| const r = new RequestFlowBuilder()
|
| const r = new RequestFlowBuilder()
|
||||||
| .disableInjections()
|
| .disableInjections()
|
||||||
| .withRawScript(
|
| .withRawScript(
|
||||||
@ -130,7 +133,7 @@ object TypeScriptFunc {
|
|||||||
case OptionType(t) => typeToTs(t) + " | null"
|
case OptionType(t) => typeToTs(t) + " | null"
|
||||||
case ArrayType(t) => typeToTs(t) + "[]"
|
case ArrayType(t) => typeToTs(t) + "[]"
|
||||||
case StreamType(t) => typeToTs(t) + "[]"
|
case StreamType(t) => typeToTs(t) + "[]"
|
||||||
case pt: ProductType =>
|
case pt: StructType =>
|
||||||
s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
|
s"{${pt.fields.map(typeToTs).toNel.map(kv => kv._1 + ":" + kv._2).toList.mkString(";")}}"
|
||||||
case st: ScalarType if ScalarType.number(st) => "number"
|
case st: ScalarType if ScalarType.number(st) => "number"
|
||||||
case ScalarType.bool => "boolean"
|
case ScalarType.bool => "boolean"
|
||||||
@ -142,14 +145,15 @@ object TypeScriptFunc {
|
|||||||
case at: ArrowType =>
|
case at: ArrowType =>
|
||||||
s"(${argsToTs(at)}) => ${at.res
|
s"(${argsToTs(at)}) => ${at.res
|
||||||
.fold("void")(typeToTs)}"
|
.fold("void")(typeToTs)}"
|
||||||
|
case _ =>
|
||||||
|
// TODO: handle product types in returns
|
||||||
|
"any"
|
||||||
}
|
}
|
||||||
|
|
||||||
def argsToTs(at: ArrowType): String =
|
def argsToTs(at: ArrowType): String =
|
||||||
at.args
|
at.domain
|
||||||
.map(typeToTs)
|
.toLabelledList()
|
||||||
.zipWithIndex
|
.map(nt => nt._1 + ": " + typeToTs(nt._2))
|
||||||
.map(_.swap)
|
|
||||||
.map(kv => "arg" + kv._1 + ": " + kv._2)
|
|
||||||
.mkString(", ")
|
.mkString(", ")
|
||||||
|
|
||||||
def argsCallToTs(at: ArrowType): String =
|
def argsCallToTs(at: ArrowType): String =
|
||||||
|
@ -2,10 +2,9 @@ package aqua.model
|
|||||||
|
|
||||||
import aqua.model.func.raw.{CallServiceTag, FuncOp}
|
import aqua.model.func.raw.{CallServiceTag, FuncOp}
|
||||||
import aqua.model.func.{ArgsCall, FuncCallable, FuncModel}
|
import aqua.model.func.{ArgsCall, FuncCallable, FuncModel}
|
||||||
import aqua.types.{ProductType, Type}
|
import aqua.types.{StructType, Type}
|
||||||
import cats.Monoid
|
import cats.Monoid
|
||||||
import cats.data.NonEmptyMap
|
import cats.data.NonEmptyMap
|
||||||
import cats.syntax.apply.*
|
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
import cats.syntax.monoid.*
|
import cats.syntax.monoid.*
|
||||||
import wvlet.log.LogSupport
|
import wvlet.log.LogSupport
|
||||||
@ -52,7 +51,7 @@ case class AquaContext(
|
|||||||
}
|
}
|
||||||
.map(prefixFirst(prefix, _))
|
.map(prefixFirst(prefix, _))
|
||||||
|
|
||||||
def `type`(name: String): Option[ProductType] =
|
def `type`(name: String): Option[StructType] =
|
||||||
NonEmptyMap
|
NonEmptyMap
|
||||||
.fromMap(
|
.fromMap(
|
||||||
SortedMap.from(
|
SortedMap.from(
|
||||||
@ -65,7 +64,7 @@ case class AquaContext(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.map(ProductType(name, _))
|
.map(StructType(name, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
object AquaContext extends LogSupport {
|
object AquaContext extends LogSupport {
|
||||||
@ -104,8 +103,8 @@ object AquaContext extends LogSupport {
|
|||||||
fnName,
|
fnName,
|
||||||
// TODO: capture ability resolution, get ID from the call context
|
// TODO: capture ability resolution, get ID from the call context
|
||||||
FuncOp.leaf(CallServiceTag(serviceId, fnName, call)),
|
FuncOp.leaf(CallServiceTag(serviceId, fnName, call)),
|
||||||
args,
|
arrowType,
|
||||||
(ret.map(_.model), arrowType.res).mapN(_ -> _),
|
ret.map(_.model),
|
||||||
Map.empty,
|
Map.empty,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package aqua.model
|
package aqua.model
|
||||||
|
|
||||||
import aqua.types.{ArrowType, ProductType}
|
import aqua.types.{ArrowType, StructType}
|
||||||
import cats.data.NonEmptyMap
|
import cats.data.NonEmptyMap
|
||||||
|
|
||||||
case class ServiceModel(
|
case class ServiceModel(
|
||||||
@ -8,5 +8,5 @@ case class ServiceModel(
|
|||||||
arrows: NonEmptyMap[String, ArrowType],
|
arrows: NonEmptyMap[String, ArrowType],
|
||||||
defaultId: Option[ValueModel]
|
defaultId: Option[ValueModel]
|
||||||
) extends Model {
|
) extends Model {
|
||||||
def `type`: ProductType = ProductType(name, arrows)
|
def `type`: StructType = StructType(name, arrows)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ object VarModel {
|
|||||||
|
|
||||||
val lastError: VarModel = VarModel(
|
val lastError: VarModel = VarModel(
|
||||||
"%last_error%",
|
"%last_error%",
|
||||||
ProductType(
|
StructType(
|
||||||
"LastError",
|
"LastError",
|
||||||
NonEmptyMap.of(
|
NonEmptyMap.of(
|
||||||
"instruction" -> ScalarType.string,
|
"instruction" -> ScalarType.string,
|
||||||
@ -112,6 +112,6 @@ object VarModel {
|
|||||||
|
|
||||||
val nil: VarModel = VarModel(
|
val nil: VarModel = VarModel(
|
||||||
"nil",
|
"nil",
|
||||||
StreamType(DataType.Bottom)
|
StreamType(BottomType)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package aqua.model.func
|
|
||||||
|
|
||||||
import aqua.types.{ArrowType, DataType, Type}
|
|
||||||
|
|
||||||
sealed abstract class ArgDef(val `type`: Type) {
|
|
||||||
def name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
object ArgDef {
|
|
||||||
case class Data(name: String, dataType: DataType) extends ArgDef(dataType)
|
|
||||||
case class Arrow(name: String, arrowType: ArrowType) extends ArgDef(arrowType)
|
|
||||||
}
|
|
@ -1,27 +1,26 @@
|
|||||||
package aqua.model.func
|
package aqua.model.func
|
||||||
|
|
||||||
import aqua.model.{ValueModel, VarModel}
|
import aqua.model.{ValueModel, VarModel}
|
||||||
import aqua.types.{ArrowType, DataType}
|
import aqua.types.{ArrowType, DataType, ProductType, Type}
|
||||||
import cats.syntax.functor.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps argument definitions of a function, along with values provided when this function is called
|
* Wraps argument definitions of a function, along with values provided when this function is called
|
||||||
* @param args Argument definitions
|
* @param args Argument definitions
|
||||||
* @param callWith Values provided for arguments
|
* @param callWith Values provided for arguments
|
||||||
*/
|
*/
|
||||||
case class ArgsCall(args: List[ArgDef], callWith: List[ValueModel]) {
|
case class ArgsCall(args: ProductType, callWith: List[ValueModel]) {
|
||||||
// Both arguments (arg names and types how they seen from the function body)
|
// Both arguments (arg names and types how they seen from the function body)
|
||||||
// and values (value models and types how they seen on the call site)
|
// and values (value models and types how they seen on the call site)
|
||||||
lazy val zipped: List[(ArgDef, ValueModel)] = args zip callWith
|
lazy val zipped: List[((String, Type), ValueModel)] = args.toLabelledList() zip callWith
|
||||||
|
|
||||||
lazy val dataArgs: Map[String, ValueModel] =
|
lazy val dataArgs: Map[String, ValueModel] =
|
||||||
zipped.collect { case (ArgDef.Data(name, _), value) =>
|
zipped.collect { case ((name, _: DataType), value) =>
|
||||||
name -> value
|
name -> value
|
||||||
}.toMap
|
}.toMap
|
||||||
|
|
||||||
def arrowArgs(arrowsInScope: Map[String, FuncCallable]): Map[String, FuncCallable] =
|
def arrowArgs(arrowsInScope: Map[String, FuncCallable]): Map[String, FuncCallable] =
|
||||||
zipped.collect {
|
zipped.collect {
|
||||||
case (ArgDef.Arrow(name, _), VarModel(value, _, _)) if arrowsInScope.contains(value) =>
|
case ((name, _: ArrowType), VarModel(value, _, _)) if arrowsInScope.contains(value) =>
|
||||||
name -> arrowsInScope(value)
|
name -> arrowsInScope(value)
|
||||||
}.toMap
|
}.toMap
|
||||||
}
|
}
|
||||||
@ -32,22 +31,18 @@ object ArgsCall {
|
|||||||
arrow: ArrowType,
|
arrow: ArrowType,
|
||||||
argPrefix: String = "arg",
|
argPrefix: String = "arg",
|
||||||
retName: String = "init_call_res"
|
retName: String = "init_call_res"
|
||||||
): (ArgsDef, Call, Option[Call.Export]) = {
|
): (ProductType, Call, List[Call.Export]) = {
|
||||||
val argNamesTypes = arrow.args.zipWithIndex.map { case (t, i) => (argPrefix + i, t) }
|
val argNamesTypes = arrow.domain.toLabelledList(argPrefix)
|
||||||
|
val res = arrow.codomain.toLabelledList(retName).map(Call.Export(_, _))
|
||||||
val argsDef = ArgsDef(argNamesTypes.map {
|
|
||||||
case (a, t: DataType) => ArgDef.Data(a, t)
|
|
||||||
case (a, t: ArrowType) => ArgDef.Arrow(a, t)
|
|
||||||
})
|
|
||||||
|
|
||||||
val call = Call(
|
val call = Call(
|
||||||
argNamesTypes.map { case (a, t) =>
|
argNamesTypes.map { case (a, t) =>
|
||||||
VarModel(a, t)
|
VarModel(a, t)
|
||||||
},
|
},
|
||||||
arrow.res.map(Call.Export(retName, _))
|
res
|
||||||
)
|
)
|
||||||
|
|
||||||
(argsDef, call, arrow.res.map(t => Call.Export(retName, t)))
|
(arrow.domain, call, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package aqua.model.func
|
|
||||||
|
|
||||||
import aqua.model.VarModel
|
|
||||||
import aqua.types.Type
|
|
||||||
import cats.data.Chain
|
|
||||||
|
|
||||||
case class ArgsDef(args: List[ArgDef]) {
|
|
||||||
def isEmpty: Boolean = args.isEmpty
|
|
||||||
|
|
||||||
def call(c: Call): ArgsCall = ArgsCall(args, c.args)
|
|
||||||
|
|
||||||
def types: List[Type] = args.map(_.`type`)
|
|
||||||
|
|
||||||
def toCallArgs: List[VarModel] = args.map(ad => VarModel(ad.name, ad.`type`))
|
|
||||||
|
|
||||||
lazy val dataArgs: Chain[ArgDef.Data] = Chain.fromSeq(args.collect { case ad: ArgDef.Data =>
|
|
||||||
ad
|
|
||||||
})
|
|
||||||
|
|
||||||
lazy val arrowArgs: Chain[ArgDef.Arrow] = Chain.fromSeq(args.collect { case ad: ArgDef.Arrow =>
|
|
||||||
ad
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
object ArgsDef {
|
|
||||||
val empty: ArgsDef = ArgsDef(Nil)
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ package aqua.model.func
|
|||||||
import aqua.model.{ValueModel, VarModel}
|
import aqua.model.{ValueModel, VarModel}
|
||||||
import aqua.types.Type
|
import aqua.types.Type
|
||||||
|
|
||||||
case class Call(args: List[ValueModel], exportTo: Option[Call.Export]) {
|
case class Call(args: List[ValueModel], exportTo: List[Call.Export]) {
|
||||||
|
|
||||||
def mapValues(f: ValueModel => ValueModel): Call =
|
def mapValues(f: ValueModel => ValueModel): Call =
|
||||||
Call(
|
Call(
|
||||||
@ -18,7 +18,7 @@ case class Call(args: List[ValueModel], exportTo: Option[Call.Export]) {
|
|||||||
}.toSet
|
}.toSet
|
||||||
|
|
||||||
override def toString: String =
|
override def toString: String =
|
||||||
s"[${args.mkString(" ")}]${exportTo.map(_.model).map(" " + _).getOrElse("")}"
|
s"[${args.mkString(" ")}]${exportTo.map(_.model).map(" " + _).mkString(",")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Call {
|
object Call {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package aqua.model.func
|
package aqua.model.func
|
||||||
|
|
||||||
import aqua.model.ValueModel.varName
|
import aqua.model.ValueModel.varName
|
||||||
import aqua.model.func.raw._
|
import aqua.model.func.raw.*
|
||||||
import aqua.model.{Model, ValueModel, VarModel}
|
import aqua.model.{Model, ValueModel, VarModel}
|
||||||
import aqua.types.{ArrowType, StreamType, Type}
|
import aqua.types.{ArrowType, ProductType, StreamType, Type}
|
||||||
import cats.Eval
|
import cats.Eval
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.free.Cofree
|
import cats.free.Cofree
|
||||||
@ -12,8 +12,8 @@ import wvlet.log.Logger
|
|||||||
case class FuncCallable(
|
case class FuncCallable(
|
||||||
funcName: String,
|
funcName: String,
|
||||||
body: FuncOp,
|
body: FuncOp,
|
||||||
args: ArgsDef,
|
arrowType: ArrowType,
|
||||||
ret: Option[(ValueModel, Type)],
|
ret: List[ValueModel],
|
||||||
capturedArrows: Map[String, FuncCallable],
|
capturedArrows: Map[String, FuncCallable],
|
||||||
capturedValues: Map[String, ValueModel]
|
capturedValues: Map[String, ValueModel]
|
||||||
) extends Model {
|
) extends Model {
|
||||||
@ -21,11 +21,8 @@ case class FuncCallable(
|
|||||||
private val logger = Logger.of[FuncCallable]
|
private val logger = Logger.of[FuncCallable]
|
||||||
import logger._
|
import logger._
|
||||||
|
|
||||||
def arrowType: ArrowType =
|
lazy val args: List[(String, Type)] = arrowType.domain.toLabelledList()
|
||||||
ArrowType(
|
lazy val argNames: List[String] = args.map(_._1)
|
||||||
args.types,
|
|
||||||
ret.map(_._2)
|
|
||||||
)
|
|
||||||
|
|
||||||
def findNewNames(forbidden: Set[String], introduce: Set[String]): Map[String, String] =
|
def findNewNames(forbidden: Set[String], introduce: Set[String]): Map[String, String] =
|
||||||
(forbidden intersect introduce).foldLeft(Map.empty[String, String]) { case (acc, name) =>
|
(forbidden intersect introduce).foldLeft(Map.empty[String, String]) { case (acc, name) =>
|
||||||
@ -49,12 +46,12 @@ case class FuncCallable(
|
|||||||
call: Call,
|
call: Call,
|
||||||
arrows: Map[String, FuncCallable],
|
arrows: Map[String, FuncCallable],
|
||||||
forbiddenNames: Set[String]
|
forbiddenNames: Set[String]
|
||||||
): Eval[(FuncOp, Option[ValueModel])] = {
|
): Eval[(FuncOp, List[ValueModel])] = {
|
||||||
|
|
||||||
debug("Call: " + call)
|
debug("Call: " + call)
|
||||||
|
|
||||||
// Collect all arguments: what names are used inside the function, what values are received
|
// Collect all arguments: what names are used inside the function, what values are received
|
||||||
val argsFull = args.call(call)
|
val argsFull = ArgsCall(arrowType.domain, call.args)
|
||||||
// DataType arguments
|
// DataType arguments
|
||||||
val argsToDataRaw = argsFull.dataArgs
|
val argsToDataRaw = argsFull.dataArgs
|
||||||
// Arrow arguments: expected type is Arrow, given by-name
|
// Arrow arguments: expected type is Arrow, given by-name
|
||||||
@ -96,7 +93,7 @@ case class FuncCallable(
|
|||||||
val treeRenamed = treeWithValues.rename(shouldRename)
|
val treeRenamed = treeWithValues.rename(shouldRename)
|
||||||
|
|
||||||
// Result could be derived from arguments, or renamed; take care about that
|
// Result could be derived from arguments, or renamed; take care about that
|
||||||
val result = ret.map(_._1).map(_.resolveWith(argsToData)).map {
|
val result: List[ValueModel] = ret.map(_.resolveWith(argsToData)).map {
|
||||||
case v: VarModel if shouldRename.contains(v.name) => v.copy(shouldRename(v.name))
|
case v: VarModel if shouldRename.contains(v.name) => v.copy(shouldRename(v.name))
|
||||||
case v => v
|
case v => v
|
||||||
}
|
}
|
||||||
@ -155,23 +152,22 @@ case class FuncCallable(
|
|||||||
}
|
}
|
||||||
.map { case ((_, resolvedExports), callableFuncBody) =>
|
.map { case ((_, resolvedExports), callableFuncBody) =>
|
||||||
// If return value is affected by any of internal functions, resolve it
|
// If return value is affected by any of internal functions, resolve it
|
||||||
(for {
|
val resolvedResult = result.map(_.resolveWith(resolvedExports))
|
||||||
exp <- call.exportTo
|
|
||||||
res <- result
|
val (ops, rets) = (call.exportTo zip resolvedResult)
|
||||||
pair <- exp match {
|
.map[(Option[FuncOp], ValueModel)] {
|
||||||
case Call.Export(name, StreamType(_)) =>
|
case (exp @ Call.Export(_, StreamType(_)), res) =>
|
||||||
val resolved = res.resolveWith(resolvedExports)
|
// pass nested function results to a stream
|
||||||
// path nested function results to a stream
|
Some(FuncOps.identity(res, exp)) -> exp.model
|
||||||
Some(
|
case (_, res) =>
|
||||||
FuncOps.seq(FuncOp(callableFuncBody), FuncOps.identity(resolved, exp)) -> Some(
|
None -> res
|
||||||
exp.model
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case _ => None
|
|
||||||
}
|
}
|
||||||
} yield {
|
.foldLeft[(List[FuncOp], List[ValueModel])]((FuncOp(callableFuncBody) :: Nil, Nil)) {
|
||||||
pair
|
case ((ops, rets), (Some(fo), r)) => (fo :: ops, r :: rets)
|
||||||
}).getOrElse(FuncOp(callableFuncBody) -> result.map(_.resolveWith(resolvedExports)))
|
case ((ops, rets), (_, r)) => (ops, r :: rets)
|
||||||
|
}
|
||||||
|
|
||||||
|
FuncOps.seq(ops.reverse: _*) -> rets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ package aqua.model.func
|
|||||||
|
|
||||||
import aqua.model.func.raw.FuncOp
|
import aqua.model.func.raw.FuncOp
|
||||||
import aqua.model.{Model, ValueModel}
|
import aqua.model.{Model, ValueModel}
|
||||||
import aqua.types.Type
|
import aqua.types.ArrowType
|
||||||
|
|
||||||
case class FuncModel(
|
case class FuncModel(
|
||||||
name: String,
|
name: String,
|
||||||
args: ArgsDef,
|
arrowType: ArrowType,
|
||||||
ret: Option[(ValueModel, Type)],
|
ret: List[ValueModel],
|
||||||
body: FuncOp
|
body: FuncOp
|
||||||
) extends Model {
|
) extends Model {
|
||||||
|
|
||||||
@ -15,6 +15,6 @@ case class FuncModel(
|
|||||||
arrows: Map[String, FuncCallable],
|
arrows: Map[String, FuncCallable],
|
||||||
constants: Map[String, ValueModel]
|
constants: Map[String, ValueModel]
|
||||||
): FuncCallable =
|
): FuncCallable =
|
||||||
FuncCallable(name, body.fixXorPar, args, ret, arrows, constants)
|
FuncCallable(name, body.fixXorPar, arrowType, ret, arrows, constants)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,19 +23,19 @@ case class FuncOp(tree: Cofree[Chain, RawTag]) extends Model {
|
|||||||
Cofree.cata(tree)(folder)
|
Cofree.cata(tree)(folder)
|
||||||
|
|
||||||
def definesVarNames: Eval[Set[String]] = cata[Set[String]] {
|
def definesVarNames: Eval[Set[String]] = cata[Set[String]] {
|
||||||
case (CallArrowTag(_, Call(_, Some(exportTo))), acc) =>
|
case (CallArrowTag(_, Call(_, exportTo)), acc) if exportTo.nonEmpty =>
|
||||||
Eval.later(acc.foldLeft(Set(exportTo.name))(_ ++ _))
|
Eval.later(acc.foldLeft(exportTo.map(_.name).toSet)(_ ++ _))
|
||||||
case (CallServiceTag(_, _, Call(_, Some(exportTo))), acc) =>
|
case (CallServiceTag(_, _, Call(_, exportTo)), acc) if exportTo.nonEmpty =>
|
||||||
Eval.later(acc.foldLeft(Set(exportTo.name))(_ ++ _))
|
Eval.later(acc.foldLeft(exportTo.map(_.name).toSet)(_ ++ _))
|
||||||
case (NextTag(exportTo), acc) => Eval.later(acc.foldLeft(Set(exportTo))(_ ++ _))
|
case (NextTag(exportTo), acc) => Eval.later(acc.foldLeft(Set(exportTo))(_ ++ _))
|
||||||
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
|
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
|
||||||
}
|
}
|
||||||
|
|
||||||
def exportsVarNames: Eval[Set[String]] = cata[Set[String]] {
|
def exportsVarNames: Eval[Set[String]] = cata[Set[String]] {
|
||||||
case (CallArrowTag(_, Call(_, Some(exportTo))), acc) =>
|
case (CallArrowTag(_, Call(_, exportTo)), acc) if exportTo.nonEmpty =>
|
||||||
Eval.later(acc.foldLeft(Set(exportTo.name))(_ ++ _))
|
Eval.later(acc.foldLeft(exportTo.map(_.name).toSet)(_ ++ _))
|
||||||
case (CallServiceTag(_, _, Call(_, Some(exportTo))), acc) =>
|
case (CallServiceTag(_, _, Call(_, exportTo)), acc) if exportTo.nonEmpty =>
|
||||||
Eval.later(acc.foldLeft(Set(exportTo.name))(_ ++ _))
|
Eval.later(acc.foldLeft(exportTo.map(_.name).toSet)(_ ++ _))
|
||||||
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
|
case (_, acc) => Eval.later(acc.foldLeft(Set.empty[String])(_ ++ _))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@ import cats.free.Cofree
|
|||||||
object FuncOps {
|
object FuncOps {
|
||||||
|
|
||||||
def noop: FuncOp =
|
def noop: FuncOp =
|
||||||
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None)))
|
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, Nil)))
|
||||||
|
|
||||||
def identity(what: ValueModel, to: Call.Export): FuncOp =
|
def identity(what: ValueModel, to: Call.Export): FuncOp =
|
||||||
FuncOp.leaf(
|
FuncOp.leaf(
|
||||||
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to)))
|
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, to :: Nil))
|
||||||
)
|
)
|
||||||
|
|
||||||
def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
|
def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package aqua.model.func.resolved
|
||||||
|
|
||||||
|
import aqua.model.ValueModel
|
||||||
|
import aqua.model.func.Call
|
||||||
|
|
||||||
|
case class CallRes(args: List[ValueModel], exportTo: Option[Call.Export])
|
@ -1,6 +1,17 @@
|
|||||||
package aqua.model.func.resolved
|
package aqua.model.func.resolved
|
||||||
|
|
||||||
import aqua.model.func.Call
|
import aqua.model.func.Call
|
||||||
|
import aqua.model.func.raw.{
|
||||||
|
CallServiceTag,
|
||||||
|
ForTag,
|
||||||
|
MatchMismatchTag,
|
||||||
|
NextTag,
|
||||||
|
OnTag,
|
||||||
|
ParTag,
|
||||||
|
RawTag,
|
||||||
|
SeqTag,
|
||||||
|
XorTag
|
||||||
|
}
|
||||||
import aqua.model.topology.Topology.Res
|
import aqua.model.topology.Topology.Res
|
||||||
import aqua.model.{LiteralModel, ValueModel}
|
import aqua.model.{LiteralModel, ValueModel}
|
||||||
import cats.Eval
|
import cats.Eval
|
||||||
@ -28,5 +39,25 @@ object MakeRes {
|
|||||||
Cofree[Chain, ResolvedOp](FoldRes(item, iter), Eval.now(Chain.one(body)))
|
Cofree[Chain, ResolvedOp](FoldRes(item, iter), Eval.now(Chain.one(body)))
|
||||||
|
|
||||||
def noop(onPeer: ValueModel): Res =
|
def noop(onPeer: ValueModel): Res =
|
||||||
leaf(CallServiceRes(LiteralModel.quote("op"), "noop", Call(Nil, None), onPeer))
|
leaf(CallServiceRes(LiteralModel.quote("op"), "noop", CallRes(Nil, None), onPeer))
|
||||||
|
|
||||||
|
def resolve(
|
||||||
|
currentPeerId: Option[ValueModel]
|
||||||
|
): PartialFunction[RawTag, ResolvedOp] = {
|
||||||
|
case SeqTag => SeqRes
|
||||||
|
case _: OnTag => SeqRes
|
||||||
|
case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s)
|
||||||
|
case ForTag(item, iter) => FoldRes(item, iter)
|
||||||
|
case ParTag | ParTag.Detach => ParRes
|
||||||
|
case XorTag | XorTag.LeftBiased => XorRes
|
||||||
|
case NextTag(item) => NextRes(item)
|
||||||
|
case CallServiceTag(serviceId, funcName, Call(args, exportTo)) =>
|
||||||
|
CallServiceRes(
|
||||||
|
serviceId,
|
||||||
|
funcName,
|
||||||
|
CallRes(args, exportTo.headOption),
|
||||||
|
currentPeerId
|
||||||
|
.getOrElse(LiteralModel.initPeerId)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ case class AbilityIdRes(
|
|||||||
case class CallServiceRes(
|
case class CallServiceRes(
|
||||||
serviceId: ValueModel,
|
serviceId: ValueModel,
|
||||||
funcName: String,
|
funcName: String,
|
||||||
call: Call,
|
call: CallRes,
|
||||||
peerId: ValueModel
|
peerId: ValueModel
|
||||||
) extends ResolvedOp {
|
) extends ResolvedOp {
|
||||||
override def toString: String = s"(call $peerId ($serviceId $funcName) $call)"
|
override def toString: String = s"(call $peerId ($serviceId $funcName) $call)"
|
||||||
|
@ -46,32 +46,14 @@ object Topology extends LogSupport {
|
|||||||
else cz.current
|
else cz.current
|
||||||
)
|
)
|
||||||
|
|
||||||
private def rawToResolved(
|
|
||||||
currentPeerId: Option[ValueModel]
|
|
||||||
): PartialFunction[RawTag, ResolvedOp] = {
|
|
||||||
case SeqTag => SeqRes
|
|
||||||
case _: OnTag => SeqRes
|
|
||||||
case MatchMismatchTag(a, b, s) => MatchMismatchRes(a, b, s)
|
|
||||||
case ForTag(item, iter) => FoldRes(item, iter)
|
|
||||||
case ParTag | ParTag.Detach => ParRes
|
|
||||||
case XorTag | XorTag.LeftBiased => XorRes
|
|
||||||
case NextTag(item) => NextRes(item)
|
|
||||||
case CallServiceTag(serviceId, funcName, call) =>
|
|
||||||
CallServiceRes(
|
|
||||||
serviceId,
|
|
||||||
funcName,
|
|
||||||
call,
|
|
||||||
currentPeerId
|
|
||||||
.getOrElse(LiteralModel.initPeerId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def resolveOnMoves(op: Tree): Eval[Res] = {
|
def resolveOnMoves(op: Tree): Eval[Res] = {
|
||||||
val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
|
val cursor = RawCursor(NonEmptyList.one(ChainZipper.one(op)))
|
||||||
val resolvedCofree = cursor
|
val resolvedCofree = cursor
|
||||||
.cata(wrap) { rc =>
|
.cata(wrap) { rc =>
|
||||||
debug(s"<:> $rc")
|
debug(s"<:> $rc")
|
||||||
val resolved = rawToResolved(rc.currentPeerId).lift
|
val resolved = MakeRes
|
||||||
|
.resolve(rc.currentPeerId)
|
||||||
|
.lift
|
||||||
.apply(rc.tag)
|
.apply(rc.tag)
|
||||||
.map(MakeRes.leaf)
|
.map(MakeRes.leaf)
|
||||||
val chainZipperEv = resolved.traverse(cofree =>
|
val chainZipperEv = resolved.traverse(cofree =>
|
||||||
|
@ -20,7 +20,7 @@ case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataT
|
|||||||
FuncOps.callService(
|
FuncOps.callService(
|
||||||
dataServiceId,
|
dataServiceId,
|
||||||
name,
|
name,
|
||||||
Call(Nil, Some(Call.Export(iter, ArrayType(t.element))))
|
Call(Nil, Call.Export(iter, ArrayType(t.element)) :: Nil)
|
||||||
),
|
),
|
||||||
FuncOps.fold(
|
FuncOps.fold(
|
||||||
item,
|
item,
|
||||||
@ -41,7 +41,7 @@ case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataT
|
|||||||
FuncOps.callService(
|
FuncOps.callService(
|
||||||
dataServiceId,
|
dataServiceId,
|
||||||
name,
|
name,
|
||||||
Call(Nil, Some(Call.Export(name, t)))
|
Call(Nil, Call.Export(name, t) :: Nil)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,6 @@ object ErrorsCatcher {
|
|||||||
|
|
||||||
def lastErrorCall(i: Int): Call = Call(
|
def lastErrorCall(i: Int): Call = Call(
|
||||||
lastErrorArg :: LiteralModel(i.toString, LiteralType.number) :: Nil,
|
lastErrorArg :: LiteralModel(i.toString, LiteralType.number) :: Nil,
|
||||||
None
|
Nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package aqua.model.transform
|
package aqua.model.transform
|
||||||
|
|
||||||
import aqua.model.func._
|
import aqua.model.func.*
|
||||||
import aqua.model.func.raw.{FuncOp, FuncOps}
|
import aqua.model.func.raw.{FuncOp, FuncOps}
|
||||||
import aqua.model.{ValueModel, VarModel}
|
import aqua.model.{ValueModel, VarModel}
|
||||||
import aqua.types.{ArrayType, ArrowType, StreamType}
|
import aqua.types.{ArrayType, ArrowType, ConsType, NilType, ProductType, StreamType}
|
||||||
import cats.Eval
|
import cats.Eval
|
||||||
import cats.syntax.apply._
|
|
||||||
|
|
||||||
case class ResolveFunc(
|
case class ResolveFunc(
|
||||||
transform: FuncOp => FuncOp,
|
transform: FuncOp => FuncOp,
|
||||||
@ -22,7 +21,7 @@ case class ResolveFunc(
|
|||||||
respFuncName,
|
respFuncName,
|
||||||
Call(
|
Call(
|
||||||
retModel :: Nil,
|
retModel :: Nil,
|
||||||
None
|
Nil
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,19 +30,19 @@ case class ResolveFunc(
|
|||||||
FuncCallable(
|
FuncCallable(
|
||||||
arrowCallbackPrefix + name,
|
arrowCallbackPrefix + name,
|
||||||
callback(name, call),
|
callback(name, call),
|
||||||
args,
|
arrowType,
|
||||||
(ret.map(_.model), arrowType.res).mapN(_ -> _),
|
ret.map(_.model),
|
||||||
Map.empty,
|
Map.empty,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def wrap(func: FuncCallable): FuncCallable = {
|
def wrap(func: FuncCallable): FuncCallable = {
|
||||||
val returnType = func.ret.map(_._1.lastType).map {
|
val returnType = ProductType(func.ret.map(_.lastType).map {
|
||||||
// we mustn't return a stream in response callback to avoid pushing stream to `-return-` value
|
// we mustn't return a stream in response callback to avoid pushing stream to `-return-` value
|
||||||
case StreamType(t) => ArrayType(t)
|
case StreamType(t) => ArrayType(t)
|
||||||
case t => t
|
case t => t
|
||||||
}
|
}).toLabelledList(returnVar)
|
||||||
|
|
||||||
FuncCallable(
|
FuncCallable(
|
||||||
wrapCallableName,
|
wrapCallableName,
|
||||||
@ -53,21 +52,22 @@ case class ResolveFunc(
|
|||||||
.callArrow(
|
.callArrow(
|
||||||
func.funcName,
|
func.funcName,
|
||||||
Call(
|
Call(
|
||||||
func.args.toCallArgs,
|
func.arrowType.domain.toLabelledList().map(ad => VarModel(ad._1, ad._2)),
|
||||||
returnType.map(t => Call.Export(returnVar, t))
|
returnType.map { case (l, t) => Call.Export(l, t) }
|
||||||
)
|
)
|
||||||
) ::
|
) ::
|
||||||
returnType
|
returnType.map { case (l, t) => VarModel(l, t) }
|
||||||
.map(t => VarModel(returnVar, t))
|
.map(returnCallback): _*
|
||||||
.map(returnCallback)
|
|
||||||
.toList: _*
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
ArgsDef(ArgDef.Arrow(func.funcName, func.arrowType) :: Nil),
|
ArrowType(ConsType.cons(func.funcName, func.arrowType, NilType), NilType),
|
||||||
None,
|
Nil,
|
||||||
func.args.arrowArgs.map { case ArgDef.Arrow(argName, arrowType) =>
|
func.arrowType.domain
|
||||||
argName -> arrowToCallback(argName, arrowType)
|
.toLabelledList()
|
||||||
}.toList.toMap,
|
.collect { case (argName, arrowType: ArrowType) =>
|
||||||
|
argName -> arrowToCallback(argName, arrowType)
|
||||||
|
}
|
||||||
|
.toMap,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ case class ResolveFunc(
|
|||||||
): Eval[FuncOp] =
|
): Eval[FuncOp] =
|
||||||
wrap(func)
|
wrap(func)
|
||||||
.resolve(
|
.resolve(
|
||||||
Call(VarModel(funcArgName, func.arrowType) :: Nil, None),
|
Call(VarModel(funcArgName, func.arrowType) :: Nil, Nil),
|
||||||
Map(funcArgName -> func),
|
Map(funcArgName -> func),
|
||||||
Set.empty
|
Set.empty
|
||||||
)
|
)
|
||||||
|
@ -35,9 +35,7 @@ object Transform extends LogSupport {
|
|||||||
val argsProvider: ArgsProvider =
|
val argsProvider: ArgsProvider =
|
||||||
ArgsFromService(
|
ArgsFromService(
|
||||||
conf.dataSrvId,
|
conf.dataSrvId,
|
||||||
conf.relayVarName.map(_ -> ScalarType.string).toList ::: func.args.dataArgs.toList.map(
|
conf.relayVarName.map(_ -> ScalarType.string).toList ::: func.arrowType.domain.labelledData
|
||||||
add => add.name -> add.dataType
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val transform =
|
val transform =
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
import aqua.model.func.Call
|
import aqua.model.func.Call
|
||||||
import aqua.model.func.raw._
|
import aqua.model.func.raw.*
|
||||||
import aqua.model.func.resolved.{CallServiceRes, MakeRes, MatchMismatchRes, ResolvedOp}
|
import aqua.model.func.resolved.{CallRes, CallServiceRes, MakeRes, MatchMismatchRes, ResolvedOp}
|
||||||
import aqua.model.transform.{ErrorsCatcher, GenerationConfig}
|
import aqua.model.transform.{ErrorsCatcher, GenerationConfig}
|
||||||
import aqua.model.{LiteralModel, ValueModel, VarModel}
|
import aqua.model.{LiteralModel, ValueModel, VarModel}
|
||||||
import aqua.types.{ArrayType, LiteralType, ScalarType}
|
import aqua.types.{ArrayType, LiteralType, ScalarType}
|
||||||
@ -48,7 +48,7 @@ object Node {
|
|||||||
val relay = LiteralModel("-relay-", ScalarType.string)
|
val relay = LiteralModel("-relay-", ScalarType.string)
|
||||||
val relayV = VarModel("-relay-", ScalarType.string)
|
val relayV = VarModel("-relay-", ScalarType.string)
|
||||||
val initPeer = LiteralModel.initPeerId
|
val initPeer = LiteralModel.initPeerId
|
||||||
val emptyCall = Call(Nil, None)
|
val emptyCall = Call(Nil, Nil)
|
||||||
val otherPeer = LiteralModel("other-peer", ScalarType.string)
|
val otherPeer = LiteralModel("other-peer", ScalarType.string)
|
||||||
val otherPeerL = LiteralModel("\"other-peer\"", LiteralType.string)
|
val otherPeerL = LiteralModel("\"other-peer\"", LiteralType.string)
|
||||||
val otherRelay = LiteralModel("other-relay", ScalarType.string)
|
val otherRelay = LiteralModel("other-relay", ScalarType.string)
|
||||||
@ -63,10 +63,10 @@ object Node {
|
|||||||
exportTo: Option[Call.Export] = None,
|
exportTo: Option[Call.Export] = None,
|
||||||
args: List[ValueModel] = Nil
|
args: List[ValueModel] = Nil
|
||||||
): Res = Node(
|
): Res = Node(
|
||||||
CallServiceRes(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo), on)
|
CallServiceRes(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", CallRes(args, exportTo), on)
|
||||||
)
|
)
|
||||||
|
|
||||||
def callTag(i: Int, exportTo: Option[Call.Export] = None, args: List[ValueModel] = Nil): Raw =
|
def callTag(i: Int, exportTo: List[Call.Export] = Nil, args: List[ValueModel] = Nil): Raw =
|
||||||
Node(
|
Node(
|
||||||
CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo))
|
CallServiceTag(LiteralModel(s"srv$i", ScalarType.string), s"fn$i", Call(args, exportTo))
|
||||||
)
|
)
|
||||||
@ -75,12 +75,12 @@ object Node {
|
|||||||
CallServiceRes(
|
CallServiceRes(
|
||||||
LiteralModel("\"srv" + i + "\"", LiteralType.string),
|
LiteralModel("\"srv" + i + "\"", LiteralType.string),
|
||||||
s"fn$i",
|
s"fn$i",
|
||||||
Call(Nil, exportTo),
|
CallRes(Nil, exportTo),
|
||||||
on
|
on
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def callLiteralRaw(i: Int, exportTo: Option[Call.Export] = None): Raw = Node(
|
def callLiteralRaw(i: Int, exportTo: List[Call.Export] = Nil): Raw = Node(
|
||||||
CallServiceTag(
|
CallServiceTag(
|
||||||
LiteralModel("\"srv" + i + "\"", LiteralType.string),
|
LiteralModel("\"srv" + i + "\"", LiteralType.string),
|
||||||
s"fn$i",
|
s"fn$i",
|
||||||
@ -92,7 +92,7 @@ object Node {
|
|||||||
CallServiceRes(
|
CallServiceRes(
|
||||||
bc.errorHandlingCallback,
|
bc.errorHandlingCallback,
|
||||||
bc.errorFuncName,
|
bc.errorFuncName,
|
||||||
Call(
|
CallRes(
|
||||||
ErrorsCatcher.lastErrorArg :: LiteralModel(
|
ErrorsCatcher.lastErrorArg :: LiteralModel(
|
||||||
i.toString,
|
i.toString,
|
||||||
LiteralType.number
|
LiteralType.number
|
||||||
@ -108,7 +108,7 @@ object Node {
|
|||||||
CallServiceRes(
|
CallServiceRes(
|
||||||
bc.callbackSrvId,
|
bc.callbackSrvId,
|
||||||
bc.respFuncName,
|
bc.respFuncName,
|
||||||
Call(value :: Nil, None),
|
CallRes(value :: Nil, None),
|
||||||
on
|
on
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ object Node {
|
|||||||
CallServiceRes(
|
CallServiceRes(
|
||||||
bc.dataSrvId,
|
bc.dataSrvId,
|
||||||
name,
|
name,
|
||||||
Call(Nil, Some(Call.Export(name, ScalarType.string))),
|
CallRes(Nil, Some(Call.Export(name, ScalarType.string))),
|
||||||
on
|
on
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -156,7 +156,7 @@ object Node {
|
|||||||
Console.GREEN + "(" +
|
Console.GREEN + "(" +
|
||||||
equalOrNot(left, right) + Console.GREEN + ")"
|
equalOrNot(left, right) + Console.GREEN + ")"
|
||||||
|
|
||||||
private def diffCall(left: Call, right: Call): String =
|
private def diffCall(left: CallRes, right: CallRes): String =
|
||||||
if (left == right) Console.GREEN + left + Console.RESET
|
if (left == right) Console.GREEN + left + Console.RESET
|
||||||
else
|
else
|
||||||
Console.GREEN + "Call(" +
|
Console.GREEN + "Call(" +
|
||||||
|
@ -90,7 +90,7 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"topology resolver" should "build return path in par if there are exported variables" in {
|
"topology resolver" should "build return path in par if there are exported variables" in {
|
||||||
val exportTo = Some(Call.Export("result", ScalarType.string))
|
val exportTo = Call.Export("result", ScalarType.string) :: Nil
|
||||||
val result = VarModel("result", ScalarType.string)
|
val result = VarModel("result", ScalarType.string)
|
||||||
|
|
||||||
val init = on(
|
val init = on(
|
||||||
@ -105,7 +105,7 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
),
|
),
|
||||||
callTag(2)
|
callTag(2)
|
||||||
),
|
),
|
||||||
callTag(3, None, result :: Nil)
|
callTag(3, Nil, result :: Nil)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class TopologySpec extends AnyFlatSpec with Matchers {
|
|||||||
MakeRes.seq(
|
MakeRes.seq(
|
||||||
through(relay),
|
through(relay),
|
||||||
through(otherRelay),
|
through(otherRelay),
|
||||||
callRes(1, otherPeer, exportTo),
|
callRes(1, otherPeer, exportTo.headOption),
|
||||||
through(otherRelay),
|
through(otherRelay),
|
||||||
through(relay),
|
through(relay),
|
||||||
// we should return to a caller to continue execution
|
// we should return to a caller to continue execution
|
||||||
|
@ -2,16 +2,18 @@ package aqua.model.transform
|
|||||||
|
|
||||||
import aqua.Node
|
import aqua.Node
|
||||||
import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp, FuncOps}
|
import aqua.model.func.raw.{CallArrowTag, CallServiceTag, FuncOp, FuncOps}
|
||||||
import aqua.model.func.resolved.{CallServiceRes, MakeRes}
|
import aqua.model.func.resolved.{CallRes, CallServiceRes, MakeRes}
|
||||||
import aqua.model.func.{ArgsDef, Call, FuncCallable}
|
import aqua.model.func.{Call, FuncCallable}
|
||||||
import aqua.model.{LiteralModel, VarModel}
|
import aqua.model.{LiteralModel, VarModel}
|
||||||
import aqua.types.ScalarType
|
import aqua.types.{ArrowType, NilType, ProductType, ScalarType}
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
class TransformSpec extends AnyFlatSpec with Matchers {
|
class TransformSpec extends AnyFlatSpec with Matchers {
|
||||||
import Node._
|
import Node._
|
||||||
|
|
||||||
|
val stringArrow: ArrowType = ArrowType(NilType, ProductType(ScalarType.string :: Nil))
|
||||||
|
|
||||||
"transform.forClient" should "work well with function 1 (no calls before on), generate correct error handling" in {
|
"transform.forClient" should "work well with function 1 (no calls before on), generate correct error handling" in {
|
||||||
|
|
||||||
val ret = LiteralModel.quote("return this")
|
val ret = LiteralModel.quote("return this")
|
||||||
@ -20,8 +22,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
|
|||||||
FuncCallable(
|
FuncCallable(
|
||||||
"ret",
|
"ret",
|
||||||
on(otherPeer, otherRelay :: Nil, callTag(1)),
|
on(otherPeer, otherRelay :: Nil, callTag(1)),
|
||||||
ArgsDef.empty,
|
stringArrow,
|
||||||
Some((ret, ScalarType.string)),
|
ret :: Nil,
|
||||||
Map.empty,
|
Map.empty,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
@ -70,8 +72,8 @@ class TransformSpec extends AnyFlatSpec with Matchers {
|
|||||||
val func: FuncCallable = FuncCallable(
|
val func: FuncCallable = FuncCallable(
|
||||||
"ret",
|
"ret",
|
||||||
FuncOps.seq(callTag(0), on(otherPeer, Nil, callTag(1))),
|
FuncOps.seq(callTag(0), on(otherPeer, Nil, callTag(1))),
|
||||||
ArgsDef.empty,
|
stringArrow,
|
||||||
Some((ret, ScalarType.string)),
|
ret :: Nil,
|
||||||
Map.empty,
|
Map.empty,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
@ -115,12 +117,12 @@ class TransformSpec extends AnyFlatSpec with Matchers {
|
|||||||
CallServiceTag(
|
CallServiceTag(
|
||||||
LiteralModel.quote("srv1"),
|
LiteralModel.quote("srv1"),
|
||||||
"foo",
|
"foo",
|
||||||
Call(Nil, Some(Call.Export("v", ScalarType.string)))
|
Call(Nil, Call.Export("v", ScalarType.string) :: Nil)
|
||||||
)
|
)
|
||||||
).cof
|
).cof
|
||||||
),
|
),
|
||||||
ArgsDef.empty,
|
stringArrow,
|
||||||
Some((VarModel("v", ScalarType.string), ScalarType.string)),
|
VarModel("v", ScalarType.string) :: Nil,
|
||||||
Map.empty,
|
Map.empty,
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
@ -129,10 +131,10 @@ class TransformSpec extends AnyFlatSpec with Matchers {
|
|||||||
FuncCallable(
|
FuncCallable(
|
||||||
"f2",
|
"f2",
|
||||||
FuncOp(
|
FuncOp(
|
||||||
Node(CallArrowTag("callable", Call(Nil, Some(Call.Export("v", ScalarType.string))))).cof
|
Node(CallArrowTag("callable", Call(Nil, Call.Export("v", ScalarType.string) :: Nil))).cof
|
||||||
),
|
),
|
||||||
ArgsDef.empty,
|
stringArrow,
|
||||||
Some((VarModel("v", ScalarType.string), ScalarType.string)),
|
VarModel("v", ScalarType.string) :: Nil,
|
||||||
Map("callable" -> f1),
|
Map("callable" -> f1),
|
||||||
Map.empty
|
Map.empty
|
||||||
)
|
)
|
||||||
@ -148,7 +150,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
|
|||||||
CallServiceRes(
|
CallServiceRes(
|
||||||
LiteralModel.quote("srv1"),
|
LiteralModel.quote("srv1"),
|
||||||
"foo",
|
"foo",
|
||||||
Call(Nil, Some(Call.Export("v", ScalarType.string))),
|
CallRes(Nil, Some(Call.Export("v", ScalarType.string))),
|
||||||
initPeer
|
initPeer
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -68,6 +68,7 @@ object Token {
|
|||||||
val `[]` : P[Unit] = P.string("[]")
|
val `[]` : P[Unit] = P.string("[]")
|
||||||
val `⊤` : P[Unit] = P.char('⊤')
|
val `⊤` : P[Unit] = P.char('⊤')
|
||||||
val `⊥` : P[Unit] = P.char('⊥')
|
val `⊥` : P[Unit] = P.char('⊥')
|
||||||
|
val `∅` : P[Unit] = P.char('∅')
|
||||||
val `(` : P[Unit] = P.char('(').surroundedBy(` `.?)
|
val `(` : P[Unit] = P.char('(').surroundedBy(` `.?)
|
||||||
val `)` : P[Unit] = P.char(')').surroundedBy(` `.?)
|
val `)` : P[Unit] = P.char(')').surroundedBy(` `.?)
|
||||||
val `()` : P[Unit] = P.string("()")
|
val `()` : P[Unit] = P.string("()")
|
||||||
|
@ -113,7 +113,8 @@ object DataTypeToken {
|
|||||||
(`[]`.lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
|
(`[]`.lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
|
||||||
|
|
||||||
def `topbottomdef`[F[_]: LiftParser: Comonad]: P[TopBottomToken[F]] =
|
def `topbottomdef`[F[_]: LiftParser: Comonad]: P[TopBottomToken[F]] =
|
||||||
`⊥`.lift.map(TopBottomToken(_, isTop = false)) | `⊤`.lift.map(TopBottomToken(_, isTop = true))
|
`⊥`.lift.map(TopBottomToken(_, isTop = false)) |
|
||||||
|
`⊤`.lift.map(TopBottomToken(_, isTop = true))
|
||||||
|
|
||||||
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
|
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
|
||||||
P.oneOf(
|
P.oneOf(
|
||||||
|
@ -83,18 +83,18 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
|
|||||||
case _ =>
|
case _ =>
|
||||||
Free.pure[Alg, Option[Call.Export]](None)
|
Free.pure[Alg, Option[Call.Export]](None)
|
||||||
|
|
||||||
}).map(call =>
|
}).map(maybeExport =>
|
||||||
FuncOp.leaf(serviceId match {
|
FuncOp.leaf(serviceId match {
|
||||||
case Some(sid) =>
|
case Some(sid) =>
|
||||||
CallServiceTag(
|
CallServiceTag(
|
||||||
serviceId = sid,
|
serviceId = sid,
|
||||||
funcName = funcName.value,
|
funcName = funcName.value,
|
||||||
Call(argsResolved, call)
|
Call(argsResolved, maybeExport.toList)
|
||||||
)
|
)
|
||||||
case None =>
|
case None =>
|
||||||
CallArrowTag(
|
CallArrowTag(
|
||||||
funcName = funcName.value,
|
funcName = funcName.value,
|
||||||
Call(argsResolved, call)
|
Call(argsResolved, maybeExport.toList)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ import aqua.parser.expr.DataStructExpr
|
|||||||
import aqua.semantics.Prog
|
import aqua.semantics.Prog
|
||||||
import aqua.semantics.rules.names.NamesAlgebra
|
import aqua.semantics.rules.names.NamesAlgebra
|
||||||
import aqua.semantics.rules.types.TypesAlgebra
|
import aqua.semantics.rules.types.TypesAlgebra
|
||||||
import aqua.types.ProductType
|
import aqua.types.StructType
|
||||||
import cats.free.Free
|
import cats.free.Free
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class DataStructSem[F[_]](val expr: DataStructExpr[F]) extends AnyVal {
|
|||||||
case Some(fields) =>
|
case Some(fields) =>
|
||||||
T.defineDataType(expr.name, fields) as (TypeModel(
|
T.defineDataType(expr.name, fields) as (TypeModel(
|
||||||
expr.name.value,
|
expr.name.value,
|
||||||
ProductType(expr.name.value, fields)
|
StructType(expr.name.value, fields)
|
||||||
): Model)
|
): Model)
|
||||||
case None => Free.pure[Alg, Model](Model.error("Data struct types unresolved"))
|
case None => Free.pure[Alg, Model](Model.error("Data struct types unresolved"))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package aqua.semantics.expr
|
package aqua.semantics.expr
|
||||||
|
|
||||||
import aqua.model.func.raw.{FuncOp, FuncOps}
|
import aqua.model.func.raw.{FuncOp, FuncOps}
|
||||||
import aqua.model.func.{ArgDef, ArgsDef, FuncModel}
|
import aqua.model.func.FuncModel
|
||||||
import aqua.model.{Model, ReturnModel, ValueModel}
|
import aqua.model.{Model, ReturnModel, ValueModel}
|
||||||
import aqua.parser.expr.FuncExpr
|
import aqua.parser.expr.FuncExpr
|
||||||
import aqua.parser.lexer.Arg
|
import aqua.parser.lexer.Arg
|
||||||
@ -10,7 +10,7 @@ import aqua.semantics.rules.ValuesAlgebra
|
|||||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||||
import aqua.semantics.rules.names.NamesAlgebra
|
import aqua.semantics.rules.names.NamesAlgebra
|
||||||
import aqua.semantics.rules.types.TypesAlgebra
|
import aqua.semantics.rules.types.TypesAlgebra
|
||||||
import aqua.types.{ArrowType, DataType, Type}
|
import aqua.types.{ArrowType, ProductType, Type}
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.free.Free
|
import cats.free.Free
|
||||||
@ -32,15 +32,15 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
|
|||||||
args
|
args
|
||||||
.foldLeft(
|
.foldLeft(
|
||||||
// Begin scope -- for mangling
|
// Begin scope -- for mangling
|
||||||
N.beginScope(name).as[Chain[Type]](Chain.empty)
|
N.beginScope(name).as[Chain[(String, Type)]](Chain.empty)
|
||||||
) { case (f, Arg(argName, argType)) =>
|
) { case (f, Arg(argName, argType)) =>
|
||||||
// Resolve arg type, remember it
|
// Resolve arg type, remember it
|
||||||
f.flatMap(acc =>
|
f.flatMap(acc =>
|
||||||
T.resolveType(argType).flatMap {
|
T.resolveType(argType).flatMap {
|
||||||
case Some(t: ArrowType) =>
|
case Some(t: ArrowType) =>
|
||||||
N.defineArrow(argName, t, isRoot = false).as(acc.append(t))
|
N.defineArrow(argName, t, isRoot = false).as(acc.append(argName.value -> t))
|
||||||
case Some(t) =>
|
case Some(t) =>
|
||||||
N.define(argName, t).as(acc.append(t))
|
N.define(argName, t).as(acc.append(argName.value -> t))
|
||||||
case None =>
|
case None =>
|
||||||
Free.pure(acc)
|
Free.pure(acc)
|
||||||
}
|
}
|
||||||
@ -50,24 +50,19 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
|
|||||||
// Resolve return type
|
// Resolve return type
|
||||||
ret.fold(Free.pure[Alg, Option[Type]](None))(T.resolveType(_))
|
ret.fold(Free.pure[Alg, Option[Type]](None))(T.resolveType(_))
|
||||||
)
|
)
|
||||||
.map(argsAndRes => ArrowType(argsAndRes._1, argsAndRes._2))
|
.map(argsAndRes =>
|
||||||
|
ArrowType(ProductType.labelled(argsAndRes._1), ProductType(argsAndRes._2.toList))
|
||||||
|
)
|
||||||
|
|
||||||
def generateFuncModel[Alg[_]](funcArrow: ArrowType, retModel: Option[ValueModel], body: FuncOp)(
|
def generateFuncModel[Alg[_]](funcArrow: ArrowType, retModel: List[ValueModel], body: FuncOp)(
|
||||||
implicit N: NamesAlgebra[F, Alg]
|
implicit N: NamesAlgebra[F, Alg]
|
||||||
): Free[Alg, Model] = {
|
): Free[Alg, Model] = {
|
||||||
val argNames = args.map(_.name.value)
|
val argNames = args.map(_.name.value)
|
||||||
|
|
||||||
val model = FuncModel(
|
val model = FuncModel(
|
||||||
name = name.value,
|
name = name.value,
|
||||||
args = ArgsDef(
|
arrowType = funcArrow,
|
||||||
argNames
|
ret = retModel,
|
||||||
.zip(funcArrow.args)
|
|
||||||
.map {
|
|
||||||
case (n, dt: DataType) => ArgDef.Data(n, dt)
|
|
||||||
case (n, at: ArrowType) => ArgDef.Arrow(n, at)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
ret = retModel zip funcArrow.res,
|
|
||||||
body = body
|
body = body
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,12 +82,14 @@ class FuncSem[F[_]](val expr: FuncExpr[F]) extends AnyVal {
|
|||||||
// Check return value type
|
// Check return value type
|
||||||
((funcArrow.res, retValue) match {
|
((funcArrow.res, retValue) match {
|
||||||
case (Some(t), Some(v)) =>
|
case (Some(t), Some(v)) =>
|
||||||
V.valueToModel(v).flatTap {
|
V.valueToModel(v)
|
||||||
case Some(vt) => T.ensureTypeMatches(v, t, vt.lastType).void
|
.flatTap {
|
||||||
case None => Free.pure[Alg, Unit](())
|
case Some(vt) => T.ensureTypeMatches(v, t, vt.lastType).void
|
||||||
}
|
case None => Free.pure[Alg, Unit](())
|
||||||
case _ =>
|
}
|
||||||
Free.pure[Alg, Option[ValueModel]](None)
|
.map(_.toList)
|
||||||
|
case (_, _) =>
|
||||||
|
Free.pure[Alg, List[ValueModel]](Nil)
|
||||||
|
|
||||||
// Erase arguments and internal variables
|
// Erase arguments and internal variables
|
||||||
}).flatMap(retModel =>
|
}).flatMap(retModel =>
|
||||||
|
@ -55,10 +55,11 @@ class PushToStreamSem[F[_]](val expr: PushToStreamExpr[F]) extends AnyVal {
|
|||||||
.map(t =>
|
.map(t =>
|
||||||
FuncOp
|
FuncOp
|
||||||
.leaf(
|
.leaf(
|
||||||
|
// TODO: replace with Apply
|
||||||
CallServiceTag(
|
CallServiceTag(
|
||||||
LiteralModel.quote("op"),
|
LiteralModel.quote("op"),
|
||||||
"identity",
|
"identity",
|
||||||
Call(vm :: Nil, Some(Call.Export(expr.stream.value, t)))
|
Call(vm :: Nil, Call.Export(expr.stream.value, t) :: Nil)
|
||||||
)
|
)
|
||||||
): Model
|
): Model
|
||||||
)
|
)
|
||||||
|
@ -43,15 +43,16 @@ class ValuesAlgebra[F[_], Alg[_]](implicit N: NamesAlgebra[F, Alg], T: TypesAlge
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def checkArguments(token: Token[F], arr: ArrowType, args: List[Value[F]]): Free[Alg, Boolean] = {
|
def checkArguments(token: Token[F], arr: ArrowType, args: List[Value[F]]): Free[Alg, Boolean] =
|
||||||
T.checkArgumentsNumber(token, arr.args.length, args.length).flatMap {
|
// TODO: do we really need to check this?
|
||||||
|
T.checkArgumentsNumber(token, arr.domain.length, args.length).flatMap {
|
||||||
case false => Free.pure[Alg, Boolean](false)
|
case false => Free.pure[Alg, Boolean](false)
|
||||||
case true =>
|
case true =>
|
||||||
args
|
args
|
||||||
.map[Free[Alg, Option[(Token[F], Type)]]](tkn =>
|
.map[Free[Alg, Option[(Token[F], Type)]]](tkn =>
|
||||||
resolveType(tkn).map(_.map(t => tkn -> t))
|
resolveType(tkn).map(_.map(t => tkn -> t))
|
||||||
)
|
)
|
||||||
.zip(arr.args)
|
.zip(arr.domain.toList)
|
||||||
.foldLeft(
|
.foldLeft(
|
||||||
Free.pure[Alg, Boolean](true)
|
Free.pure[Alg, Boolean](true)
|
||||||
) { case (f, (ft, t)) =>
|
) { case (f, (ft, t)) =>
|
||||||
@ -66,7 +67,6 @@ class ValuesAlgebra[F[_], Alg[_]](implicit N: NamesAlgebra[F, Alg], T: TypesAlge
|
|||||||
).mapN(_ && _)
|
).mapN(_ && _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ package aqua.semantics.rules.types
|
|||||||
|
|
||||||
import aqua.parser.lexer.Token
|
import aqua.parser.lexer.Token
|
||||||
import aqua.semantics.rules.ReportError
|
import aqua.semantics.rules.ReportError
|
||||||
import aqua.types.{ArrowType, ProductType}
|
import aqua.types.{ArrowType, StructType}
|
||||||
import cats.data.Validated.{Invalid, Valid}
|
import cats.data.Validated.{Invalid, Valid}
|
||||||
import cats.data.{NonEmptyMap, State}
|
import cats.data.{NonEmptyMap, State}
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
@ -71,7 +71,7 @@ class TypesInterpreter[F[_], X](implicit lens: Lens[X, TypesState[F]], error: Re
|
|||||||
case None =>
|
case None =>
|
||||||
modify(st =>
|
modify(st =>
|
||||||
st.copy(
|
st.copy(
|
||||||
strict = st.strict.updated(ddt.name.value, ProductType(ddt.name.value, ddt.fields)),
|
strict = st.strict.updated(ddt.name.value, StructType(ddt.name.value, ddt.fields)),
|
||||||
definitions = st.definitions.updated(ddt.name.value, ddt.name)
|
definitions = st.definitions.updated(ddt.name.value, ddt.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,18 @@ import aqua.parser.lexer.{
|
|||||||
TopBottomToken,
|
TopBottomToken,
|
||||||
TypeToken
|
TypeToken
|
||||||
}
|
}
|
||||||
import aqua.types.{ArrayType, ArrowType, DataType, OptionType, ProductType, StreamType, Type}
|
import aqua.types.{
|
||||||
|
ArrayType,
|
||||||
|
ArrowType,
|
||||||
|
BottomType,
|
||||||
|
DataType,
|
||||||
|
OptionType,
|
||||||
|
ProductType,
|
||||||
|
StreamType,
|
||||||
|
StructType,
|
||||||
|
TopType,
|
||||||
|
Type
|
||||||
|
}
|
||||||
import cats.data.Validated.{Invalid, Valid}
|
import cats.data.Validated.{Invalid, Valid}
|
||||||
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
|
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
|
||||||
import cats.kernel.Monoid
|
import cats.kernel.Monoid
|
||||||
@ -32,7 +43,7 @@ case class TypesState[F[_]](
|
|||||||
def resolveTypeToken(tt: TypeToken[F]): Option[Type] =
|
def resolveTypeToken(tt: TypeToken[F]): Option[Type] =
|
||||||
tt match {
|
tt match {
|
||||||
case TopBottomToken(_, isTop) =>
|
case TopBottomToken(_, isTop) =>
|
||||||
Option(if (isTop) DataType.Top else DataType.Bottom)
|
Option(if (isTop) TopType else BottomType)
|
||||||
case ArrayTypeToken(_, dtt) =>
|
case ArrayTypeToken(_, dtt) =>
|
||||||
resolveTypeToken(dtt).collect { case it: DataType =>
|
resolveTypeToken(dtt).collect { case it: DataType =>
|
||||||
ArrayType(it)
|
ArrayType(it)
|
||||||
@ -55,7 +66,7 @@ case class TypesState[F[_]](
|
|||||||
dt
|
dt
|
||||||
}
|
}
|
||||||
Option.when(strictRes.isDefined == res.isDefined && strictArgs.length == args.length)(
|
Option.when(strictRes.isDefined == res.isDefined && strictArgs.length == args.length)(
|
||||||
ArrowType(strictArgs, strictRes)
|
ArrowType(ProductType(strictArgs), ProductType(strictRes.toList))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +83,7 @@ case class TypesState[F[_]](
|
|||||||
NonEmptyChain
|
NonEmptyChain
|
||||||
.fromChain(errs)
|
.fromChain(errs)
|
||||||
.fold[ValidatedNec[(Token[F], String), ArrowType]](
|
.fold[ValidatedNec[(Token[F], String), ArrowType]](
|
||||||
Valid(ArrowType(argTypes.toList, resType))
|
Valid(ArrowType(ProductType(argTypes.toList), ProductType(resType.toList)))
|
||||||
)(Invalid(_))
|
)(Invalid(_))
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
@ -95,7 +106,7 @@ case class TypesState[F[_]](
|
|||||||
}
|
}
|
||||||
case (i @ IntoField(_)) :: tail =>
|
case (i @ IntoField(_)) :: tail =>
|
||||||
rootT match {
|
rootT match {
|
||||||
case pt @ ProductType(_, fields) =>
|
case pt @ StructType(_, fields) =>
|
||||||
fields(i.value)
|
fields(i.value)
|
||||||
.toRight(i -> s"Field `${i.value}` not found in type `${pt.name}``")
|
.toRight(i -> s"Field `${i.value}` not found in type `${pt.name}``")
|
||||||
.flatMap(t => resolveOps(t, tail).map(IntoFieldModel(i.value, t) :: _))
|
.flatMap(t => resolveOps(t, tail).map(IntoFieldModel(i.value, t) :: _))
|
||||||
|
141
types/src/main/scala/aqua/types/CompareTypes.scala
Normal file
141
types/src/main/scala/aqua/types/CompareTypes.scala
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package aqua.types
|
||||||
|
|
||||||
|
import cats.data.NonEmptyMap
|
||||||
|
import cats.kernel.PartialOrder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types variance is given as a partial order of types.
|
||||||
|
* Type A is less than type B if B has more data than A.
|
||||||
|
* E.g. u8 < u16
|
||||||
|
*/
|
||||||
|
object CompareTypes {
|
||||||
|
import Double.NaN
|
||||||
|
|
||||||
|
private def compareTypesList(l: List[Type], r: List[Type]): Double =
|
||||||
|
if (l.length != r.length) NaN
|
||||||
|
else if (l == r) 0.0
|
||||||
|
else
|
||||||
|
(l zip r).map(lr => apply(lr._1, lr._2)).fold(0.0) {
|
||||||
|
case (a, b) if a == b => a
|
||||||
|
case (`NaN`, _) => NaN
|
||||||
|
case (_, `NaN`) => NaN
|
||||||
|
case (0, b) => b
|
||||||
|
case (a, 0) => a
|
||||||
|
case _ => NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
import ScalarType.*
|
||||||
|
|
||||||
|
private def isLessThen(a: ScalarType, b: ScalarType): Boolean = (a, b) match {
|
||||||
|
// Signed numbers
|
||||||
|
case (`i32` | `i16` | `i8`, `i64`) => true
|
||||||
|
case (`i16` | `i8`, `i32`) => true
|
||||||
|
case (`i8`, `i16`) => true
|
||||||
|
|
||||||
|
// Unsigned numbers -- can fit into larger signed ones too
|
||||||
|
case (`u32` | `u16` | `u8`, `u64` | `i64`) => true
|
||||||
|
case (`u16` | `u8`, `u32` | `i32`) => true
|
||||||
|
case (`u8`, `u16` | `i16`) => true
|
||||||
|
|
||||||
|
// Floats
|
||||||
|
case (`f32`, `f64`) => true
|
||||||
|
|
||||||
|
case (`i8` | `i16` | `u8` | `u16`, `f32` | `f64`) => true
|
||||||
|
case (`i32` | `u32`, `f64`) => true
|
||||||
|
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scalarOrder: PartialOrder[ScalarType] =
|
||||||
|
PartialOrder.from {
|
||||||
|
case (a, b) if a == b => 0.0
|
||||||
|
case (a, b) if isLessThen(a, b) => -1.0
|
||||||
|
case (a, b) if isLessThen(b, a) => 1.0
|
||||||
|
case _ => Double.NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
private def compareStructs(lf: NonEmptyMap[String, Type], rf: NonEmptyMap[String, Type]): Double =
|
||||||
|
if (lf.toSortedMap == rf.toSortedMap) 0.0
|
||||||
|
else if (
|
||||||
|
lf.keys.forall(rf.contains) && compareTypesList(
|
||||||
|
lf.toSortedMap.toList.map(_._2),
|
||||||
|
rf.toSortedMap.view.filterKeys(lf.keys.contains).toList.map(_._2)
|
||||||
|
) == -1.0
|
||||||
|
) 1.0
|
||||||
|
else if (
|
||||||
|
rf.keys.forall(lf.contains) && compareTypesList(
|
||||||
|
lf.toSortedMap.view.filterKeys(rf.keys.contains).toList.map(_._2),
|
||||||
|
rf.toSortedMap.toList.map(_._2)
|
||||||
|
) == 1.0
|
||||||
|
) -1.0
|
||||||
|
else NaN
|
||||||
|
|
||||||
|
private def compareProducts(l: ProductType, r: ProductType): Double = (l, r) match {
|
||||||
|
case (NilType, NilType) => 0.0
|
||||||
|
case (_: ConsType, NilType) => -1.0
|
||||||
|
case (NilType, _: ConsType) => 1.0
|
||||||
|
case (ConsType(lhead, ltail), ConsType(rhead, rtail)) =>
|
||||||
|
// If any is not Cons, than it's Bottom and already handled
|
||||||
|
val headCmp = apply(lhead, rhead)
|
||||||
|
if (headCmp.isNaN) NaN
|
||||||
|
else {
|
||||||
|
val tailCmp = compareProducts(ltail, rtail)
|
||||||
|
// If one is >, and another eq, it's >, and vice versa
|
||||||
|
if (headCmp >= 0 && tailCmp >= 0) 1.0
|
||||||
|
else if (headCmp <= 0 && tailCmp <= 0) -1.0
|
||||||
|
else NaN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare types in the meaning of type variance.
|
||||||
|
*
|
||||||
|
* @param l Type
|
||||||
|
* @param r Type
|
||||||
|
* @return 0 if types match,
|
||||||
|
* 1 if left type is supertype for the right one,
|
||||||
|
* -1 if left is a subtype of the right
|
||||||
|
*/
|
||||||
|
def apply(l: Type, r: Type): Double =
|
||||||
|
if (l == r) 0.0
|
||||||
|
else
|
||||||
|
(l, r) match {
|
||||||
|
case (TopType, _) | (_, BottomType) => 1.0
|
||||||
|
case (BottomType, _) | (_, TopType) => -1.0
|
||||||
|
|
||||||
|
// Literals and scalars
|
||||||
|
case (x: ScalarType, y: ScalarType) => scalarOrder.partialCompare(x, y)
|
||||||
|
case (LiteralType(xs, _), y: ScalarType) if xs == Set(y) => 0.0
|
||||||
|
case (LiteralType(xs, _), y: ScalarType) if xs(y) => -1.0
|
||||||
|
case (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
|
||||||
|
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 1.0
|
||||||
|
|
||||||
|
// Collections
|
||||||
|
case (x: ArrayType, y: ArrayType) => apply(x.element, y.element)
|
||||||
|
case (x: ArrayType, y: StreamType) => apply(x.element, y.element)
|
||||||
|
case (x: ArrayType, y: OptionType) => apply(x.element, y.element)
|
||||||
|
case (x: OptionType, y: StreamType) => apply(x.element, y.element)
|
||||||
|
case (x: OptionType, y: ArrayType) => apply(x.element, y.element)
|
||||||
|
case (x: StreamType, y: StreamType) => apply(x.element, y.element)
|
||||||
|
case (StructType(_, xFields), StructType(_, yFields)) =>
|
||||||
|
compareStructs(xFields, yFields)
|
||||||
|
|
||||||
|
// Products
|
||||||
|
case (l: ProductType, r: ProductType) => compareProducts(l, r)
|
||||||
|
|
||||||
|
// Arrows
|
||||||
|
case (ArrowType(ldom, lcodom), ArrowType(rdom, rcodom)) =>
|
||||||
|
val cmpDom = apply(ldom, rdom)
|
||||||
|
val cmpCodom = apply(lcodom, rcodom)
|
||||||
|
|
||||||
|
if (cmpDom >= 0 && cmpCodom <= 0) -1.0
|
||||||
|
else if (cmpDom <= 0 && cmpCodom >= 0) 1.0
|
||||||
|
else NaN
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
Double.NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val partialOrder: PartialOrder[Type] =
|
||||||
|
PartialOrder.from(CompareTypes.apply)
|
||||||
|
}
|
@ -2,8 +2,6 @@ package aqua.types
|
|||||||
|
|
||||||
import cats.PartialOrder
|
import cats.PartialOrder
|
||||||
import cats.data.NonEmptyMap
|
import cats.data.NonEmptyMap
|
||||||
import cats.instances.option._
|
|
||||||
import cats.syntax.apply._
|
|
||||||
|
|
||||||
sealed trait Type {
|
sealed trait Type {
|
||||||
|
|
||||||
@ -12,12 +10,107 @@ sealed trait Type {
|
|||||||
import cats.syntax.partialOrder._
|
import cats.syntax.partialOrder._
|
||||||
this >= incoming
|
this >= incoming
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isInhabited: Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Product is a list of (optionally labelled) types
|
||||||
|
sealed trait ProductType extends Type {
|
||||||
|
def isEmpty: Boolean = this == NilType
|
||||||
|
|
||||||
|
def length: Int
|
||||||
|
|
||||||
|
def uncons: Option[(Type, ProductType)] = this match {
|
||||||
|
case ConsType(t, pt) => Some(t -> pt)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val toList: List[Type] = this match {
|
||||||
|
case ConsType(t, pt) => t :: pt.toList
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts product type to a list of types, labelling each of them with a string
|
||||||
|
* Label is either got from the types with labels, or from the given prefix and index of a type.
|
||||||
|
* @param prefix Prefix to generate a missing label
|
||||||
|
* @param index Index to ensure generated labels are unique
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def toLabelledList(prefix: String = "arg", index: Int = 0): List[(String, Type)] = this match {
|
||||||
|
case LabelledConsType(label, t, pt) => (label -> t) :: pt.toLabelledList(prefix, index + 1)
|
||||||
|
case UnlabelledConsType(t, pt) =>
|
||||||
|
(s"$prefix$index" -> t) :: pt.toLabelledList(prefix, index + 1)
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val labelledData: List[(String, DataType)] = this match {
|
||||||
|
case LabelledConsType(label, t: DataType, pt) => (label -> t) :: pt.labelledData
|
||||||
|
case UnlabelledConsType(_, pt) => pt.labelledData
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ProductType {
|
||||||
|
|
||||||
|
def apply(types: List[Type]): ProductType = types match {
|
||||||
|
case h :: t =>
|
||||||
|
ConsType.cons(h, ProductType(t))
|
||||||
|
case _ => NilType
|
||||||
|
}
|
||||||
|
|
||||||
|
def labelled(types: List[(String, Type)]): ProductType = types match {
|
||||||
|
case (l, h) :: t =>
|
||||||
|
ConsType.cons(l, h, ProductType.labelled(t))
|
||||||
|
case _ => NilType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConsType adds a type to the ProductType, and delegates all the others to tail
|
||||||
|
* Corresponds to Cons (::) in the List
|
||||||
|
*/
|
||||||
|
sealed trait ConsType extends ProductType {
|
||||||
|
def `type`: Type
|
||||||
|
def tail: ProductType
|
||||||
|
|
||||||
|
override def length: Int = 1 + tail.length
|
||||||
|
}
|
||||||
|
|
||||||
|
object ConsType {
|
||||||
|
def unapply(cons: ConsType): Option[(Type, ProductType)] = Some(cons.`type` -> cons.tail)
|
||||||
|
def cons(`type`: Type, tail: ProductType): ConsType = UnlabelledConsType(`type`, tail)
|
||||||
|
|
||||||
|
def cons(label: String, `type`: Type, tail: ProductType): ConsType =
|
||||||
|
LabelledConsType(label, `type`, tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class LabelledConsType(label: String, `type`: Type, tail: ProductType) extends ConsType {
|
||||||
|
override def toString: String = s"($label: " + `type` + s" :: $tail"
|
||||||
|
}
|
||||||
|
|
||||||
|
case class UnlabelledConsType(`type`: Type, tail: ProductType) extends ConsType {
|
||||||
|
override def toString: String = `type`.toString + s" :: $tail"
|
||||||
|
}
|
||||||
|
|
||||||
|
object NilType extends ProductType {
|
||||||
|
override def toString: String = "∅"
|
||||||
|
|
||||||
|
override def isInhabited: Boolean = false
|
||||||
|
|
||||||
|
override def length: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
sealed trait DataType extends Type
|
sealed trait DataType extends Type
|
||||||
|
|
||||||
object DataType {
|
case object TopType extends DataType {
|
||||||
case object Top extends DataType
|
override def toString: String = "⊤"
|
||||||
case object Bottom extends DataType
|
}
|
||||||
|
|
||||||
|
case object BottomType extends DataType {
|
||||||
|
override def toString: String = "⊥"
|
||||||
|
|
||||||
|
override def isInhabited: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ScalarType private (name: String) extends DataType {
|
case class ScalarType private (name: String) extends DataType {
|
||||||
@ -46,34 +139,6 @@ object ScalarType {
|
|||||||
val signed = float ++ Set(i8, i16, i32, i64)
|
val signed = float ++ Set(i8, i16, i32, i64)
|
||||||
val number = signed ++ Set(u8, u16, u32, u64)
|
val number = signed ++ Set(u8, u16, u32, u64)
|
||||||
val all = number ++ Set(bool, string)
|
val all = number ++ Set(bool, string)
|
||||||
|
|
||||||
private def isLessThen(a: ScalarType, b: ScalarType): Boolean = (a, b) match {
|
|
||||||
// Signed numbers
|
|
||||||
case (`i32` | `i16` | `i8`, `i64`) => true
|
|
||||||
case (`i16` | `i8`, `i32`) => true
|
|
||||||
case (`i8`, `i16`) => true
|
|
||||||
|
|
||||||
// Unsigned numbers -- can fit into larger signed ones too
|
|
||||||
case (`u32` | `u16` | `u8`, `u64` | `i64`) => true
|
|
||||||
case (`u16` | `u8`, `u32` | `i32`) => true
|
|
||||||
case (`u8`, `u16` | `i16`) => true
|
|
||||||
|
|
||||||
// Floats
|
|
||||||
case (`f32`, `f64`) => true
|
|
||||||
|
|
||||||
case (`i8` | `i16` | `u8` | `u16`, `f32` | `f64`) => true
|
|
||||||
case (`i32` | `u32`, `f64`) => true
|
|
||||||
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
|
|
||||||
val scalarOrder: PartialOrder[ScalarType] =
|
|
||||||
PartialOrder.from {
|
|
||||||
case (a, b) if a == b => 0.0
|
|
||||||
case (a, b) if isLessThen(a, b) => -1.0
|
|
||||||
case (a, b) if isLessThen(b, a) => 1.0
|
|
||||||
case _ => Double.NaN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType {
|
case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType {
|
||||||
@ -100,93 +165,49 @@ case class OptionType(element: Type) extends BoxType {
|
|||||||
override def toString: String = "?" + element
|
override def toString: String = "?" + element
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ProductType(name: String, fields: NonEmptyMap[String, Type]) extends DataType {
|
// Struct is an unordered collection of labelled types
|
||||||
|
case class StructType(name: String, fields: NonEmptyMap[String, Type]) extends DataType {
|
||||||
|
|
||||||
override def toString: String =
|
override def toString: String =
|
||||||
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ArrowType(args: List[Type], res: Option[Type]) extends Type {
|
/**
|
||||||
|
* ArrowType is a profunctor pointing its domain to codomain.
|
||||||
|
* Profunctor means variance: Arrow is contravariant on domain, and variant on codomain.
|
||||||
|
* See tests for details.
|
||||||
|
* @param domain Where this Arrow is defined
|
||||||
|
* @param codomain Where this Arrow points on
|
||||||
|
*/
|
||||||
|
case class ArrowType(domain: ProductType, codomain: ProductType) extends Type {
|
||||||
|
|
||||||
|
@deprecated(
|
||||||
|
"Use .domain to get arguments, add .args helper to the typed object, if needed",
|
||||||
|
"5.08.2021"
|
||||||
|
)
|
||||||
|
def args: List[Type] = domain.toList
|
||||||
|
|
||||||
|
@deprecated(
|
||||||
|
"Use .codomain to get results, add .res helper to the typed object, if needed; consider multi-value return",
|
||||||
|
"5.08.2021"
|
||||||
|
)
|
||||||
|
def res: Option[Type] = codomain.uncons.map(_._1)
|
||||||
|
|
||||||
|
@deprecated(
|
||||||
|
"Replace with this function's body",
|
||||||
|
"5.08.2021"
|
||||||
|
)
|
||||||
def acceptsAsArguments(valueTypes: List[Type]): Boolean =
|
def acceptsAsArguments(valueTypes: List[Type]): Boolean =
|
||||||
(args.length == valueTypes.length) && args
|
domain.acceptsValueOf(ProductType(valueTypes))
|
||||||
.zip(valueTypes)
|
|
||||||
.forall(av => av._1.acceptsValueOf(av._2))
|
|
||||||
|
|
||||||
override def toString: String =
|
override def toString: String =
|
||||||
args.map(_.toString).mkString(", ") + " -> " + res.map(_.toString).getOrElse("()")
|
s"$domain -> $codomain"
|
||||||
}
|
}
|
||||||
|
|
||||||
case class StreamType(element: Type) extends BoxType
|
case class StreamType(element: Type) extends BoxType
|
||||||
|
|
||||||
object Type {
|
object Type {
|
||||||
import Double.NaN
|
|
||||||
|
|
||||||
private def cmpTypesList(l: List[Type], r: List[Type]): Double =
|
implicit lazy val typesPartialOrder: PartialOrder[Type] =
|
||||||
if (l.length != r.length) NaN
|
CompareTypes.partialOrder
|
||||||
else if (l == r) 0.0
|
|
||||||
else
|
|
||||||
(l zip r).map(lr => cmp(lr._1, lr._2)).fold(0.0) {
|
|
||||||
case (a, b) if a == b => a
|
|
||||||
case (`NaN`, _) => NaN
|
|
||||||
case (_, `NaN`) => NaN
|
|
||||||
case (0, b) => b
|
|
||||||
case (a, 0) => a
|
|
||||||
case _ => NaN
|
|
||||||
}
|
|
||||||
|
|
||||||
private def cmpProd(lf: NonEmptyMap[String, Type], rf: NonEmptyMap[String, Type]): Double =
|
|
||||||
if (lf.toSortedMap == rf.toSortedMap) 0.0
|
|
||||||
else if (
|
|
||||||
lf.keys.forall(rf.contains) && cmpTypesList(
|
|
||||||
lf.toSortedMap.toList.map(_._2),
|
|
||||||
rf.toSortedMap.view.filterKeys(lf.keys.contains).toList.map(_._2)
|
|
||||||
) == -1.0
|
|
||||||
) 1.0
|
|
||||||
else if (
|
|
||||||
rf.keys.forall(lf.contains) && cmpTypesList(
|
|
||||||
lf.toSortedMap.view.filterKeys(rf.keys.contains).toList.map(_._2),
|
|
||||||
rf.toSortedMap.toList.map(_._2)
|
|
||||||
) == 1.0
|
|
||||||
) -1.0
|
|
||||||
else NaN
|
|
||||||
|
|
||||||
private def cmp(l: Type, r: Type): Double =
|
|
||||||
if (l == r) 0.0
|
|
||||||
else
|
|
||||||
(l, r) match {
|
|
||||||
case (DataType.Top, _: DataType) | (_: DataType, DataType.Bottom) => 1.0
|
|
||||||
case (DataType.Bottom, _: DataType) | (_: DataType, DataType.Top) => -1.0
|
|
||||||
case (x: ScalarType, y: ScalarType) => ScalarType.scalarOrder.partialCompare(x, y)
|
|
||||||
case (LiteralType(xs, _), y: ScalarType) if xs == Set(y) => 0.0
|
|
||||||
case (LiteralType(xs, _), y: ScalarType) if xs(y) => -1.0
|
|
||||||
case (x: ScalarType, LiteralType(ys, _)) if ys == Set(x) => 0.0
|
|
||||||
case (x: ScalarType, LiteralType(ys, _)) if ys(x) => 1.0
|
|
||||||
case (x: ArrayType, y: ArrayType) => cmp(x.element, y.element)
|
|
||||||
case (x: ArrayType, y: StreamType) => cmp(x.element, y.element)
|
|
||||||
case (x: ArrayType, y: OptionType) => cmp(x.element, y.element)
|
|
||||||
case (x: OptionType, y: StreamType) => cmp(x.element, y.element)
|
|
||||||
case (x: OptionType, y: ArrayType) => cmp(x.element, y.element)
|
|
||||||
case (x: StreamType, y: StreamType) => cmp(x.element, y.element)
|
|
||||||
case (ProductType(_, xFields), ProductType(_, yFields)) =>
|
|
||||||
cmpProd(xFields, yFields)
|
|
||||||
case (l: ArrowType, r: ArrowType) =>
|
|
||||||
val argL = l.args
|
|
||||||
val resL = l.res
|
|
||||||
val argR = r.args
|
|
||||||
val resR = r.res
|
|
||||||
val cmpTypes = cmpTypesList(argR, argL)
|
|
||||||
val cmpRes =
|
|
||||||
if (resL == resR) 0.0
|
|
||||||
else (resL, resR).mapN(cmp).getOrElse(NaN)
|
|
||||||
|
|
||||||
if (cmpTypes >= 0 && cmpRes >= 0) 1.0
|
|
||||||
else if (cmpTypes <= 0 && cmpRes <= 0) -1.0
|
|
||||||
else NaN
|
|
||||||
|
|
||||||
case _ =>
|
|
||||||
Double.NaN
|
|
||||||
}
|
|
||||||
|
|
||||||
implicit lazy val typesPartialOrder: PartialOrder[Type] = PartialOrder.from(cmp)
|
|
||||||
}
|
}
|
||||||
|
@ -38,15 +38,15 @@ class TypeSpec extends AnyFlatSpec with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"top type" should "accept anything" in {
|
"top type" should "accept anything" in {
|
||||||
accepts(DataType.Top, u64) should be(true)
|
accepts(TopType, u64) should be(true)
|
||||||
accepts(DataType.Top, LiteralType.bool) should be(true)
|
accepts(TopType, LiteralType.bool) should be(true)
|
||||||
accepts(DataType.Top, `*`(u64)) should be(true)
|
accepts(TopType, `*`(u64)) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"bottom type" should "be accepted by everything" in {
|
"bottom type" should "be accepted by everything" in {
|
||||||
accepts(u64, DataType.Bottom) should be(true)
|
accepts(u64, BottomType) should be(true)
|
||||||
accepts(LiteralType.bool, DataType.Bottom) should be(true)
|
accepts(LiteralType.bool, BottomType) should be(true)
|
||||||
accepts(`*`(u64), DataType.Bottom) should be(true)
|
accepts(`*`(u64), BottomType) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"arrays of scalars" should "be variant" in {
|
"arrays of scalars" should "be variant" in {
|
||||||
@ -62,52 +62,16 @@ class TypeSpec extends AnyFlatSpec with Matchers {
|
|||||||
(`[]`(`[]`(u32)): Type) <= `[]`(`[]`(u64)) should be(true)
|
(`[]`(`[]`(u32)): Type) <= `[]`(`[]`(u64)) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"products of scalars" should "be variant" in {
|
"structs of scalars" should "be variant" in {
|
||||||
val one: Type = ProductType("one", NonEmptyMap.of("field" -> u32))
|
val one: Type = StructType("one", NonEmptyMap.of("field" -> u32))
|
||||||
val two: Type = ProductType("two", NonEmptyMap.of("field" -> u64, "other" -> string))
|
val two: Type = StructType("two", NonEmptyMap.of("field" -> u64, "other" -> string))
|
||||||
val three: Type = ProductType("three", NonEmptyMap.of("field" -> u32))
|
val three: Type = StructType("three", NonEmptyMap.of("field" -> u32))
|
||||||
|
|
||||||
accepts(one, two) should be(true)
|
accepts(one, two) should be(true)
|
||||||
accepts(two, one) should be(false)
|
accepts(two, one) should be(false)
|
||||||
PartialOrder[Type].eqv(one, three) should be(true)
|
PartialOrder[Type].eqv(one, three) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"arrows" should "be contravariant on arguments" in {
|
|
||||||
val one: Type = ArrowType(u32 :: Nil, None)
|
|
||||||
val two: Type = ArrowType(u64 :: Nil, None)
|
|
||||||
|
|
||||||
accepts(one, two) should be(true)
|
|
||||||
|
|
||||||
one > two should be(true)
|
|
||||||
two < one should be(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
"arrows" should "be variant on results" in {
|
|
||||||
val one: Type = ArrowType(Nil, Some(u64))
|
|
||||||
val two: Type = ArrowType(Nil, Some(u32))
|
|
||||||
|
|
||||||
accepts(one, two) should be(true)
|
|
||||||
|
|
||||||
one > two should be(true)
|
|
||||||
two < one should be(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
"arrows" should "respect both args and results" in {
|
|
||||||
val one: Type = ArrowType(bool :: f64 :: Nil, Some(u64))
|
|
||||||
val two: Type = ArrowType(bool :: Nil, Some(u64))
|
|
||||||
val three: Type = ArrowType(bool :: f32 :: Nil, Some(u64))
|
|
||||||
val four: Type = ArrowType(bool :: f32 :: Nil, Some(u32))
|
|
||||||
|
|
||||||
accepts(one, two) should be(false)
|
|
||||||
accepts(two, one) should be(false)
|
|
||||||
|
|
||||||
accepts(one, three) should be(false)
|
|
||||||
accepts(three, one) should be(true)
|
|
||||||
|
|
||||||
accepts(one, four) should be(false)
|
|
||||||
accepts(four, one) should be(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
"streams" should "be accepted as an array, but not vice versa" in {
|
"streams" should "be accepted as an array, but not vice versa" in {
|
||||||
val stream: Type = StreamType(bool)
|
val stream: Type = StreamType(bool)
|
||||||
val array: Type = ArrayType(bool)
|
val array: Type = ArrayType(bool)
|
||||||
@ -126,4 +90,68 @@ class TypeSpec extends AnyFlatSpec with Matchers {
|
|||||||
accepts(opt, opt) should be(true)
|
accepts(opt, opt) should be(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"products" should "compare" in {
|
||||||
|
val empty: ProductType = NilType
|
||||||
|
val smth: ProductType = ConsType.cons(bool, empty)
|
||||||
|
|
||||||
|
accepts(empty, smth) should be(true)
|
||||||
|
accepts(smth, empty) should be(false)
|
||||||
|
|
||||||
|
val longer = ConsType.cons(string, smth)
|
||||||
|
accepts(empty, longer) should be(true)
|
||||||
|
accepts(smth, longer) should be(false)
|
||||||
|
accepts(longer, longer) should be(true)
|
||||||
|
accepts(longer, empty) should be(false)
|
||||||
|
accepts(longer, smth) should be(false)
|
||||||
|
accepts(ConsType.cons("label", string, empty), longer) should be(true)
|
||||||
|
|
||||||
|
accepts(ConsType.cons(u64, empty), ConsType.cons(u32, empty)) should be(true)
|
||||||
|
accepts(ConsType.cons(u32, empty), ConsType.cons(u64, empty)) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
"arrows" should "be contravariant on arguments" in {
|
||||||
|
val one: Type = ArrowType(ProductType(u32 :: Nil), NilType)
|
||||||
|
val onePrime: Type = ArrowType(ProductType(u32 :: bool :: Nil), NilType)
|
||||||
|
val two: Type = ArrowType(ProductType(u64 :: Nil), NilType)
|
||||||
|
|
||||||
|
accepts(one, onePrime) should be(false)
|
||||||
|
accepts(onePrime, one) should be(true)
|
||||||
|
accepts(one, two) should be(true)
|
||||||
|
accepts(onePrime, two) should be(true)
|
||||||
|
|
||||||
|
one > two should be(true)
|
||||||
|
two < one should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"arrows" should "be variant on results" in {
|
||||||
|
val one: Type = ArrowType(NilType, ProductType(u64 :: Nil))
|
||||||
|
val two: Type = ArrowType(NilType, ProductType(u32 :: Nil))
|
||||||
|
val three: Type = ArrowType(NilType, ProductType(u32 :: bool :: Nil))
|
||||||
|
|
||||||
|
accepts(one, two) should be(true)
|
||||||
|
accepts(one, three) should be(true)
|
||||||
|
accepts(three, two) should be(false)
|
||||||
|
accepts(three, one) should be(false)
|
||||||
|
accepts(two, one) should be(false)
|
||||||
|
|
||||||
|
one > two should be(true)
|
||||||
|
two < one should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
"arrows" should "respect both args and results" in {
|
||||||
|
val one: Type = ArrowType(ProductType(bool :: f64 :: Nil), ProductType(u64 :: Nil))
|
||||||
|
val two: Type = ArrowType(ProductType(bool :: Nil), ProductType(u64 :: Nil))
|
||||||
|
val three: Type = ArrowType(ProductType(bool :: f32 :: Nil), ProductType(u64 :: Nil))
|
||||||
|
val four: Type = ArrowType(ProductType(bool :: f32 :: Nil), ProductType(u32 :: Nil))
|
||||||
|
|
||||||
|
accepts(one, two) should be(true)
|
||||||
|
accepts(two, one) should be(false)
|
||||||
|
|
||||||
|
accepts(one, three) should be(false)
|
||||||
|
accepts(three, one) should be(true)
|
||||||
|
|
||||||
|
accepts(one, four) should be(false)
|
||||||
|
accepts(four, one) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user