Arrows WIP

This commit is contained in:
dmitry 2021-03-03 15:46:31 +03:00
parent 07155ecdbf
commit 05970c248c
4 changed files with 198 additions and 2 deletions

View File

@ -1,6 +1,6 @@
package aqua
import aqua.context.{Abilities, AbilitiesResolve, ArgsAndVars, Types}
import aqua.context.{Abilities, AbilitiesResolve, ArgsAndVars, Arrows, Types}
import aqua.context.scope.ScopeWalker
import aqua.context.walker.Walker
import aqua.parser.Block
@ -21,6 +21,7 @@ object Aqua {
.andThen(new Types.ExpDef(_))
.andThen(new Abilities.ExpDef(_))
.andThen(new AbilitiesResolve.ExpDef(_))
.andThen(new Arrows.ExpDef(_))
def parse(input: String): ValidatedNel[AquaError, List[Block[Span.F, walker.Out]]] =
Validated

View File

@ -0,0 +1,105 @@
package aqua.context
import aqua.context.marker.{AbilityArrow, ArrowMarker, FuncArrow, LocalArrow}
import aqua.context.walker.Walker.{DupError, UnresolvedError}
import aqua.context.walker.{Acc, ExpectAndDefine, Walker}
import aqua.parser.{AbilityFuncCall, Block, DefFunc, DefService, Extract, FuncCall, FuncExpr}
import aqua.parser.lexer.{ArrowName, ArrowType}
import cats.{Comonad, Functor}
import shapeless._
import cats.syntax.comonad._
case class Arrows[F[_]](expDef: ExpectAndDefine[F, ArrowName[F], ArrowMarker[F]]) {
def clearLocal: Arrows[F] =
copy(expDef.collectDefinitions {
case fa: FuncArrow[F, _] => fa
case aa: AbilityArrow[F, _] => aa
})
def clearExpectations: Arrows[F] = copy(expDef.clearExpectations)
def expect(a: ArrowName[F])(implicit F: Comonad[F]): Arrows[F] =
copy(expDef.expect(Acc.one(a.name.extract, a)))
def defined(name: String, marker: ArrowMarker[F]): Arrows[F] =
copy(expDef.defined(Acc.one(name, marker)))
}
object Arrows {
type Acc[F[_]] = ExpectAndDefine[F, ArrowName[F], ArrowMarker[F]]
def emptyAcc[F[_]]: Acc[F] = ExpectAndDefine.empty[F, ArrowName[F], ArrowMarker[F]]
def empty[F[_]]: Arrows[F] = Arrows[F](emptyAcc[F])
case class DuplicateArrow[F[_]](name: String, marker: ArrowMarker[F]) extends Walker.DupError[F] {
override def toStringF(implicit F: Functor[F]): F[String] =
marker.toError(s"Duplicate function or arrow definition: ${name}")
}
case class UnresolvedArrow[F[_]](name: String, usage: ArrowName[F]) extends Walker.UnresolvedError[F] {
override def toStringF(implicit F: Functor[F]): F[String] = usage.as(s"Unresolved arrow $name")
}
class ExpDef[F[_]: Comonad, I <: HList, O <: HList](extend: Walker[F, I, O]) extends Walker[F, I, Arrows[F] :: O] {
type Ctx = Arrows[F] :: O
override def exitFuncExprGroup(group: FuncExpr[F, I], last: Ctx): Ctx =
last.head :: extend.exitFuncExprGroup(group, last.tail)
override def funcOpCtx(op: FuncExpr[F, I], prev: Ctx): Ctx =
(op match {
case FuncCall(a, _, _) =>
prev.head.expect(a)
case afc: AbilityFuncCall[F, I] =>
prev.head.expect(afc.abilityArrow)
case Extract(_, fc, _) =>
fc match {
case FuncCall(a, _, _) =>
prev.head.expect(a)
case afc: AbilityFuncCall[F, I] =>
prev.head.expect(afc.abilityArrow)
}
case _ =>
prev.head
}) :: extend.funcOpCtx(op, prev.tail)
override def blockCtx(block: Block[F, I]): Ctx =
(block match {
case DefFunc(head, _, _) =>
head.args
.foldLeft(empty[F]) {
case (acc, (k, _, at: ArrowType[F])) =>
acc.defined(k, LocalArrow(at))
case (acc, _) => acc
}
case DefService(ab, funcs, _) =>
val prefix = ab.name.extract + "."
funcs.toNel.foldLeft(empty[F]) {
case (acc, (n, at)) =>
acc.defined(prefix + n, AbilityArrow(ab, at))
}
case _ =>
empty[F]
}) :: extend.blockCtx(block)
override def duplicates(prev: Out, next: Out): List[DupError[F]] =
Walker.collectDups(prev.head.expDef, next.head.expDef, DuplicateArrow[F]) ::: extend
.duplicates(prev.tail, next.tail)
override def emptyCtx: Out = empty[F] :: extend.emptyCtx
override def combineBlockCtx(prev: Out, block: Out): Out =
Arrows(prev.head.expDef combineSeq block.head.expDef).clearLocal :: extend
.combineBlockCtx(prev.tail, block.tail)
override def unresolved(ctx: Out): (List[UnresolvedError[F]], Out) = {
val (extErrs, extCtx) = extend.unresolved(ctx.tail)
val (curErrs, curExpDef) = Walker.collectUnresolved(ctx.head.expDef, UnresolvedArrow[F])
(curErrs ::: extErrs, Arrows(curExpDef) :: extCtx)
}
}
}

View File

@ -1,7 +1,7 @@
package aqua.context.marker
import aqua.parser.DefFunc
import aqua.parser.lexer.{ArrowType, Token}
import aqua.parser.lexer.{Ability, ArrowType, Token}
sealed trait ArrowMarker[F[_]] extends Marker[F]
@ -12,3 +12,7 @@ case class LocalArrow[F[_], L](arr: ArrowType[F]) extends ArrowMarker[F] {
case class FuncArrow[F[_], L](funcDef: DefFunc[F, L]) extends ArrowMarker[F] {
override def pointer: Token[F] = funcDef.head.name
}
case class AbilityArrow[F[_], L](ability: Ability[F], arr: ArrowType[F]) extends ArrowMarker[F] {
override def pointer: Token[F] = arr
}

View File

@ -0,0 +1,86 @@
package aqua.context
import aqua.context.walker.Walker
import aqua.parser.Block
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.Id
import cats.data.Validated.Invalid
import cats.data.{NonEmptyList, Validated}
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import shapeless._
class ArrowsSpec extends AnyFlatSpec with Matchers with EitherValues {
val walker = Walker.hnil[Id].andThen(new Arrows.ExpDef(_))
def parseBlocks(str: String): List[Block[Id, Arrows[Id] :: HNil]] =
Validated
.fromEither(Block.blocks[Id].parseAll(str))
.leftMap(_.toString)
.leftMap(NonEmptyList.one)
.andThen(
walker.walkValidate
)
.toEither
.right
.value
def parseBlocksV(str: String) =
Validated
.fromEither(Block.blocks[Id].parseAll(str))
.leftMap(_.toString)
.leftMap(NonEmptyList.one)
.andThen(
walker.walkValidate
)
"Arrows walker" should "collect no ability resolutions from a function" in {
val bs = parseBlocks("""
|func some():
| Peer "peer"
| Peer.timestamp()
|
|""".stripMargin)
bs.length should be(1)
val ctx = bs.head.context
val acc = ctx.head.expDef
acc.expectAcc.keys should be('empty)
acc.defineAcc.keys should be('empty)
}
"Arrows walker" should "collect ability expectations from two functions" in {
val res = parseBlocksV("""
|func some():
| x <- First.arr()
|
|
|func other(x: i32):
| Peer "smth"
| y <- Second.arr2(x)
| Peer.timestamp()
|
|""".stripMargin)
res.isValid should be(false)
val Invalid(errs) = res
errs should have length (2)
}
"Arrows walker" should "resolve abilities in a function" in {
val res = parseBlocksV("""
|func some():
| y <- Smth.foo()
| x <- Ab.f(z, y)
|
|alias T: i32
|""".stripMargin)
res.isValid should be(false)
val Invalid(errs) = res
errs should have length (2)
}
}