108 option type (#146)

* Option type support: arguments, values

* Aqua version 0.1.4

* Fix for optional return value in TS

* Tiny fixes

* Example for declaring local options
This commit is contained in:
Dmitry Kurinskiy 2021-06-02 12:29:12 +03:00 committed by GitHub
parent d1e76c1fd3
commit 807c26619f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 151 additions and 37 deletions

View File

@ -13,7 +13,13 @@ service Test("test"):
getUserList: -> []User
doSomething: -> bool
func betterMessage(relay: string, arr: []string) -> string:
func include(opt: ?string) -> string:
Peer.is_connected(opt!)
for i <- opt:
Peer.is_connected(i)
<- opt!
func betterMessage(relay: string, arr: []string, opt: ?string, str: *string) -> ?string:
on relay:
Peer.is_connected("something")
par isOnline <- Peer.is_connected(relay)
@ -22,10 +28,16 @@ func betterMessage(relay: string, arr: []string) -> string:
stream: *string
localOpt: ?string
if isOnline:
try:
Test.doSomething()
else:
Peer.is_connected(stream!)
<- stream!2
x <- include(arr)
y <- include(opt)
localOpt <- include(str)
<- opt

View File

@ -3,7 +3,7 @@ package aqua.backend.air
import aqua.model._
import aqua.model.func.Call
import aqua.model.func.body._
import aqua.types.StreamType
import aqua.types.{OptionType, StreamType}
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
@ -30,6 +30,7 @@ object AirGen {
case VarModel(name, t, lambda) =>
val n = t match {
case _: StreamType => "$" + name
case _: OptionType => "$" + name
case _ => name
}
if (lambda.isEmpty) DataView.Variable(n)

View File

@ -18,7 +18,14 @@ case class TypescriptFunc(func: FuncCallable) {
val tsAir = FuncAirGen(func).generateClientAir(conf)
val returnCallback = func.ret.as {
val returnCallback = func.ret.map(_._2).map {
case OptionType(_) =>
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
| const [res] = args;
| resolve(res.length === 0 ? null : res[0]);
|});
|""".stripMargin
case _ =>
s"""h.onEvent('${conf.callbackService}', '${conf.respFuncName}', (args) => {
| const [res] = args;
| resolve(res);
@ -28,6 +35,8 @@ case class TypescriptFunc(func: FuncCallable) {
}
val setCallbacks = func.args.args.map {
case ArgDef.Data(argName, OptionType(_)) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName === null ? [] : [$argName];});"""
case ArgDef.Data(argName, _) =>
s"""h.on('${conf.getDataService}', '$argName', () => {return $argName;});"""
case ArgDef.Arrow(argName, at) =>
@ -88,6 +97,7 @@ case class TypescriptFunc(func: FuncCallable) {
object TypescriptFunc {
def typeToTs(t: Type): String = t match {
case OptionType(t) => typeToTs(t) + " | null"
case ArrayType(t) => typeToTs(t) + "[]"
case StreamType(t) => typeToTs(t) + "[]"
case pt: ProductType =>

View File

@ -1,32 +1,32 @@
val dottyVersion = "2.13.5"
//val dottyVersion = "3.0.0-RC3"
//val dottyVersion = "3.0.0"
scalaVersion := dottyVersion
val baseAquaVersion = settingKey[String]("base aqua version")
val catsV = "2.6.0"
val catsParseV = "0.3.3"
val catsV = "2.6.1"
val catsParseV = "0.3.4"
val monocleV = "3.0.0-M5"
val scalaTestV = "3.2.7" // TODO update version for scala 3-RC3
val fs2V = "3.0.2"
val catsEffectV = "3.1.0"
val scalaTestV = "3.2.9"
val fs2V = "3.0.4"
val catsEffectV = "3.1.1"
val airframeLogV = "21.5.4"
val log4catsV = "2.1.1"
val enumeratumV = "1.6.1"
val slf4jV = "1.7.25"
val enumeratumV = "1.6.1" // Scala3 issue: https://github.com/lloydmeta/enumeratum/issues/300
val slf4jV = "1.7.30"
val declineV = "2.0.0-RC1" // Scala3 issue: https://github.com/bkirwi/decline/issues/260
val declineEnumV = "1.3.0"
name := "aqua-hll"
val commons = Seq(
baseAquaVersion := "0.1.3",
baseAquaVersion := "0.1.4",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion,
libraryDependencies ++= Seq(
"org.typelevel" %% "log4cats-core" % "2.1.1",
"org.typelevel" %% "log4cats-core" % log4catsV,
"org.scalatest" %% "scalatest" % scalaTestV % Test
),
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full)

View File

@ -10,6 +10,11 @@ object FuncOps {
def noop(peerId: ValueModel): FuncOp =
FuncOp.leaf(CallServiceTag(LiteralModel.quote("op"), "identity", Call(Nil, None), Some(peerId)))
def identity(what: ValueModel, to: Call.Export): FuncOp =
FuncOp.leaf(
CallServiceTag(LiteralModel.quote("op"), "identity", Call(what :: Nil, Some(to)), None)
)
def callService(srvId: ValueModel, funcName: String, call: Call): FuncOp =
FuncOp.leaf(
CallServiceTag(
@ -48,4 +53,13 @@ object FuncOps {
def xor(left: FuncOp, right: FuncOp): FuncOp =
FuncOp.node(XorTag, Chain(left, right))
def fold(item: String, iter: ValueModel, op: FuncOp): FuncOp =
FuncOp.wrap(
ForTag(item, iter),
op
)
def next(item: String): FuncOp =
FuncOp.leaf(NextTag(item))
}

View File

@ -1,9 +1,10 @@
package aqua.model.transform
import aqua.model.ValueModel
import aqua.model.{ValueModel, VarModel}
import aqua.model.func.Call
import aqua.model.func.body.{FuncOp, FuncOps}
import aqua.types.DataType
import aqua.types.{ArrayType, DataType, OptionType, StreamType, Type}
import cats.data.Chain
trait ArgsProvider {
def transform(op: FuncOp): FuncOp
@ -12,12 +13,39 @@ trait ArgsProvider {
case class ArgsFromService(dataServiceId: ValueModel, names: List[(String, DataType)])
extends ArgsProvider {
private def getDataElOp(name: String, t: DataType, el: Type): FuncOp = {
val iter = s"$name-iter"
val item = s"$name-item"
FuncOps.seq(
FuncOps.callService(
dataServiceId,
name,
Call(Nil, Some(Call.Export(iter, t)))
),
FuncOps.fold(
item,
VarModel(iter, ArrayType(el), Chain.empty),
FuncOps.seq(
FuncOps.identity(VarModel(item, el), Call.Export(name, t)),
FuncOps.next(item)
)
)
)
}
def getDataOp(name: String, t: DataType): FuncOp =
t match {
case StreamType(el) =>
getDataElOp(name, t, el)
case OptionType(el) =>
getDataElOp(name, StreamType(el), el)
case _ =>
FuncOps.callService(
dataServiceId,
name,
Call(Nil, Some(Call.Export(name, t)))
)
}
def transform(op: FuncOp): FuncOp =
FuncOps.seq(

View File

@ -29,6 +29,18 @@ object StreamTypeToken {
}
case class OptionTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F])
extends DataTypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v)
}
object OptionTypeToken {
def `optiontypedef`[F[_]: LiftParser: Comonad]: P[OptionTypeToken[F]] =
(`?`.lift ~ DataTypeToken.`datatypedef`[F]).map(ud => OptionTypeToken(ud._1, ud._2))
}
case class CustomTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken[F] {
override def as[T](v: T): F[T] = name.as(v)
@ -94,7 +106,9 @@ object DataTypeToken {
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf(
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: BasicTypeToken
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer(
OptionTypeToken.`optiontypedef`
) :: BasicTypeToken
.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil
)
}

View File

@ -5,7 +5,7 @@ import aqua.parser.expr.DeclareStreamExpr
import aqua.semantics.Prog
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, StreamType}
import aqua.types.{ArrayType, OptionType, StreamType}
import cats.free.Free
class DeclareStreamSem[F[_]](val expr: DeclareStreamExpr[F]) {
@ -19,6 +19,8 @@ class DeclareStreamSem[F[_]](val expr: DeclareStreamExpr[F]) {
.flatMap {
case Some(t: StreamType) =>
N.define(expr.name, t)
case Some(t: OptionType) =>
N.define(expr.name, StreamType(t.element))
case Some(at @ ArrayType(t)) =>
T.ensureTypeMatches(expr.`type`, StreamType(t), at)
case Some(t) =>

View File

@ -7,7 +7,7 @@ import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{ArrayType, StreamType, Type}
import aqua.types.{ArrayType, BoxType}
import cats.data.Chain
import cats.free.Free
import cats.syntax.flatMap._
@ -24,10 +24,8 @@ class ForSem[F[_]](val expr: ForExpr[F]) extends AnyVal {
N.beginScope(expr.item) >> V.valueToModel(expr.iterable).flatMap[Option[ValueModel]] {
case Some(vm) =>
vm.lastType match {
case at @ ArrayType(t) =>
N.define(expr.item, t).as(Option(vm))
case st @ StreamType(t) =>
N.define(expr.item, t).as(Option(vm))
case t: BoxType =>
N.define(expr.item, t.element).as(Option(vm))
case dt =>
T.ensureTypeMatches(expr.iterable, ArrayType(dt), dt).as(Option.empty[ValueModel])
}

View File

@ -11,11 +11,12 @@ import aqua.parser.lexer.{
IntoIndex,
LambdaOp,
Name,
OptionTypeToken,
StreamTypeToken,
Token,
TypeToken
}
import aqua.types.{ArrayType, ArrowType, DataType, ProductType, StreamType, Type}
import aqua.types.{ArrayType, ArrowType, DataType, OptionType, ProductType, StreamType, Type}
import cats.data.Validated.{Invalid, Valid}
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
import cats.kernel.Monoid
@ -37,6 +38,10 @@ case class TypesState[F[_]](
resolveTypeToken(dtt).collect { case it: DataType =>
StreamType(it)
}
case OptionTypeToken(_, dtt) =>
resolveTypeToken(dtt).collect { case it: DataType =>
OptionType(it)
}
case ctt: CustomTypeToken[F] => strict.get(ctt.value)
case btt: BasicTypeToken[F] => Some(btt.value)
case ArrowTypeToken(_, args, res) =>
@ -99,7 +104,15 @@ case class TypesState[F[_]](
resolveOps(intern, tail).map(IntoIndexModel(i.value, intern) :: _)
case StreamType(intern) =>
resolveOps(intern, tail).map(IntoIndexModel(i.value, intern) :: _)
case _ => Left(i -> s"Expected $rootT to be an array")
case OptionType(intern) =>
i.value match {
case 0 =>
resolveOps(intern, tail).map(IntoIndexModel(i.value, intern) :: _)
case _ =>
Left(i -> s"Option might have only one element, use ! to get it")
}
case _ => Left(i -> s"Expected $rootT to be an array or a stream")
}
}
}

View File

@ -83,10 +83,18 @@ object LiteralType {
val string = LiteralType(Set(ScalarType.string), "string")
}
case class ArrayType(element: Type) extends DataType {
sealed trait BoxType extends DataType {
def element: Type
}
case class ArrayType(element: Type) extends BoxType {
override def toString: String = "[]" + element
}
case class OptionType(element: Type) extends BoxType {
override def toString: String = "?" + element
}
case class ProductType(name: String, fields: NonEmptyMap[String, Type]) extends DataType {
override def toString: String =
@ -104,7 +112,7 @@ case class ArrowType(args: List[Type], res: Option[Type]) extends Type {
args.map(_.toString).mkString(", ") + " -> " + res.map(_.toString).getOrElse("()")
}
case class StreamType(element: Type) extends DataType
case class StreamType(element: Type) extends BoxType
object Type {
import Double.NaN
@ -149,6 +157,9 @@ object Type {
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)

View File

@ -12,6 +12,8 @@ class TypeSpec extends AnyFlatSpec with Matchers {
import aqua.types.ScalarType._
def `[]`(t: DataType): DataType = ArrayType(t)
def `?`(t: DataType): DataType = OptionType(t)
def `*`(t: DataType): DataType = StreamType(t)
def accepts(recv: Type, incoming: Type) =
recv >= incoming
@ -103,4 +105,13 @@ class TypeSpec extends AnyFlatSpec with Matchers {
accepts(stream, stream) should be(true)
}
"streams" should "be accepted as an option, but not vice versa" in {
val stream: Type = StreamType(bool)
val opt: Type = OptionType(bool)
accepts(opt, stream) should be(true)
accepts(stream, opt) should be(false)
accepts(opt, opt) should be(true)
}
}