mirror of
https://github.com/fluencelabs/aqua.git
synced 2025-04-24 22:42:13 +00:00
refactor(semantics): Refactor semantics to produce concrete tags [fixes LNG-201] (#776)
* Introduce IfTag, TryTag; Remove XorTag * Add IfTag, TryTag inlining * Fix test compilation * Fix test * Hack to fix topology * Support try otherwise syntax * Add comments * Refactor diff show * Handle ParTag.Par in single check, add tests
This commit is contained in:
parent
339d3a8217
commit
8ba7021cd4
@ -1,7 +1,8 @@
|
|||||||
package aqua.lsp
|
package aqua.lsp
|
||||||
|
|
||||||
import aqua.parser.lexer.Token
|
import aqua.parser.lexer.Token
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
import aqua.semantics.rules.StackInterpreter
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.semantics.rules.locations.{LocationsAlgebra, LocationsState}
|
import aqua.semantics.rules.locations.{LocationsAlgebra, LocationsState}
|
||||||
import cats.data.State
|
import cats.data.State
|
||||||
import monocle.Lens
|
import monocle.Lens
|
||||||
@ -10,7 +11,7 @@ import scribe.Logging
|
|||||||
|
|
||||||
class LocationsInterpreter[S[_], X](implicit
|
class LocationsInterpreter[S[_], X](implicit
|
||||||
lens: Lens[X, LocationsState[S]],
|
lens: Lens[X, LocationsState[S]],
|
||||||
error: ReportError[S, X]
|
error: ReportErrors[S, X]
|
||||||
) extends LocationsAlgebra[S, State[X, *]] with Logging {
|
) extends LocationsAlgebra[S, State[X, *]] with Logging {
|
||||||
|
|
||||||
type SX[A] = State[X, A]
|
type SX[A] = State[X, A]
|
||||||
|
@ -3,9 +3,9 @@ package aqua.lsp
|
|||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
import aqua.parser.head.{ImportExpr, ImportFromExpr, UseExpr, UseFromExpr}
|
import aqua.parser.head.{ImportExpr, ImportFromExpr, UseExpr, UseFromExpr}
|
||||||
import aqua.parser.lexer.{LiteralToken, Token}
|
import aqua.parser.lexer.{LiteralToken, Token}
|
||||||
import aqua.semantics.rules.ReportError
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.semantics.rules.locations.LocationsState
|
import aqua.semantics.rules.locations.LocationsState
|
||||||
import aqua.semantics.{CompilerState, RulesViolated, SemanticError, Semantics}
|
import aqua.semantics.{CompilerState, RawSemantics, RulesViolated, SemanticError, Semantics}
|
||||||
import cats.data.Validated.{Invalid, Valid}
|
import cats.data.Validated.{Invalid, Valid}
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
import cats.syntax.apply.*
|
import cats.syntax.apply.*
|
||||||
@ -57,14 +57,14 @@ class LspSemantics[S[_]] extends Semantics[S, LspContext[S]] {
|
|||||||
GenLens[CompilerState[S]](_.locations)
|
GenLens[CompilerState[S]](_.locations)
|
||||||
|
|
||||||
import monocle.syntax.all.*
|
import monocle.syntax.all.*
|
||||||
implicit val re: ReportError[S, CompilerState[S]] =
|
implicit val re: ReportErrors[S, CompilerState[S]] =
|
||||||
(st: CompilerState[S], token: Token[S], hints: List[String]) =>
|
(st: CompilerState[S], token: Token[S], hints: List[String]) =>
|
||||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||||
|
|
||||||
implicit val locationsInterpreter: LocationsInterpreter[S, CompilerState[S]] =
|
implicit val locationsInterpreter: LocationsInterpreter[S, CompilerState[S]] =
|
||||||
new LocationsInterpreter[S, CompilerState[S]]()
|
new LocationsInterpreter[S, CompilerState[S]]()
|
||||||
|
|
||||||
Semantics
|
RawSemantics
|
||||||
.interpret(ast, initState, init.raw)
|
.interpret(ast, initState, init.raw)
|
||||||
.map { case (state, ctx) =>
|
.map { case (state, ctx) =>
|
||||||
NonEmptyChain
|
NonEmptyChain
|
||||||
|
@ -10,12 +10,15 @@ import aqua.raw.value.*
|
|||||||
import aqua.types.{ArrayType, ArrowType, BoxType, CanonStreamType, StreamType}
|
import aqua.types.{ArrayType, ArrowType, BoxType, CanonStreamType, StreamType}
|
||||||
import cats.syntax.traverse.*
|
import cats.syntax.traverse.*
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
|
import cats.syntax.flatMap.*
|
||||||
|
import cats.syntax.apply.*
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
import cats.syntax.option.*
|
import cats.syntax.option.*
|
||||||
import cats.instances.list.*
|
import cats.instances.list.*
|
||||||
import cats.data.{Chain, State, StateT}
|
import cats.data.{Chain, State, StateT}
|
||||||
import cats.syntax.show.*
|
import cats.syntax.show.*
|
||||||
import scribe.{log, Logging}
|
import scribe.{log, Logging}
|
||||||
|
import aqua.model.inline.Inline.parDesugarPrefixOpt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [[TagInliner]] prepares a [[RawTag]] for futher processing by converting [[ValueRaw]]s into [[ValueModel]]s.
|
* [[TagInliner]] prepares a [[RawTag]] for futher processing by converting [[ValueRaw]]s into [[ValueModel]]s.
|
||||||
@ -30,13 +33,66 @@ object TagInliner extends Logging {
|
|||||||
|
|
||||||
import RawValueInliner.{callToModel, valueListToModel, valueToModel}
|
import RawValueInliner.{callToModel, valueListToModel, valueToModel}
|
||||||
|
|
||||||
import Inline.*
|
import Inline.parDesugarPrefix
|
||||||
|
|
||||||
private def pure[S](op: OpModel): State[S, (Option[OpModel], Option[OpModel.Tree])] =
|
/**
|
||||||
State.pure(Some(op) -> None)
|
* Result of [[RawTag]] inlining
|
||||||
|
*
|
||||||
|
* @param prefix Previous instructions
|
||||||
|
*/
|
||||||
|
enum TagInlined(prefix: Option[OpModel.Tree]) {
|
||||||
|
|
||||||
private def none[S]: State[S, (Option[OpModel], Option[OpModel.Tree])] =
|
/**
|
||||||
State.pure(None -> None)
|
* Tag inlining emitted nothing
|
||||||
|
*/
|
||||||
|
case Empty(
|
||||||
|
prefix: Option[OpModel.Tree] = None
|
||||||
|
) extends TagInlined(prefix)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag inlining emitted one parent model
|
||||||
|
*
|
||||||
|
* @param model Model which will wrap children
|
||||||
|
*/
|
||||||
|
case Single(
|
||||||
|
model: OpModel,
|
||||||
|
prefix: Option[OpModel.Tree] = None
|
||||||
|
) extends TagInlined(prefix)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag inling emitted complex transformation
|
||||||
|
*
|
||||||
|
* @param toModel Function from children results to result of this tag
|
||||||
|
*/
|
||||||
|
case Mapping(
|
||||||
|
toModel: Chain[OpModel.Tree] => OpModel.Tree,
|
||||||
|
prefix: Option[OpModel.Tree] = None
|
||||||
|
) extends TagInlined(prefix)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize inlining, construct a tree
|
||||||
|
*
|
||||||
|
* @param children Children results
|
||||||
|
* @return Result of inlining
|
||||||
|
*/
|
||||||
|
def build(children: Chain[OpModel.Tree]): OpModel.Tree = {
|
||||||
|
val inlined = this match {
|
||||||
|
case Empty(_) => children
|
||||||
|
case Single(model, _) =>
|
||||||
|
Chain.one(model.wrap(children))
|
||||||
|
case Mapping(toModel, _) =>
|
||||||
|
Chain.one(toModel(children))
|
||||||
|
}
|
||||||
|
|
||||||
|
SeqModel.wrap(Chain.fromOption(prefix) ++ inlined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def pure[S](op: OpModel): State[S, TagInlined] =
|
||||||
|
TagInlined.Single(model = op).pure
|
||||||
|
|
||||||
|
private def none[S]: State[S, TagInlined] =
|
||||||
|
TagInlined.Empty().pure
|
||||||
|
|
||||||
private def combineOpsWithSeq(l: Option[OpModel.Tree], r: Option[OpModel.Tree]) =
|
private def combineOpsWithSeq(l: Option[OpModel.Tree], r: Option[OpModel.Tree]) =
|
||||||
l match {
|
l match {
|
||||||
@ -120,7 +176,7 @@ object TagInliner extends Logging {
|
|||||||
def tagToModel[S: Mangler: Arrows: Exports](
|
def tagToModel[S: Mangler: Arrows: Exports](
|
||||||
tag: RawTag,
|
tag: RawTag,
|
||||||
treeFunctionName: String
|
treeFunctionName: String
|
||||||
): State[S, (Option[OpModel], Option[OpModel.Tree])] =
|
): State[S, TagInlined] =
|
||||||
tag match {
|
tag match {
|
||||||
case OnTag(peerId, via) =>
|
case OnTag(peerId, via) =>
|
||||||
for {
|
for {
|
||||||
@ -133,21 +189,52 @@ object TagInliner extends Logging {
|
|||||||
viaD = Chain.fromSeq(viaDeFlattened.map(_._1))
|
viaD = Chain.fromSeq(viaDeFlattened.map(_._1))
|
||||||
viaF = viaDeFlattened.flatMap(_._2)
|
viaF = viaDeFlattened.flatMap(_._2)
|
||||||
|
|
||||||
} yield Some(OnModel(pid, viaD)) -> parDesugarPrefix(viaF.prependedAll(pif))
|
} yield TagInlined.Single(
|
||||||
|
model = OnModel(pid, viaD),
|
||||||
case MatchMismatchTag(left, right, shouldMatch) =>
|
prefix = parDesugarPrefix(viaF.prependedAll(pif))
|
||||||
for {
|
|
||||||
ld <- valueToModel(left)
|
|
||||||
rd <- valueToModel(right)
|
|
||||||
ldCanon <- canonicalizeIfStream(ld._1, ld._2)
|
|
||||||
rdCanon <- canonicalizeIfStream(rd._1, rd._2)
|
|
||||||
} yield Some(
|
|
||||||
MatchMismatchModel(ldCanon._1, rdCanon._1, shouldMatch)
|
|
||||||
) -> parDesugarPrefixOpt(
|
|
||||||
ldCanon._2,
|
|
||||||
rdCanon._2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case IfTag(leftRaw, rightRaw, shouldMatch) =>
|
||||||
|
(
|
||||||
|
valueToModel(leftRaw) >>= canonicalizeIfStream.tupled,
|
||||||
|
valueToModel(rightRaw) >>= canonicalizeIfStream.tupled
|
||||||
|
).mapN { case ((leftModel, leftPrefix), (rightModel, rightPrefix)) =>
|
||||||
|
val prefix = parDesugarPrefixOpt(leftPrefix, rightPrefix)
|
||||||
|
val toModel = (children: Chain[OpModel.Tree]) =>
|
||||||
|
XorModel.wrap(
|
||||||
|
children.uncons.map { case (ifBody, elseBody) =>
|
||||||
|
val elseBodyFiltered = elseBody.filterNot(
|
||||||
|
_.head == EmptyModel
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hack for xor with mismatch always have second branch
|
||||||
|
* TODO: Fix this in topology
|
||||||
|
* see https://linear.app/fluence/issue/LNG-69/if-inside-on-produces-invalid-topology
|
||||||
|
*/
|
||||||
|
val elseBodyAugmented =
|
||||||
|
if (elseBodyFiltered.isEmpty)
|
||||||
|
Chain.one(
|
||||||
|
NullModel.leaf
|
||||||
|
)
|
||||||
|
else elseBodyFiltered
|
||||||
|
|
||||||
|
MatchMismatchModel(
|
||||||
|
leftModel,
|
||||||
|
rightModel,
|
||||||
|
shouldMatch
|
||||||
|
).wrap(ifBody) +: elseBodyAugmented
|
||||||
|
}.getOrElse(children)
|
||||||
|
)
|
||||||
|
|
||||||
|
TagInlined.Mapping(
|
||||||
|
toModel = toModel,
|
||||||
|
prefix = prefix
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case TryTag => pure(XorModel)
|
||||||
|
|
||||||
case ForTag(item, iterable, mode) =>
|
case ForTag(item, iterable, mode) =>
|
||||||
for {
|
for {
|
||||||
vp <- valueToModel(iterable)
|
vp <- valueToModel(iterable)
|
||||||
@ -164,18 +251,21 @@ object TagInliner extends Logging {
|
|||||||
iterable.`type`
|
iterable.`type`
|
||||||
}
|
}
|
||||||
_ <- Exports[S].resolved(item, VarModel(n, elementType))
|
_ <- Exports[S].resolved(item, VarModel(n, elementType))
|
||||||
} yield {
|
m = mode.map {
|
||||||
val m = mode.map {
|
|
||||||
case ForTag.WaitMode => ForModel.NeverMode
|
case ForTag.WaitMode => ForModel.NeverMode
|
||||||
case ForTag.PassMode => ForModel.NullMode
|
case ForTag.PassMode => ForModel.NullMode
|
||||||
}
|
}
|
||||||
|
} yield TagInlined.Single(
|
||||||
Some(ForModel(n, v, m)) -> p
|
model = ForModel(n, v, m),
|
||||||
}
|
prefix = p
|
||||||
|
)
|
||||||
|
|
||||||
case PushToStreamTag(operand, exportTo) =>
|
case PushToStreamTag(operand, exportTo) =>
|
||||||
valueToModel(operand).map { case (v, p) =>
|
valueToModel(operand).map { case (v, p) =>
|
||||||
Some(PushToStreamModel(v, CallModel.callExport(exportTo))) -> p
|
TagInlined.Single(
|
||||||
|
model = PushToStreamModel(v, CallModel.callExport(exportTo)),
|
||||||
|
prefix = p
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case CanonicalizeTag(operand, exportTo) =>
|
case CanonicalizeTag(operand, exportTo) =>
|
||||||
@ -184,9 +274,14 @@ object TagInliner extends Logging {
|
|||||||
case (l @ LiteralModel(_, _), p) =>
|
case (l @ LiteralModel(_, _), p) =>
|
||||||
for {
|
for {
|
||||||
_ <- Exports[S].resolved(exportTo.name, l)
|
_ <- Exports[S].resolved(exportTo.name, l)
|
||||||
} yield None -> p
|
} yield TagInlined.Empty(prefix = p)
|
||||||
case (v, p) =>
|
case (v, p) =>
|
||||||
State.pure(Some(CanonicalizeModel(v, CallModel.callExport(exportTo))) -> p)
|
TagInlined
|
||||||
|
.Single(
|
||||||
|
model = CanonicalizeModel(v, CallModel.callExport(exportTo)),
|
||||||
|
prefix = p
|
||||||
|
)
|
||||||
|
.pure
|
||||||
}
|
}
|
||||||
|
|
||||||
case FlattenTag(operand, assignTo) =>
|
case FlattenTag(operand, assignTo) =>
|
||||||
@ -195,9 +290,14 @@ object TagInliner extends Logging {
|
|||||||
case (l @ LiteralModel(_, _), p) =>
|
case (l @ LiteralModel(_, _), p) =>
|
||||||
for {
|
for {
|
||||||
_ <- Exports[S].resolved(assignTo, l)
|
_ <- Exports[S].resolved(assignTo, l)
|
||||||
} yield None -> p
|
} yield TagInlined.Empty(prefix = p)
|
||||||
case (v, p) =>
|
case (v, p) =>
|
||||||
State.pure(Some(FlattenModel(v, assignTo)) -> p)
|
TagInlined
|
||||||
|
.Single(
|
||||||
|
model = FlattenModel(v, assignTo),
|
||||||
|
prefix = p
|
||||||
|
)
|
||||||
|
.pure
|
||||||
}
|
}
|
||||||
|
|
||||||
case JoinTag(operands) =>
|
case JoinTag(operands) =>
|
||||||
@ -205,13 +305,19 @@ object TagInliner extends Logging {
|
|||||||
.traverse(o => valueToModel(o))
|
.traverse(o => valueToModel(o))
|
||||||
.map(nel => {
|
.map(nel => {
|
||||||
logger.trace("join after " + nel.map(_._1))
|
logger.trace("join after " + nel.map(_._1))
|
||||||
// None because join behaviour will be processed in ApplyPropertiesRawInliner
|
// Empty because join behaviour will be processed in ApplyPropertiesRawInliner
|
||||||
None -> parDesugarPrefix(nel.toList.flatMap(_._2))
|
TagInlined.Empty(prefix = parDesugarPrefix(nel.toList.flatMap(_._2)))
|
||||||
})
|
})
|
||||||
|
|
||||||
case CallArrowRawTag(exportTo, value: CallArrowRaw) =>
|
case CallArrowRawTag(exportTo, value: CallArrowRaw) =>
|
||||||
CallArrowRawInliner.unfoldArrow(value, exportTo).flatMap { case (_, inline) =>
|
CallArrowRawInliner.unfoldArrow(value, exportTo).flatMap { case (_, inline) =>
|
||||||
RawValueInliner.inlineToTree(inline).map(tree => (None, Some(SeqModel.wrap(tree: _*))))
|
RawValueInliner
|
||||||
|
.inlineToTree(inline)
|
||||||
|
.map(tree =>
|
||||||
|
TagInlined.Empty(
|
||||||
|
prefix = SeqModel.wrap(tree).some
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case AssignmentTag(value, assignTo) =>
|
case AssignmentTag(value, assignTo) =>
|
||||||
@ -226,43 +332,41 @@ object TagInliner extends Logging {
|
|||||||
// NOTE: Name <assignTo> should not exist yet
|
// NOTE: Name <assignTo> should not exist yet
|
||||||
_ <- Mangler[S].forbidName(assignTo)
|
_ <- Mangler[S].forbidName(assignTo)
|
||||||
_ <- Exports[S].resolved(assignTo, model)
|
_ <- Exports[S].resolved(assignTo, model)
|
||||||
} yield None -> prefix
|
} yield TagInlined.Empty(prefix = prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ClosureTag(arrow, detach) =>
|
case ClosureTag(arrow, detach) =>
|
||||||
if (detach) Arrows[S].resolved(arrow, None).map(_ => None -> None)
|
if (detach) Arrows[S].resolved(arrow, None).as(TagInlined.Empty())
|
||||||
else
|
else
|
||||||
for {
|
for {
|
||||||
t <- Mangler[S].findAndForbidName(arrow.name)
|
t <- Mangler[S].findAndForbidName(arrow.name)
|
||||||
_ <- Arrows[S].resolved(arrow, Some(t))
|
_ <- Arrows[S].resolved(arrow, Some(t))
|
||||||
} yield Some(CaptureTopologyModel(t)) -> None
|
} yield TagInlined.Single(model = CaptureTopologyModel(t))
|
||||||
|
|
||||||
case NextTag(item) =>
|
case NextTag(item) =>
|
||||||
for {
|
for {
|
||||||
exps <- Exports[S].exports
|
exps <- Exports[S].exports
|
||||||
} yield {
|
model = exps.get(item).collect { case VarModel(n, _, _) =>
|
||||||
exps.get(item).collect { case VarModel(n, _, _) =>
|
|
||||||
NextModel(n)
|
NextModel(n)
|
||||||
} -> None
|
|
||||||
}
|
}
|
||||||
|
} yield model.fold(TagInlined.Empty())(m => TagInlined.Single(model = m))
|
||||||
|
|
||||||
case RestrictionTag(name, isStream) =>
|
case RestrictionTag(name, isStream) =>
|
||||||
pure(RestrictionModel(name, isStream))
|
pure(RestrictionModel(name, isStream))
|
||||||
|
|
||||||
case _: SeqGroupTag => pure(SeqModel)
|
|
||||||
case ParTag.Detach => pure(DetachModel)
|
|
||||||
case _: ParGroupTag => pure(ParModel)
|
|
||||||
case XorTag | XorTag.LeftBiased =>
|
|
||||||
pure(XorModel)
|
|
||||||
case DeclareStreamTag(value) =>
|
case DeclareStreamTag(value) =>
|
||||||
value match
|
value match
|
||||||
case VarRaw(name, _) =>
|
case VarRaw(name, _) =>
|
||||||
for {
|
for {
|
||||||
cd <- valueToModel(value)
|
cd <- valueToModel(value)
|
||||||
_ <- Exports[S].resolved(name, cd._1)
|
_ <- Exports[S].resolved(name, cd._1)
|
||||||
} yield None -> cd._2
|
} yield TagInlined.Empty(prefix = cd._2)
|
||||||
case _ => none
|
case _ => none
|
||||||
|
|
||||||
|
case _: SeqGroupTag => pure(SeqModel)
|
||||||
|
case ParTag.Detach => pure(DetachModel)
|
||||||
|
case _: ParGroupTag => pure(ParModel)
|
||||||
|
|
||||||
case _: NoExecTag => none
|
case _: NoExecTag => none
|
||||||
case _ =>
|
case _ =>
|
||||||
logger.warn(s"Tag $tag must have been eliminated at this point")
|
logger.warn(s"Tag $tag must have been eliminated at this point")
|
||||||
@ -271,16 +375,13 @@ object TagInliner extends Logging {
|
|||||||
|
|
||||||
private def traverseS[S](
|
private def traverseS[S](
|
||||||
cf: RawTag.Tree,
|
cf: RawTag.Tree,
|
||||||
f: RawTag => State[S, (Option[OpModel], Option[OpModel.Tree])]
|
f: RawTag => State[S, TagInlined]
|
||||||
): State[S, OpModel.Tree] =
|
): State[S, OpModel.Tree] =
|
||||||
for {
|
for {
|
||||||
headTree <- f(cf.head)
|
headInlined <- f(cf.head)
|
||||||
tail <- StateT.liftF(cf.tail)
|
tail <- StateT.liftF(cf.tail)
|
||||||
tailTree <- tail.traverse(traverseS[S](_, f))
|
children <- tail.traverse(traverseS[S](_, f))
|
||||||
} yield headTree match {
|
} yield headInlined.build(children)
|
||||||
case (Some(m), prefix) => SeqModel.wrap(prefix.toList :+ m.wrap(tailTree.toList: _*): _*)
|
|
||||||
case (None, prefix) => SeqModel.wrap(prefix.toList ++ tailTree.toList: _*)
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleTree[S: Exports: Mangler: Arrows](
|
def handleTree[S: Exports: Mangler: Arrows](
|
||||||
tree: RawTag.Tree,
|
tree: RawTag.Tree,
|
||||||
|
@ -8,13 +8,15 @@ import aqua.types.{ScalarType, StreamType}
|
|||||||
import org.scalatest.flatspec.AnyFlatSpec
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
import cats.syntax.show.*
|
import cats.syntax.show.*
|
||||||
|
import org.scalatest.Inside
|
||||||
|
import aqua.model.inline.TagInliner.TagInlined
|
||||||
|
|
||||||
class TagInlinerSpec extends AnyFlatSpec with Matchers {
|
class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||||
|
|
||||||
"CanonicalizeTag" should "pass literals as is" in {
|
"CanonicalizeTag" should "pass literals as is" in {
|
||||||
val canonTo = "canon_to"
|
val canonTo = "canon_to"
|
||||||
|
|
||||||
val model = TagInliner
|
val (state, inlined) = TagInliner
|
||||||
.tagToModel[InliningState](
|
.tagToModel[InliningState](
|
||||||
CanonicalizeTag(ValueRaw.Nil, Call.Export(canonTo, StreamType(ScalarType.string))),
|
CanonicalizeTag(ValueRaw.Nil, Call.Export(canonTo, StreamType(ScalarType.string))),
|
||||||
""
|
""
|
||||||
@ -22,15 +24,20 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers {
|
|||||||
.run(InliningState())
|
.run(InliningState())
|
||||||
.value
|
.value
|
||||||
|
|
||||||
model._1.resolvedExports(canonTo) shouldBe LiteralModel(ValueRaw.Nil.value, ValueRaw.Nil.baseType)
|
state.resolvedExports(canonTo) shouldBe LiteralModel(
|
||||||
model._2._1 shouldBe None
|
ValueRaw.Nil.value,
|
||||||
model._2._2 shouldBe None
|
ValueRaw.Nil.baseType
|
||||||
|
)
|
||||||
|
|
||||||
|
inside(inlined) { case TagInlined.Empty(prefix) =>
|
||||||
|
prefix shouldBe None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"FlattenTag" should "pass literals as is" in {
|
"FlattenTag" should "pass literals as is" in {
|
||||||
val canonTo = "canon_to"
|
val canonTo = "canon_to"
|
||||||
|
|
||||||
val model = TagInliner
|
val (state, inlined) = TagInliner
|
||||||
.tagToModel[InliningState](
|
.tagToModel[InliningState](
|
||||||
FlattenTag(ValueRaw.Nil, canonTo),
|
FlattenTag(ValueRaw.Nil, canonTo),
|
||||||
""
|
""
|
||||||
@ -38,11 +45,13 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers {
|
|||||||
.run(InliningState())
|
.run(InliningState())
|
||||||
.value
|
.value
|
||||||
|
|
||||||
model._1.resolvedExports(canonTo) shouldBe LiteralModel(
|
state.resolvedExports(canonTo) shouldBe LiteralModel(
|
||||||
ValueRaw.Nil.value,
|
ValueRaw.Nil.value,
|
||||||
ValueRaw.Nil.baseType
|
ValueRaw.Nil.baseType
|
||||||
)
|
)
|
||||||
model._2._1 shouldBe None
|
|
||||||
model._2._2 shouldBe None
|
inside(inlined) { case TagInlined.Empty(prefix) =>
|
||||||
|
prefix shouldBe None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,4 @@ import cats.Monad
|
|||||||
import cats.data.State
|
import cats.data.State
|
||||||
import cats.data.StateT
|
import cats.data.StateT
|
||||||
|
|
||||||
case class FuncOp(tree: RawTag.Tree) extends Raw {
|
case class FuncOp(tree: RawTag.Tree) extends Raw
|
||||||
def isRightAssoc: Boolean = RawTag.isRightAssoc(tree.head)
|
|
||||||
|
|
||||||
def :+:(prev: FuncOp): FuncOp = FuncOp(RawTag.rightAssocCombine(prev.tree, tree))
|
|
||||||
}
|
|
||||||
|
@ -60,19 +60,51 @@ sealed trait ParGroupTag extends GroupTag
|
|||||||
|
|
||||||
case object SeqTag extends SeqGroupTag {
|
case object SeqTag extends SeqGroupTag {
|
||||||
|
|
||||||
override def wrap(children: Tree*): Tree =
|
override def wrap(children: Chain[Tree]): Tree =
|
||||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyTag).toList, RawTag.empty)
|
super.wrapNonEmpty(children.filterNot(_.head == EmptyTag), RawTag.empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
case object ParTag extends ParGroupTag {
|
case object ParTag extends ParGroupTag {
|
||||||
case object Detach extends ParGroupTag
|
|
||||||
|
/**
|
||||||
|
* Used for `co` instruction
|
||||||
|
*/
|
||||||
|
case object Detach extends GroupTag
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tag should be eliminated in semantics
|
||||||
|
* and merged with [[ParTag]]
|
||||||
|
*
|
||||||
|
* Used for `par` instruction
|
||||||
|
*/
|
||||||
|
case object Par extends GroupTag
|
||||||
}
|
}
|
||||||
|
|
||||||
case object XorTag extends GroupTag {
|
case class IfTag(left: ValueRaw, right: ValueRaw, equal: Boolean) extends GroupTag
|
||||||
case object LeftBiased extends GroupTag
|
|
||||||
|
object IfTag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tag should be eliminated in semantics
|
||||||
|
* and merged with [[IfTag]]
|
||||||
|
*/
|
||||||
|
case object Else extends GroupTag
|
||||||
}
|
}
|
||||||
|
|
||||||
case class XorParTag(xor: RawTag.Tree, par: RawTag.Tree) extends RawTag
|
case object TryTag extends GroupTag {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tag should be eliminated in semantics
|
||||||
|
* and merged with [[TryTag]]
|
||||||
|
*/
|
||||||
|
case object Catch extends GroupTag
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tag should be eliminated in semantics
|
||||||
|
* and merged with [[TryTag]]
|
||||||
|
*/
|
||||||
|
case object Otherwise extends GroupTag
|
||||||
|
}
|
||||||
|
|
||||||
case class OnTag(peerId: ValueRaw, via: Chain[ValueRaw]) extends SeqGroupTag {
|
case class OnTag(peerId: ValueRaw, via: Chain[ValueRaw]) extends SeqGroupTag {
|
||||||
|
|
||||||
@ -97,13 +129,6 @@ case class RestrictionTag(name: String, isStream: Boolean) extends SeqGroupTag {
|
|||||||
copy(name = map.getOrElse(name, name))
|
copy(name = map.getOrElse(name, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
case class MatchMismatchTag(left: ValueRaw, right: ValueRaw, shouldMatch: Boolean)
|
|
||||||
extends SeqGroupTag {
|
|
||||||
|
|
||||||
override def mapValues(f: ValueRaw => ValueRaw): RawTag =
|
|
||||||
MatchMismatchTag(left.map(f), right.map(f), shouldMatch)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class ForTag(item: String, iterable: ValueRaw, mode: Option[ForTag.Mode] = None)
|
case class ForTag(item: String, iterable: ValueRaw, mode: Option[ForTag.Mode] = None)
|
||||||
extends SeqGroupTag {
|
extends SeqGroupTag {
|
||||||
|
|
||||||
|
@ -9,92 +9,34 @@ import cats.syntax.semigroup.*
|
|||||||
|
|
||||||
trait RawTagGivens {
|
trait RawTagGivens {
|
||||||
|
|
||||||
def isRightAssoc(tag: RawTag): Boolean = tag match {
|
|
||||||
case XorTag | ParTag => true
|
|
||||||
case _: XorParTag => true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert some tags in tree to fix corner cases
|
|
||||||
def fixCornerCases(tree: RawTag.Tree): RawTag.Tree =
|
|
||||||
Cofree
|
|
||||||
.cata[Chain, RawTag, RawTag.Tree](tree) {
|
|
||||||
case (XorParTag(left, right), _) =>
|
|
||||||
Eval.now(
|
|
||||||
ParTag.wrap(
|
|
||||||
XorTag.wrap(left),
|
|
||||||
right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case (XorTag.LeftBiased, tail) =>
|
|
||||||
// TODO: fix me in topology
|
|
||||||
// https://linear.app/fluence/issue/LNG-69/if-inside-on-produces-invalid-topology
|
|
||||||
Eval.now(
|
|
||||||
Cofree(
|
|
||||||
XorTag,
|
|
||||||
Eval.now(
|
|
||||||
tail.append(
|
|
||||||
CallArrowRawTag.service(LiteralRaw.quote("op"), "noop", Call(Nil, Nil)).leaf
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case (head, tail) => Eval.now(Cofree(head, Eval.now(tail)))
|
|
||||||
}
|
|
||||||
.value
|
|
||||||
|
|
||||||
given Semigroup[RawTag.Tree] with
|
given Semigroup[RawTag.Tree] with
|
||||||
|
|
||||||
override def combine(x: RawTag.Tree, y: RawTag.Tree): RawTag.Tree = {
|
override def combine(x: RawTag.Tree, y: RawTag.Tree): RawTag.Tree = {
|
||||||
// Remove right-asscoc protection of Seq with single child
|
// Remove seq with single child
|
||||||
val flatX = SeqGroupTag.ungroupSingle(x)
|
val flatX = SeqGroupTag.ungroupSingle(x)
|
||||||
val flatY = SeqGroupTag.ungroupSingle(y)
|
val flatY = SeqGroupTag.ungroupSingle(y)
|
||||||
(flatX.head, flatY.head) match {
|
(flatX.head, flatY.head) match {
|
||||||
case (_, XorParTag(xor, par)) => combine(combine(flatX, xor), par)
|
case (SeqTag, SeqTag) => flatX.copy(tail = (flatX.tail, flatY.tail).mapN(_ ++ _))
|
||||||
case (XorParTag(xor, par), _) => combine(combine(xor, par), flatY)
|
|
||||||
case (SeqTag, SeqTag) => flatY.copy(tail = (flatX.tail, flatY.tail).mapN(_ ++ _))
|
|
||||||
case (_, SeqTag) => flatY.copy(tail = flatY.tail.map(_.prepend(flatX)))
|
case (_, SeqTag) => flatY.copy(tail = flatY.tail.map(_.prepend(flatX)))
|
||||||
case (SeqTag, _) => flatX.copy(tail = flatX.tail.map(_.append(flatY)))
|
case (SeqTag, _) => flatX.copy(tail = flatX.tail.map(_.append(flatY)))
|
||||||
case _ => SeqTag.wrap(flatX, flatY)
|
case _ => SeqTag.wrap(flatX, flatY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Semigroup for foldRight processing
|
|
||||||
def rightAssocCombine(x: RawTag.Tree, y: RawTag.Tree): RawTag.Tree =
|
|
||||||
(x.head, y.head) match {
|
|
||||||
case (_: ParGroupTag, ParTag) =>
|
|
||||||
y.copy(tail = (x.tail, y.tail).mapN(_ ++ _))
|
|
||||||
case (XorTag, XorTag) =>
|
|
||||||
y.copy(tail = (x.tail, y.tail).mapN(_ ++ _))
|
|
||||||
case (XorTag.LeftBiased, XorTag) =>
|
|
||||||
SeqGroupTag.wrap(y.copy(tail = (x.tail, y.tail).mapN(_ ++ _)))
|
|
||||||
case (XorTag, ParTag) => XorParTag(x, y).leaf
|
|
||||||
case (_, ParTag | XorTag) =>
|
|
||||||
// When right-associative tag is combined with left-associative,
|
|
||||||
// we need result to be left-associative to prevent greedy behavior.
|
|
||||||
// SeqGroupTag does just this.
|
|
||||||
SeqGroupTag.wrap(y.copy(tail = y.tail.map(_.prepend(x))))
|
|
||||||
case (_, XorParTag(xor, par)) =>
|
|
||||||
rightAssocCombine(rightAssocCombine(x, xor), par)
|
|
||||||
case _ => x |+| y
|
|
||||||
}
|
|
||||||
|
|
||||||
extension (tree: RawTag.Tree)
|
extension (tree: RawTag.Tree)
|
||||||
|
|
||||||
def toFuncOp: FuncOp = FuncOp(tree)
|
def toFuncOp: FuncOp = FuncOp(tree)
|
||||||
|
|
||||||
def rename(vals: Map[String, String]): RawTag.Tree =
|
def rename(vals: Map[String, String]): RawTag.Tree =
|
||||||
if (vals.isEmpty) tree
|
if (vals.isEmpty) tree
|
||||||
else
|
else tree.map(_.mapValues(_.renameVars(vals)).renameExports(vals))
|
||||||
tree.map[RawTag](_.mapValues(_.renameVars(vals)).renameExports(vals))
|
|
||||||
|
|
||||||
def renameExports(vals: Map[String, String]): RawTag.Tree =
|
def renameExports(vals: Map[String, String]): RawTag.Tree =
|
||||||
if (vals.isEmpty) tree
|
if (vals.isEmpty) tree
|
||||||
else
|
else tree.map(_.renameExports(vals))
|
||||||
tree.map[RawTag](_.renameExports(vals))
|
|
||||||
|
|
||||||
def definesVarNames: Eval[Set[String]] =
|
def definesVarNames: Eval[Set[String]] =
|
||||||
Cofree.cata[Chain, RawTag, Set[String]](tree) { case (tag, acc) =>
|
Cofree.cata(tree) { case (tag, acc) =>
|
||||||
Eval.later(acc.foldLeft(tag.definesVarNames)(_ ++ _))
|
Eval.later(acc.foldLeft(tag.definesVarNames)(_ ++ _))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ object FuncArrow {
|
|||||||
): FuncArrow =
|
): FuncArrow =
|
||||||
FuncArrow(
|
FuncArrow(
|
||||||
raw.name,
|
raw.name,
|
||||||
RawTag.fixCornerCases(raw.arrow.body),
|
raw.arrow.body,
|
||||||
raw.arrow.`type`,
|
raw.arrow.`type`,
|
||||||
raw.arrow.ret,
|
raw.arrow.ret,
|
||||||
arrows,
|
arrows,
|
||||||
|
@ -71,13 +71,8 @@ sealed trait ParGroupModel extends GroupOpModel
|
|||||||
|
|
||||||
case object SeqModel extends SeqGroupModel {
|
case object SeqModel extends SeqGroupModel {
|
||||||
|
|
||||||
override def wrap(children: Tree*): Tree =
|
override def wrap(children: Chain[Tree]): Tree =
|
||||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyModel).toList, EmptyModel.leaf)
|
super.wrapNonEmpty(children.filterNot(_.head == EmptyModel), EmptyModel.leaf)
|
||||||
|
|
||||||
// EmptyModel allowed – useful for tests
|
|
||||||
def wrapWithEmpty(children: Tree*): Tree =
|
|
||||||
super.wrapNonEmpty(children.toList, EmptyModel.leaf)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case object ParModel extends ParGroupModel
|
case object ParModel extends ParGroupModel
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package aqua.tree
|
package aqua.tree
|
||||||
|
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
|
import cats.data.Chain.*
|
||||||
import cats.free.Cofree
|
import cats.free.Cofree
|
||||||
import cats.Eval
|
import cats.Eval
|
||||||
|
|
||||||
@ -16,10 +17,10 @@ trait TreeNode[T <: TreeNode[T]] {
|
|||||||
|
|
||||||
def wrap(children: Chain[Tree]): Tree = Cofree(self, Eval.now(children))
|
def wrap(children: Chain[Tree]): Tree = Cofree(self, Eval.now(children))
|
||||||
|
|
||||||
protected def wrapNonEmpty(children: List[Tree], empty: Tree): Tree = children match {
|
protected def wrapNonEmpty(children: Chain[Tree], empty: Tree): Tree = children match {
|
||||||
case Nil => empty
|
case Chain.nil => empty
|
||||||
case x :: Nil => x
|
case x ==: Chain.nil => x
|
||||||
case _ => Cofree(self, Eval.now(Chain.fromSeq(children)))
|
case _ => Cofree(self, Eval.now(children))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,35 +24,34 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
|||||||
}.value
|
}.value
|
||||||
}
|
}
|
||||||
|
|
||||||
private def showDiffOffset(what: (Tree, Tree), offset: Int): String = {
|
private def showDiffOffset(left: Tree, right: Tree, offset: Int): String = {
|
||||||
val spaces = "| " * offset
|
val spaces = "| " * offset
|
||||||
|
val leftShow = left.head.show
|
||||||
|
val rightShow = right.head.show
|
||||||
val head =
|
val head =
|
||||||
if (what._1.head == what._2.head) what._1.head.show
|
if (leftShow == rightShow) leftShow
|
||||||
else {
|
else {
|
||||||
val lft = what._1.head.show
|
val commonPrefix = (l: String, r: String) =>
|
||||||
val rgt = what._2.head.show
|
l.lazyZip(r).takeWhile(_ == _).map(_._1).mkString
|
||||||
val commonPrefixLen = lft.zip(rgt).takeWhile(_ == _).length
|
|
||||||
val commonSuffixLen = rgt.reverse.zip(lft.reverse).takeWhile(_ == _).length
|
val prefix = commonPrefix(leftShow, rightShow)
|
||||||
val commonPrefix = lft.take(commonPrefixLen)
|
val suffix = commonPrefix(leftShow.reverse, rightShow.reverse).reverse
|
||||||
val commonSuffix = rgt.takeRight(commonSuffixLen)
|
|
||||||
val lSuffix = lft.length - commonSuffixLen
|
val diff = (s: String) => s.drop(prefix.length).dropRight(suffix.length)
|
||||||
val lftDiff =
|
|
||||||
if (commonPrefixLen - lSuffix < lft.length) lft.substring(commonPrefixLen, lSuffix)
|
val lftDiff = diff(leftShow)
|
||||||
else ""
|
val rgtDiff = diff(rightShow)
|
||||||
val rSuffix = rgt.length - commonSuffixLen
|
|
||||||
val rgtDiff =
|
|
||||||
if (commonPrefixLen + rSuffix < rgt.length) rgt.substring(commonPrefixLen, rSuffix)
|
|
||||||
else ""
|
|
||||||
if (rgtDiff.isEmpty) {
|
if (rgtDiff.isEmpty) {
|
||||||
commonPrefix + Console.YELLOW + lftDiff + Console.RESET + commonSuffix
|
prefix + Console.YELLOW + lftDiff + Console.RESET + suffix
|
||||||
} else {
|
} else {
|
||||||
commonPrefix +
|
prefix + Console.YELLOW + lftDiff + Console.RED +
|
||||||
Console.YELLOW + lftDiff + Console.RED + " != " + Console.CYAN + rgtDiff + Console.RESET + commonSuffix
|
" != " + Console.CYAN + rgtDiff + Console.RESET + suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spaces + head + (what._1.tail, what._2.tail).mapN {
|
spaces + head + (left.tail, right.tail).mapN {
|
||||||
case (c1, c2) if c1.isEmpty && c2.isEmpty => "\n"
|
case (c1, c2) if c1.isEmpty && c2.isEmpty => "\n"
|
||||||
case (c1, c2) =>
|
case (c1, c2) =>
|
||||||
@tailrec
|
@tailrec
|
||||||
@ -62,7 +61,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
|||||||
case (Nil, y :: tail) =>
|
case (Nil, y :: tail) =>
|
||||||
nxt(tail, Nil, acc :+ (Console.CYAN + showOffset(y, offset + 1) + Console.RESET))
|
nxt(tail, Nil, acc :+ (Console.CYAN + showOffset(y, offset + 1) + Console.RESET))
|
||||||
case (x :: xt, y :: yt) if x.head == y.head =>
|
case (x :: xt, y :: yt) if x.head == y.head =>
|
||||||
nxt(xt, yt, acc :+ showDiffOffset(x -> y, offset + 1))
|
nxt(xt, yt, acc :+ showDiffOffset(x, y, offset + 1))
|
||||||
case (x :: xt, yt) if yt.exists(_.head == x.head) =>
|
case (x :: xt, yt) if yt.exists(_.head == x.head) =>
|
||||||
val yh = yt.takeWhile(_.head != x.head)
|
val yh = yt.takeWhile(_.head != x.head)
|
||||||
nxt(
|
nxt(
|
||||||
@ -82,7 +81,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
case (x :: xt, y :: yt) =>
|
case (x :: xt, y :: yt) =>
|
||||||
nxt(xt, yt, acc :+ showDiffOffset(x -> y, offset + 1))
|
nxt(xt, yt, acc :+ showDiffOffset(x, y, offset + 1))
|
||||||
case (Nil, Nil) => acc.toList
|
case (Nil, Nil) => acc.toList
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +104,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
|||||||
given Show[(Tree, Tree)] with
|
given Show[(Tree, Tree)] with
|
||||||
|
|
||||||
override def show(tt: (Tree, Tree)): String =
|
override def show(tt: (Tree, Tree)): String =
|
||||||
showDiffOffset(tt, 0)
|
showDiffOffset(tt._1, tt._2, 0)
|
||||||
|
|
||||||
extension (t: Tree)
|
extension (t: Tree)
|
||||||
|
|
||||||
|
@ -8,17 +8,35 @@ import aqua.parser.lift.LiftParser
|
|||||||
import aqua.parser.lift.LiftParser.*
|
import aqua.parser.lift.LiftParser.*
|
||||||
import cats.parse.Parser
|
import cats.parse.Parser
|
||||||
import cats.{~>, Comonad}
|
import cats.{~>, Comonad}
|
||||||
|
import cats.syntax.comonad.*
|
||||||
|
import cats.syntax.functor.*
|
||||||
import aqua.parser.lift.Span
|
import aqua.parser.lift.Span
|
||||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||||
|
|
||||||
case class ElseOtherwiseExpr[F[_]](point: Token[F]) extends Expr[F](ElseOtherwiseExpr, point) {
|
case class ElseOtherwiseExpr[F[_]](kind: ElseOtherwiseExpr.Kind, point: Token[F])
|
||||||
override def mapK[K[_]: Comonad](fk: F ~> K): ElseOtherwiseExpr[K] = copy(point.mapK(fk))
|
extends Expr[F](ElseOtherwiseExpr, point) {
|
||||||
|
|
||||||
|
override def mapK[K[_]: Comonad](fk: F ~> K): ElseOtherwiseExpr[K] =
|
||||||
|
copy(point = point.mapK(fk))
|
||||||
}
|
}
|
||||||
|
|
||||||
object ElseOtherwiseExpr extends Expr.AndIndented {
|
object ElseOtherwiseExpr extends Expr.AndIndented {
|
||||||
|
|
||||||
|
enum Kind {
|
||||||
|
case Else, Otherwise
|
||||||
|
|
||||||
|
def fold[A](ifElse: => A, ifOtherwise: => A): A = this match {
|
||||||
|
case Else => ifElse
|
||||||
|
case Otherwise => ifOtherwise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def validChildren: List[Expr.Lexem] = ForExpr.validChildren
|
override def validChildren: List[Expr.Lexem] = ForExpr.validChildren
|
||||||
|
|
||||||
override val p: Parser[ElseOtherwiseExpr[Span.S]] =
|
override val p: Parser[ElseOtherwiseExpr[Span.S]] =
|
||||||
(`else` | `otherwise`).lift.map(Token.lift[Span.S, Unit](_)).map(ElseOtherwiseExpr(_))
|
(`else`.as(Kind.Else) | `otherwise`.as(Kind.Otherwise)).lift
|
||||||
|
.fproduct(span => Token.lift(span.as(())))
|
||||||
|
.map { case (kind, point) =>
|
||||||
|
ElseOtherwiseExpr(kind.extract, point)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,24 @@ import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
|||||||
case class ForExpr[F[_]](
|
case class ForExpr[F[_]](
|
||||||
item: Name[F],
|
item: Name[F],
|
||||||
iterable: ValueToken[F],
|
iterable: ValueToken[F],
|
||||||
mode: Option[(F[ForExpr.Mode], ForExpr.Mode)]
|
mode: Option[ForExpr.Mode]
|
||||||
) extends Expr[F](ForExpr, item) {
|
) extends Expr[F](ForExpr, item) {
|
||||||
|
|
||||||
override def mapK[K[_]: Comonad](fk: F ~> K): ForExpr[K] =
|
override def mapK[K[_]: Comonad](fk: F ~> K): ForExpr[K] =
|
||||||
copy(item.mapK(fk), iterable.mapK(fk), mode.map { case (mF, m) => (fk(mF), m) })
|
copy(item.mapK(fk), iterable.mapK(fk))
|
||||||
}
|
}
|
||||||
|
|
||||||
object ForExpr extends Expr.AndIndented {
|
object ForExpr extends Expr.AndIndented {
|
||||||
sealed trait Mode
|
enum Mode { case ParMode, TryMode }
|
||||||
case object TryMode extends Mode
|
|
||||||
case object ParMode extends Mode
|
|
||||||
|
|
||||||
override def validChildren: List[Expr.Lexem] = ArrowExpr.funcChildren
|
override def validChildren: List[Expr.Lexem] = ArrowExpr.funcChildren
|
||||||
|
|
||||||
|
private lazy val modeP: P[Mode] =
|
||||||
|
(` ` *> (`par`.as(Mode.ParMode) | `try`.as(Mode.TryMode)).lift).map(_.extract)
|
||||||
|
|
||||||
override def p: P[ForExpr[Span.S]] =
|
override def p: P[ForExpr[Span.S]] =
|
||||||
((`for` *> ` ` *> Name.p <* ` <- `) ~ ValueToken.`value` ~ (` ` *> (`par`
|
((`for` *> ` ` *> Name.p <* ` <- `) ~ ValueToken.`value` ~ modeP.?).map {
|
||||||
.as(ParMode: Mode)
|
case ((item, iterable), mode) =>
|
||||||
.lift | `try`.as(TryMode: Mode).lift)).?).map { case ((item, iterable), mode) =>
|
ForExpr(item, iterable, mode)
|
||||||
ForExpr(item, iterable, mode.map(m => m -> m.extract))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,19 @@ package aqua
|
|||||||
|
|
||||||
import aqua.AquaSpec.spanToId
|
import aqua.AquaSpec.spanToId
|
||||||
import aqua.parser.expr.*
|
import aqua.parser.expr.*
|
||||||
import aqua.parser.expr.func.{AbilityIdExpr, ArrowExpr, AssignmentExpr, CallArrowExpr, ClosureExpr, ElseOtherwiseExpr, ForExpr, IfExpr, OnExpr, PushToStreamExpr, ReturnExpr}
|
import aqua.parser.expr.func.{
|
||||||
|
AbilityIdExpr,
|
||||||
|
ArrowExpr,
|
||||||
|
AssignmentExpr,
|
||||||
|
CallArrowExpr,
|
||||||
|
ClosureExpr,
|
||||||
|
ElseOtherwiseExpr,
|
||||||
|
ForExpr,
|
||||||
|
IfExpr,
|
||||||
|
OnExpr,
|
||||||
|
PushToStreamExpr,
|
||||||
|
ReturnExpr
|
||||||
|
}
|
||||||
import aqua.parser.head.FromExpr.NameOrAbAs
|
import aqua.parser.head.FromExpr.NameOrAbAs
|
||||||
import aqua.parser.head.{FromExpr, UseFromExpr}
|
import aqua.parser.head.{FromExpr, UseFromExpr}
|
||||||
import aqua.parser.lexer.*
|
import aqua.parser.lexer.*
|
||||||
@ -10,7 +22,7 @@ import aqua.parser.lexer.Token.LiftToken
|
|||||||
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||||
import aqua.types.LiteralType.{bool, number, string}
|
import aqua.types.LiteralType.{bool, number, string}
|
||||||
import aqua.types.{LiteralType, ScalarType}
|
import aqua.types.{LiteralType, ScalarType}
|
||||||
import cats.{Id, ~>}
|
import cats.{~>, Id}
|
||||||
import org.scalatest.EitherValues
|
import org.scalatest.EitherValues
|
||||||
import aqua.parser.lift.Span
|
import aqua.parser.lift.Span
|
||||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||||
@ -128,7 +140,7 @@ trait AquaSpec extends EitherValues {
|
|||||||
def parseFor(str: String): ForExpr[Id] =
|
def parseFor(str: String): ForExpr[Id] =
|
||||||
ForExpr.p.parseAll(str).value.mapK(spanToId)
|
ForExpr.p.parseAll(str).value.mapK(spanToId)
|
||||||
|
|
||||||
def parseElse(str: String): ElseOtherwiseExpr[Id] =
|
def parseElseOtherwise(str: String): ElseOtherwiseExpr[Id] =
|
||||||
ElseOtherwiseExpr.p.parseAll(str).value.mapK(spanToId)
|
ElseOtherwiseExpr.p.parseAll(str).value.mapK(spanToId)
|
||||||
|
|
||||||
def parseFieldType(str: String): FieldTypeExpr[Id] =
|
def parseFieldType(str: String): FieldTypeExpr[Id] =
|
||||||
|
@ -13,12 +13,14 @@ class ElseOtherwiseExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
|||||||
import AquaSpec._
|
import AquaSpec._
|
||||||
|
|
||||||
"else" should "be parsed" in {
|
"else" should "be parsed" in {
|
||||||
parseElse("else") should be(
|
parseElseOtherwise("else") should be(
|
||||||
ElseOtherwiseExpr[Id](Token.lift[Id, Unit](()))
|
ElseOtherwiseExpr[Id](ElseOtherwiseExpr.Kind.Else, Token.lift(()))
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
parseElse("otherwise") should be(
|
"otherwise" should "be parsed" in {
|
||||||
ElseOtherwiseExpr[Id](Token.lift[Id, Unit](()))
|
parseElseOtherwise("otherwise") should be(
|
||||||
|
ElseOtherwiseExpr[Id](ElseOtherwiseExpr.Kind.Otherwise, Token.lift(()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ class ForExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
|||||||
)
|
)
|
||||||
|
|
||||||
parseFor("for some <- false par") should be(
|
parseFor("for some <- false par") should be(
|
||||||
ForExpr[Id]("some", toBool(false), Some(ForExpr.ParMode -> ForExpr.ParMode))
|
ForExpr[Id]("some", toBool(false), Some(ForExpr.Mode.ParMode))
|
||||||
)
|
)
|
||||||
|
|
||||||
parseFor("for some <- false try") should be(
|
parseFor("for some <- false try") should be(
|
||||||
ForExpr[Id]("some", toBool(false), Some(ForExpr.TryMode -> ForExpr.TryMode))
|
ForExpr[Id]("some", toBool(false), Some(ForExpr.Mode.TryMode))
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,15 @@ import aqua.semantics.rules.definitions.{
|
|||||||
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra, LocationsState}
|
import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra, LocationsState}
|
||||||
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
|
import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState}
|
||||||
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
|
import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState}
|
||||||
import aqua.semantics.rules.{ReportError, ValuesAlgebra}
|
import aqua.semantics.rules.ValuesAlgebra
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
|
import aqua.semantics.rules.errors.ErrorsAlgebra
|
||||||
|
import aqua.raw.ops.*
|
||||||
|
|
||||||
import cats.arrow.FunctionK
|
import cats.arrow.FunctionK
|
||||||
import cats.data.*
|
import cats.data.*
|
||||||
import cats.Reducible
|
import cats.Reducible
|
||||||
|
import cats.data.Chain.*
|
||||||
import cats.data.Validated.{Invalid, Valid}
|
import cats.data.Validated.{Invalid, Valid}
|
||||||
import cats.kernel.Monoid
|
import cats.kernel.Monoid
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
@ -29,6 +34,7 @@ import cats.syntax.flatMap.*
|
|||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
import cats.syntax.foldable.*
|
import cats.syntax.foldable.*
|
||||||
import cats.syntax.reducible.*
|
import cats.syntax.reducible.*
|
||||||
|
import cats.syntax.traverse.*
|
||||||
import cats.free.CofreeInstances
|
import cats.free.CofreeInstances
|
||||||
import cats.syntax.semigroup.*
|
import cats.syntax.semigroup.*
|
||||||
import cats.{Eval, Monad, Semigroup}
|
import cats.{Eval, Monad, Semigroup}
|
||||||
@ -55,7 +61,7 @@ class RawSemantics[S[_]](implicit p: Picker[RawContext]) extends Semantics[S, Ra
|
|||||||
implicit val locationsInterpreter: DummyLocationsInterpreter[S, CompilerState[S]] =
|
implicit val locationsInterpreter: DummyLocationsInterpreter[S, CompilerState[S]] =
|
||||||
new DummyLocationsInterpreter[S, CompilerState[S]]()
|
new DummyLocationsInterpreter[S, CompilerState[S]]()
|
||||||
|
|
||||||
Semantics
|
RawSemantics
|
||||||
.interpret(ast, CompilerState.init(init), init)
|
.interpret(ast, CompilerState.init(init), init)
|
||||||
.map { case (state, ctx) =>
|
.map { case (state, ctx) =>
|
||||||
NonEmptyChain
|
NonEmptyChain
|
||||||
@ -67,34 +73,236 @@ class RawSemantics[S[_]](implicit p: Picker[RawContext]) extends Semantics[S, Ra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Semantics extends Logging {
|
object RawSemantics extends Logging {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [[RawTag.Tree]] with [[Token]] used for error reporting
|
||||||
|
*/
|
||||||
|
private final case class RawTagWithToken[S[_]](
|
||||||
|
tree: RawTag.Tree,
|
||||||
|
token: Token[S]
|
||||||
|
) {
|
||||||
|
lazy val tag: RawTag = tree.head
|
||||||
|
|
||||||
|
private def modifyTree(f: RawTag.Tree => RawTag.Tree): RawTagWithToken[S] =
|
||||||
|
copy(tree = f(tree))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap tail of @param next in [[SeqTag]]
|
||||||
|
* and append it to current tree tail
|
||||||
|
*/
|
||||||
|
def append(next: RawTagWithToken[S]): RawTagWithToken[S] = modifyTree(tree =>
|
||||||
|
tree.copy(
|
||||||
|
tail = (
|
||||||
|
tree.tail,
|
||||||
|
// SeqTag.wrap will return single node as is
|
||||||
|
next.tree.tail.map(SeqTag.wrap)
|
||||||
|
).mapN(_ :+ _)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def wrapIn(tag: GroupTag): RawTagWithToken[S] = modifyTree(tree => tag.wrap(tree))
|
||||||
|
|
||||||
|
def toRaw: RawWithToken[S] = RawWithToken(FuncOp(tree), token)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def elseWithoutIf[S[_], G[_]](
|
||||||
|
token: Token[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Unit] =
|
||||||
|
E.report(token, "Unexpected `else` without `if`" :: Nil)
|
||||||
|
|
||||||
|
private def catchWithoutTry[S[_], G[_]](
|
||||||
|
token: Token[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Unit] =
|
||||||
|
E.report(token, "Unexpected `catch` without `try`" :: Nil)
|
||||||
|
|
||||||
|
private def otherwiseWithoutPrev[S[_], G[_]](
|
||||||
|
token: Token[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Unit] =
|
||||||
|
E.report(token, "Unexpected `otherwise` without previous instruction" :: Nil)
|
||||||
|
|
||||||
|
private def parWithoutPrev[S[_], G[_]](
|
||||||
|
token: Token[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Unit] =
|
||||||
|
E.report(token, "Unexpected `par` without previous instruction" :: Nil)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally combine two [[RawTag.Tree]] into one.
|
||||||
|
* Used to combine `if` and `else`,
|
||||||
|
* `try` and `catch` (`otherwise`);
|
||||||
|
* to create [[ParTag]] from `par`,
|
||||||
|
* [[TryTag]] from `otherwise`
|
||||||
|
*
|
||||||
|
* @param prev Previous tag
|
||||||
|
* @param next Next tag
|
||||||
|
* @param E Algebra for error reporting
|
||||||
|
* @return [[Some]] with result of combination
|
||||||
|
* [[None]] if tags should not be combined
|
||||||
|
* or error occuried
|
||||||
|
*/
|
||||||
|
private def rawTagCombine[S[_], G[_]: Monad](
|
||||||
|
prev: RawTagWithToken[S],
|
||||||
|
next: RawTagWithToken[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Option[RawTagWithToken[S]]] =
|
||||||
|
(prev.tag, next.tag) match {
|
||||||
|
case (_: IfTag, IfTag.Else) =>
|
||||||
|
prev.append(next).some.pure
|
||||||
|
case (_, IfTag.Else) | (IfTag.Else, _) =>
|
||||||
|
val token = prev.tag match {
|
||||||
|
case IfTag.Else => prev.token
|
||||||
|
case _ => next.token
|
||||||
|
}
|
||||||
|
|
||||||
|
elseWithoutIf(token).as(none)
|
||||||
|
|
||||||
|
case (TryTag, TryTag.Catch) =>
|
||||||
|
prev.append(next).some.pure
|
||||||
|
case (_, TryTag.Catch) | (TryTag.Catch, _) =>
|
||||||
|
val token = prev.tag match {
|
||||||
|
case TryTag.Catch => prev.token
|
||||||
|
case _ => next.token
|
||||||
|
}
|
||||||
|
|
||||||
|
catchWithoutTry(token).as(none)
|
||||||
|
|
||||||
|
case (TryTag.Otherwise, _) =>
|
||||||
|
otherwiseWithoutPrev(prev.token).as(none)
|
||||||
|
case (TryTag, TryTag.Otherwise) =>
|
||||||
|
prev.append(next).some.pure
|
||||||
|
case (_, TryTag.Otherwise) =>
|
||||||
|
prev
|
||||||
|
.wrapIn(TryTag)
|
||||||
|
.append(next)
|
||||||
|
.some
|
||||||
|
.pure
|
||||||
|
|
||||||
|
case (ParTag.Par, _) =>
|
||||||
|
parWithoutPrev(prev.token).as(none)
|
||||||
|
case (ParTag, ParTag.Par) =>
|
||||||
|
prev.append(next).some.pure
|
||||||
|
case (_, ParTag.Par) =>
|
||||||
|
prev
|
||||||
|
.wrapIn(ParTag)
|
||||||
|
.append(next)
|
||||||
|
.some
|
||||||
|
.pure
|
||||||
|
|
||||||
|
case _ => none.pure
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if tag is valid to be single
|
||||||
|
*
|
||||||
|
* @param single tag
|
||||||
|
* @param E Algebra for error reporting
|
||||||
|
* @return [[Some]] if tag is valid to be single
|
||||||
|
* [[None]] otherwise
|
||||||
|
*/
|
||||||
|
private def rawTagSingleCheck[S[_], G[_]: Monad](
|
||||||
|
single: RawTagWithToken[S]
|
||||||
|
)(using E: ErrorsAlgebra[S, G]): G[Option[RawTagWithToken[S]]] =
|
||||||
|
single.tag match {
|
||||||
|
case IfTag.Else => elseWithoutIf(single.token).as(none)
|
||||||
|
case TryTag.Catch => catchWithoutTry(single.token).as(none)
|
||||||
|
case TryTag.Otherwise => otherwiseWithoutPrev(single.token).as(none)
|
||||||
|
case ParTag.Par => parWithoutPrev(single.token).as(none)
|
||||||
|
case _ => single.some.pure
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [[Raw]] with [[Token]] used for error reporting
|
||||||
|
*/
|
||||||
|
private final case class RawWithToken[S[_]](
|
||||||
|
raw: Raw,
|
||||||
|
token: Token[S]
|
||||||
|
) {
|
||||||
|
|
||||||
|
def toTag: Option[RawTagWithToken[S]] =
|
||||||
|
raw match {
|
||||||
|
case FuncOp(tree) => RawTagWithToken(tree, token).some
|
||||||
|
case _ => none
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for folding [[Raw]] results of children
|
||||||
|
*
|
||||||
|
* @param last Last seen [[Raw]] with [[Token]]
|
||||||
|
* @param acc All previous [[Raw]]
|
||||||
|
*/
|
||||||
|
private final case class InnersFoldState[S[_]](
|
||||||
|
last: Option[RawWithToken[S]] = None,
|
||||||
|
acc: Chain[Raw] = Chain.empty
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process new incoming [[Raw]]
|
||||||
|
*/
|
||||||
|
def step[G[_]: Monad](
|
||||||
|
next: RawWithToken[S]
|
||||||
|
)(using ErrorsAlgebra[S, G]): G[InnersFoldState[S]] =
|
||||||
|
last.fold(copy(last = next.some).pure)(prev =>
|
||||||
|
(prev.toTag, next.toTag)
|
||||||
|
.traverseN(rawTagCombine)
|
||||||
|
.map(
|
||||||
|
_.flatten.fold(
|
||||||
|
// No combination - just update last and acc
|
||||||
|
copy(
|
||||||
|
last = next.some,
|
||||||
|
acc = prev.raw +: acc
|
||||||
|
)
|
||||||
|
)(combined =>
|
||||||
|
// Result of combination is the new last
|
||||||
|
copy(
|
||||||
|
last = combined.toRaw.some
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce result of folding
|
||||||
|
*/
|
||||||
|
def result[G[_]: Monad](using
|
||||||
|
ErrorsAlgebra[S, G]
|
||||||
|
): G[Option[Raw]] =
|
||||||
|
if (acc.isEmpty)
|
||||||
|
// Hack to report error if single tag in block is incorrect
|
||||||
|
last.flatTraverse(single =>
|
||||||
|
single.toTag.fold(single.raw.some.pure)(singleTag =>
|
||||||
|
for {
|
||||||
|
checked <- rawTagSingleCheck(singleTag)
|
||||||
|
maybeRaw = checked.map(_.toRaw.raw)
|
||||||
|
} yield maybeRaw
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
last
|
||||||
|
.fold(acc)(_.raw +: acc)
|
||||||
|
.reverse
|
||||||
|
.reduceLeftOption(_ |+| _)
|
||||||
|
.pure
|
||||||
|
}
|
||||||
|
|
||||||
private def folder[S[_], G[_]: Monad](implicit
|
private def folder[S[_], G[_]: Monad](implicit
|
||||||
A: AbilitiesAlgebra[S, G],
|
A: AbilitiesAlgebra[S, G],
|
||||||
N: NamesAlgebra[S, G],
|
N: NamesAlgebra[S, G],
|
||||||
T: TypesAlgebra[S, G],
|
T: TypesAlgebra[S, G],
|
||||||
D: DefinitionsAlgebra[S, G],
|
D: DefinitionsAlgebra[S, G],
|
||||||
L: LocationsAlgebra[S, G]
|
L: LocationsAlgebra[S, G],
|
||||||
): (Expr[S], Chain[G[Raw]]) => Eval[G[Raw]] = { case (expr, inners) =>
|
E: ErrorsAlgebra[S, G]
|
||||||
|
): (Expr[S], Chain[G[RawWithToken[S]]]) => Eval[G[RawWithToken[S]]] = (expr, inners) =>
|
||||||
Eval later ExprSem
|
Eval later ExprSem
|
||||||
.getProg[S, G](expr)
|
.getProg[S, G](expr)
|
||||||
.apply(
|
.apply(for {
|
||||||
// TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones
|
children <- inners.sequence
|
||||||
// Then foldLeft just like now
|
resultState <- children
|
||||||
inners
|
.traverse(raw => StateT.modifyF((state: InnersFoldState[S]) => state.step(raw)))
|
||||||
.foldRight[G[List[Raw]]](List.empty[Raw].pure[G]) { case (a, b) =>
|
.runS(InnersFoldState())
|
||||||
(a, b).mapN {
|
result <- resultState.result
|
||||||
case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc =>
|
} yield result.getOrElse(Raw.empty("AST is empty")))
|
||||||
(prev :+: next) :: tail
|
.map(raw => RawWithToken(raw, expr.token))
|
||||||
case (prev, acc) => prev :: acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map(
|
|
||||||
_.reduceLeftOption(_ |+| _)
|
|
||||||
.getOrElse(Raw.empty("AST is empty"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Interpreter[S[_], A] = State[CompilerState[S], A]
|
type Interpreter[S[_], A] = State[CompilerState[S], A]
|
||||||
|
|
||||||
@ -103,9 +311,14 @@ object Semantics extends Logging {
|
|||||||
)(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = {
|
)(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = {
|
||||||
import monocle.syntax.all.*
|
import monocle.syntax.all.*
|
||||||
|
|
||||||
implicit val re: ReportError[S, CompilerState[S]] =
|
implicit val re: ReportErrors[S, CompilerState[S]] = new ReportErrors[S, CompilerState[S]] {
|
||||||
(st: CompilerState[S], token: Token[S], hints: List[String]) =>
|
override def apply(
|
||||||
|
st: CompilerState[S],
|
||||||
|
token: Token[S],
|
||||||
|
hints: List[String]
|
||||||
|
): CompilerState[S] =
|
||||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||||
|
}
|
||||||
|
|
||||||
implicit val ns: Lens[CompilerState[S], NamesState[S]] = GenLens[CompilerState[S]](_.names)
|
implicit val ns: Lens[CompilerState[S], NamesState[S]] = GenLens[CompilerState[S]](_.names)
|
||||||
|
|
||||||
@ -125,7 +338,11 @@ object Semantics extends Logging {
|
|||||||
new NamesInterpreter[S, CompilerState[S]]
|
new NamesInterpreter[S, CompilerState[S]]
|
||||||
implicit val definitionsInterpreter: DefinitionsInterpreter[S, CompilerState[S]] =
|
implicit val definitionsInterpreter: DefinitionsInterpreter[S, CompilerState[S]] =
|
||||||
new DefinitionsInterpreter[S, CompilerState[S]]
|
new DefinitionsInterpreter[S, CompilerState[S]]
|
||||||
ast.cata(folder[S, Interpreter[S, *]]).value
|
|
||||||
|
ast
|
||||||
|
.cata(folder[S, Interpreter[S, *]])
|
||||||
|
.value
|
||||||
|
.map(_.raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def astToState[S[_]](ast: Ast[S])(implicit
|
private def astToState[S[_]](ast: Ast[S])(implicit
|
||||||
@ -166,6 +383,7 @@ object Semantics extends Logging {
|
|||||||
) { case (ctx, p) =>
|
) { case (ctx, p) =>
|
||||||
ctx.copy(parts = ctx.parts :+ (ctx -> p))
|
ctx.copy(parts = ctx.parts :+ (ctx -> p))
|
||||||
}
|
}
|
||||||
|
|
||||||
case (state: CompilerState[S], m) =>
|
case (state: CompilerState[S], m) =>
|
||||||
logger.error("Got unexpected " + m)
|
logger.error("Got unexpected " + m)
|
||||||
state.copy(errors = state.errors :+ WrongAST(ast)) -> RawContext.blank.copy(
|
state.copy(errors = state.errors :+ WrongAST(ast)) -> RawContext.blank.copy(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package aqua.semantics.expr.func
|
package aqua.semantics.expr.func
|
||||||
|
|
||||||
import aqua.raw.ops.{AssignmentTag, FuncOp, SeqTag, XorTag}
|
import aqua.raw.ops.{AssignmentTag, FuncOp, SeqTag, TryTag}
|
||||||
import aqua.parser.expr.func.CatchExpr
|
import aqua.parser.expr.func.CatchExpr
|
||||||
import aqua.raw.value.ValueRaw
|
import aqua.raw.value.ValueRaw
|
||||||
import aqua.raw.Raw
|
import aqua.raw.Raw
|
||||||
@ -22,23 +22,26 @@ class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal {
|
|||||||
): Prog[Alg, Raw] =
|
): Prog[Alg, Raw] =
|
||||||
Prog
|
Prog
|
||||||
.around(
|
.around(
|
||||||
N.beginScope(expr.name) >> L.beginScope() >>
|
N.beginScope(expr.name) >>
|
||||||
|
L.beginScope() >>
|
||||||
N.define(expr.name, ValueRaw.LastError.baseType),
|
N.define(expr.name, ValueRaw.LastError.baseType),
|
||||||
(_: Boolean, g: Raw) =>
|
(_, g: Raw) =>
|
||||||
|
N.endScope() >> L.endScope() as (
|
||||||
g match {
|
g match {
|
||||||
case FuncOp(op) =>
|
case FuncOp(op) =>
|
||||||
N.endScope() >> L.endScope() as (XorTag
|
TryTag.Catch
|
||||||
.wrap(
|
.wrap(
|
||||||
SeqTag.wrap(
|
SeqTag.wrap(
|
||||||
AssignmentTag(ValueRaw.LastError, expr.name.value).leaf,
|
AssignmentTag(ValueRaw.LastError, expr.name.value).leaf,
|
||||||
op
|
op
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.toFuncOp: Raw)
|
.toFuncOp
|
||||||
case _ =>
|
case _ =>
|
||||||
N.endScope() >> L.endScope() as g
|
Raw.error("Wrong body of the `catch` expression")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
.abilitiesScope[S](expr.token)
|
.abilitiesScope[S](expr.token)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package aqua.semantics.expr.func
|
package aqua.semantics.expr.func
|
||||||
|
|
||||||
import aqua.raw.ops.{FuncOp, XorTag}
|
import aqua.raw.ops.{FuncOp, IfTag, TryTag}
|
||||||
import aqua.parser.expr.func.ElseOtherwiseExpr
|
import aqua.parser.expr.func.ElseOtherwiseExpr
|
||||||
import aqua.raw.Raw
|
import aqua.raw.Raw
|
||||||
import aqua.semantics.Prog
|
import aqua.semantics.Prog
|
||||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||||
import aqua.semantics.rules.names.NamesAlgebra
|
import aqua.semantics.rules.names.NamesAlgebra
|
||||||
|
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
import cats.Monad
|
import cats.Monad
|
||||||
|
|
||||||
@ -19,8 +20,18 @@ class ElseOtherwiseSem[S[_]](val expr: ElseOtherwiseExpr[S]) extends AnyVal {
|
|||||||
): Prog[Alg, Raw] =
|
): Prog[Alg, Raw] =
|
||||||
Prog
|
Prog
|
||||||
.after[Alg, Raw] {
|
.after[Alg, Raw] {
|
||||||
case FuncOp(g) => XorTag.wrap(g).toFuncOp.pure[Alg]
|
case FuncOp(op) =>
|
||||||
case g => g.pure[Alg]
|
expr.kind
|
||||||
|
.fold(
|
||||||
|
ifElse = IfTag.Else,
|
||||||
|
ifOtherwise = TryTag.Otherwise
|
||||||
|
)
|
||||||
|
.wrap(op)
|
||||||
|
.toFuncOp
|
||||||
|
.pure
|
||||||
|
case _ =>
|
||||||
|
val name = expr.kind.fold("`else`", "`otherwise`")
|
||||||
|
Raw.error(s"Wrong body of the $name expression").pure
|
||||||
}
|
}
|
||||||
.abilitiesScope(expr.token)
|
.abilitiesScope(expr.token)
|
||||||
.namesScope(expr.token)
|
.namesScope(expr.token)
|
||||||
|
@ -11,6 +11,7 @@ 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.{ArrayType, BoxType}
|
import aqua.types.{ArrayType, BoxType}
|
||||||
|
|
||||||
import cats.Monad
|
import cats.Monad
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
@ -42,27 +43,19 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal {
|
|||||||
(stOpt: Option[ValueRaw], ops: Raw) =>
|
(stOpt: Option[ValueRaw], ops: Raw) =>
|
||||||
N.streamsDefinedWithinScope()
|
N.streamsDefinedWithinScope()
|
||||||
.map(_.keySet)
|
.map(_.keySet)
|
||||||
.map((streams: Set[String]) =>
|
.map(streams =>
|
||||||
(stOpt, ops) match {
|
(stOpt, ops) match {
|
||||||
case (Some(vm), FuncOp(op)) =>
|
case (Some(vm), FuncOp(op)) =>
|
||||||
val innerTag = expr.mode.map(_._2).fold[RawTag](SeqTag) {
|
val innerTag = expr.mode.fold[RawTag](SeqTag) {
|
||||||
case ForExpr.ParMode => ParTag
|
case ForExpr.Mode.ParMode => ParTag
|
||||||
case ForExpr.TryMode => XorTag
|
case ForExpr.Mode.TryMode => TryTag
|
||||||
}
|
}
|
||||||
|
|
||||||
val mode = expr.mode.map(_._2).flatMap {
|
val mode = expr.mode.collect { case ForExpr.Mode.ParMode => WaitMode }
|
||||||
case ForExpr.ParMode => Some(WaitMode)
|
|
||||||
case ForExpr.TryMode => None
|
|
||||||
}
|
|
||||||
|
|
||||||
val forTag =
|
val forTag =
|
||||||
ForTag(expr.item.value, vm, mode).wrap(
|
ForTag(expr.item.value, vm, mode).wrap(
|
||||||
expr.mode
|
innerTag
|
||||||
.map(_._2)
|
|
||||||
.fold[RawTag](SeqTag) {
|
|
||||||
case ForExpr.ParMode => ParTag
|
|
||||||
case ForExpr.TryMode => XorTag
|
|
||||||
}
|
|
||||||
.wrap(
|
.wrap(
|
||||||
// Restrict the streams created within this scope
|
// Restrict the streams created within this scope
|
||||||
streams.toList.foldLeft(op) { case (b, streamName) =>
|
streams.toList.foldLeft(op) { case (b, streamName) =>
|
||||||
@ -76,7 +69,7 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal {
|
|||||||
if (innerTag == ParTag) ParTag.Detach.wrap(forTag).toFuncOp
|
if (innerTag == ParTag) ParTag.Detach.wrap(forTag).toFuncOp
|
||||||
else forTag.toFuncOp
|
else forTag.toFuncOp
|
||||||
case _ =>
|
case _ =>
|
||||||
Raw.error("Wrong body of the For expression")
|
Raw.error("Wrong body of the `for` expression")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package aqua.semantics.expr.func
|
package aqua.semantics.expr.func
|
||||||
|
|
||||||
import aqua.raw.ops.{FuncOp, MatchMismatchTag, XorTag}
|
import aqua.raw.ops.{FuncOp, IfTag}
|
||||||
import aqua.parser.expr.func.IfExpr
|
import aqua.parser.expr.func.IfExpr
|
||||||
import aqua.raw.value.ValueRaw
|
import aqua.raw.value.ValueRaw
|
||||||
import aqua.raw.Raw
|
import aqua.raw.Raw
|
||||||
@ -11,10 +11,12 @@ import aqua.semantics.rules.locations.LocationsAlgebra
|
|||||||
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.Type
|
import aqua.types.Type
|
||||||
|
|
||||||
import cats.Monad
|
import cats.Monad
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
import cats.syntax.flatMap.*
|
import cats.syntax.flatMap.*
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
|
import cats.syntax.apply.*
|
||||||
|
|
||||||
class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal {
|
class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal {
|
||||||
|
|
||||||
@ -27,36 +29,31 @@ class IfSem[S[_]](val expr: IfExpr[S]) extends AnyVal {
|
|||||||
): Prog[Alg, Raw] =
|
): Prog[Alg, Raw] =
|
||||||
Prog
|
Prog
|
||||||
.around(
|
.around(
|
||||||
V.valueToRaw(expr.left).flatMap {
|
(V.valueToRaw(expr.left), V.valueToRaw(expr.right)).flatMapN {
|
||||||
case Some(lt) =>
|
case (Some(lt), Some(rt)) =>
|
||||||
V.valueToRaw(expr.right).flatMap {
|
T.ensureValuesComparable(
|
||||||
case Some(rt) =>
|
token = expr.token,
|
||||||
T.ensureValuesComparable(expr.right, lt.`type`, rt.`type`)
|
left = lt.`type`,
|
||||||
.map(m => Some(lt -> rt).filter(_ => m))
|
right = rt.`type`
|
||||||
case None =>
|
).map(Option.when(_)(lt -> rt))
|
||||||
None.pure[Alg]
|
case _ => None.pure
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
V.resolveType(expr.right).as[Option[(ValueRaw, ValueRaw)]](None)
|
|
||||||
},
|
},
|
||||||
(r: Option[(ValueRaw, ValueRaw)], ops: Raw) =>
|
(values: Option[(ValueRaw, ValueRaw)], ops: Raw) =>
|
||||||
r.fold(Raw.error("If expression errored in matching types").pure[Alg]) { case (lt, rt) =>
|
values
|
||||||
|
.fold(
|
||||||
|
Raw.error("`if` expression errored in matching types")
|
||||||
|
) { case (lt, rt) =>
|
||||||
ops match {
|
ops match {
|
||||||
case FuncOp(op) =>
|
case FuncOp(op) =>
|
||||||
XorTag.LeftBiased
|
IfTag(
|
||||||
.wrap(
|
left = lt,
|
||||||
MatchMismatchTag(
|
right = rt,
|
||||||
lt,
|
equal = expr.eqOp.value
|
||||||
rt,
|
).wrap(op).toFuncOp
|
||||||
expr.eqOp.value
|
case _ => Raw.error("Wrong body of the `if` expression")
|
||||||
).wrap(op)
|
|
||||||
)
|
|
||||||
.toFuncOp
|
|
||||||
.pure[Alg]
|
|
||||||
|
|
||||||
case _ => Raw.error("Wrong body of the if expression").pure[Alg]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.pure
|
||||||
)
|
)
|
||||||
.abilitiesScope[S](expr.token)
|
.abilitiesScope[S](expr.token)
|
||||||
.namesScope[S](expr.token)
|
.namesScope[S](expr.token)
|
||||||
|
@ -12,7 +12,7 @@ class ParSem[S[_]](val expr: ParExpr[S]) extends AnyVal {
|
|||||||
def program[Alg[_]: Monad]: Prog[Alg, Raw] =
|
def program[Alg[_]: Monad]: Prog[Alg, Raw] =
|
||||||
Prog.after[Alg, Raw] {
|
Prog.after[Alg, Raw] {
|
||||||
case FuncOp(g) =>
|
case FuncOp(g) =>
|
||||||
ParTag.wrap(g).toFuncOp.pure[Alg]
|
ParTag.Par.wrap(g).toFuncOp.pure[Alg]
|
||||||
case g => g.pure[Alg]
|
case g => g.pure[Alg]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package aqua.semantics.expr.func
|
package aqua.semantics.expr.func
|
||||||
|
|
||||||
import aqua.raw.ops.{FuncOp, XorTag}
|
import aqua.raw.ops.{FuncOp, TryTag}
|
||||||
import aqua.parser.expr.func.TryExpr
|
import aqua.parser.expr.func.TryExpr
|
||||||
import aqua.raw.Raw
|
import aqua.raw.Raw
|
||||||
import aqua.semantics.Prog
|
import aqua.semantics.Prog
|
||||||
@ -22,9 +22,9 @@ class TrySem[S[_]](val expr: TryExpr[S]) extends AnyVal {
|
|||||||
Prog
|
Prog
|
||||||
.after[Alg, Raw] {
|
.after[Alg, Raw] {
|
||||||
case FuncOp(o) =>
|
case FuncOp(o) =>
|
||||||
XorTag.LeftBiased.wrap(o).toFuncOp.pure[Alg]
|
TryTag.wrap(o).toFuncOp.pure[Alg]
|
||||||
case _ =>
|
case _ =>
|
||||||
Raw.error("Wrong body of the try expression").pure[Alg]
|
Raw.error("Wrong body of the `try` expression").pure[Alg]
|
||||||
}
|
}
|
||||||
.abilitiesScope(expr.token)
|
.abilitiesScope(expr.token)
|
||||||
.namesScope(expr.token)
|
.namesScope(expr.token)
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package aqua.semantics.rules
|
|
||||||
|
|
||||||
import aqua.parser.lexer.Token
|
|
||||||
|
|
||||||
trait ReportError[S[_], X] {
|
|
||||||
def apply(st: X, token: Token[S], hints: List[String]): X
|
|
||||||
}
|
|
@ -4,10 +4,11 @@ import aqua.parser.lexer.Token
|
|||||||
import cats.data.State
|
import cats.data.State
|
||||||
import monocle.Lens
|
import monocle.Lens
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
|
|
||||||
case class StackInterpreter[S[_], X, St, Fr](stackLens: Lens[St, List[Fr]])(implicit
|
case class StackInterpreter[S[_], X, St, Fr](stackLens: Lens[St, List[Fr]])(implicit
|
||||||
lens: Lens[X, St],
|
lens: Lens[X, St],
|
||||||
error: ReportError[S, X]
|
error: ReportErrors[S, X]
|
||||||
) {
|
) {
|
||||||
type SX[A] = State[X, A]
|
type SX[A] = State[X, A]
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import aqua.raw.value.ValueRaw
|
|||||||
import aqua.semantics.Levenshtein
|
import aqua.semantics.Levenshtein
|
||||||
import aqua.semantics.rules.definitions.DefinitionsAlgebra
|
import aqua.semantics.rules.definitions.DefinitionsAlgebra
|
||||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter, abilities}
|
import aqua.semantics.rules.{abilities, StackInterpreter}
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.types.ArrowType
|
import aqua.types.ArrowType
|
||||||
import cats.data.{NonEmptyList, NonEmptyMap, State}
|
import cats.data.{NonEmptyList, NonEmptyMap, State}
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
@ -17,7 +18,7 @@ import monocle.macros.GenLens
|
|||||||
|
|
||||||
class AbilitiesInterpreter[S[_], X](implicit
|
class AbilitiesInterpreter[S[_], X](implicit
|
||||||
lens: Lens[X, AbilitiesState[S]],
|
lens: Lens[X, AbilitiesState[S]],
|
||||||
error: ReportError[S, X],
|
error: ReportErrors[S, X],
|
||||||
locations: LocationsAlgebra[S, State[X, *]]
|
locations: LocationsAlgebra[S, State[X, *]]
|
||||||
) extends AbilitiesAlgebra[S, State[X, *]] {
|
) extends AbilitiesAlgebra[S, State[X, *]] {
|
||||||
|
|
||||||
@ -46,11 +47,14 @@ class AbilitiesInterpreter[S[_], X](implicit
|
|||||||
s.copy(
|
s.copy(
|
||||||
services = s.services
|
services = s.services
|
||||||
.updated(name.value, ServiceRaw(name.value, arrows.map(_._2), defaultId)),
|
.updated(name.value, ServiceRaw(name.value, arrows.map(_._2), defaultId)),
|
||||||
definitions =
|
definitions = s.definitions.updated(name.value, name)
|
||||||
s.definitions.updated(name.value, name)
|
|
||||||
)
|
)
|
||||||
).flatMap { _ =>
|
).flatMap { _ =>
|
||||||
locations.addTokenWithFields(name.value, name, arrows.toNel.toList.map(t => t._1 -> t._2._1))
|
locations.addTokenWithFields(
|
||||||
|
name.value,
|
||||||
|
name,
|
||||||
|
arrows.toNel.toList.map(t => t._1 -> t._2._1)
|
||||||
|
)
|
||||||
}.as(true)
|
}.as(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package aqua.semantics.rules.definitions
|
package aqua.semantics.rules.definitions
|
||||||
|
|
||||||
import aqua.parser.lexer.{Name, NamedTypeToken, Token}
|
import aqua.parser.lexer.{Name, NamedTypeToken, Token}
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
import aqua.semantics.rules.StackInterpreter
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.semantics.rules.abilities.AbilitiesState
|
import aqua.semantics.rules.abilities.AbilitiesState
|
||||||
import aqua.semantics.rules.locations.{LocationsAlgebra, LocationsState}
|
import aqua.semantics.rules.locations.{LocationsAlgebra, LocationsState}
|
||||||
import aqua.semantics.rules.types.TypesState
|
import aqua.semantics.rules.types.TypesState
|
||||||
@ -18,7 +19,7 @@ import scala.collection.immutable.SortedMap
|
|||||||
|
|
||||||
class DefinitionsInterpreter[S[_], X](implicit
|
class DefinitionsInterpreter[S[_], X](implicit
|
||||||
lens: Lens[X, DefinitionsState[S]],
|
lens: Lens[X, DefinitionsState[S]],
|
||||||
error: ReportError[S, X],
|
error: ReportErrors[S, X],
|
||||||
locations: LocationsAlgebra[S, State[X, *]]
|
locations: LocationsAlgebra[S, State[X, *]]
|
||||||
) extends DefinitionsAlgebra[S, State[X, *]] {
|
) extends DefinitionsAlgebra[S, State[X, *]] {
|
||||||
type SX[A] = State[X, A]
|
type SX[A] = State[X, A]
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package aqua.semantics.rules.errors
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
|
|
||||||
|
trait ErrorsAlgebra[S[_], Alg[_]] {
|
||||||
|
def report(token: Token[S], hints: List[String]): Alg[Unit]
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package aqua.semantics.rules.errors
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
|
|
||||||
|
import cats.data.State
|
||||||
|
|
||||||
|
trait ReportErrors[S[_], X] extends ErrorsAlgebra[S, State[X, *]] {
|
||||||
|
def apply(st: X, token: Token[S], hints: List[String]): X
|
||||||
|
|
||||||
|
def report(token: Token[S], hints: List[String]): State[X, Unit] =
|
||||||
|
State.modify(apply(_, token, hints))
|
||||||
|
}
|
@ -3,7 +3,8 @@ package aqua.semantics.rules.names
|
|||||||
import aqua.parser.lexer.{Name, Token}
|
import aqua.parser.lexer.{Name, Token}
|
||||||
import aqua.semantics.Levenshtein
|
import aqua.semantics.Levenshtein
|
||||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
import aqua.semantics.rules.StackInterpreter
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.types.{ArrowType, StreamType, Type}
|
import aqua.types.{ArrowType, StreamType, Type}
|
||||||
import cats.data.{OptionT, State}
|
import cats.data.{OptionT, State}
|
||||||
import cats.syntax.flatMap.*
|
import cats.syntax.flatMap.*
|
||||||
@ -14,7 +15,7 @@ import monocle.macros.GenLens
|
|||||||
|
|
||||||
class NamesInterpreter[S[_], X](implicit
|
class NamesInterpreter[S[_], X](implicit
|
||||||
lens: Lens[X, NamesState[S]],
|
lens: Lens[X, NamesState[S]],
|
||||||
error: ReportError[S, X],
|
error: ReportErrors[S, X],
|
||||||
locations: LocationsAlgebra[S, State[X, *]]
|
locations: LocationsAlgebra[S, State[X, *]]
|
||||||
) extends NamesAlgebra[S, State[X, *]] {
|
) extends NamesAlgebra[S, State[X, *]] {
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ package aqua.semantics.rules.types
|
|||||||
import aqua.parser.lexer.*
|
import aqua.parser.lexer.*
|
||||||
import aqua.raw.value.{FunctorRaw, IntoCopyRaw, IntoFieldRaw, IntoIndexRaw, PropertyRaw, ValueRaw}
|
import aqua.raw.value.{FunctorRaw, IntoCopyRaw, IntoFieldRaw, IntoIndexRaw, PropertyRaw, ValueRaw}
|
||||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
import aqua.semantics.rules.StackInterpreter
|
||||||
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.types.{
|
import aqua.types.{
|
||||||
ArrayType,
|
ArrayType,
|
||||||
ArrowType,
|
ArrowType,
|
||||||
@ -32,7 +33,7 @@ import scala.collection.immutable.SortedMap
|
|||||||
|
|
||||||
class TypesInterpreter[S[_], X](implicit
|
class TypesInterpreter[S[_], X](implicit
|
||||||
lens: Lens[X, TypesState[S]],
|
lens: Lens[X, TypesState[S]],
|
||||||
error: ReportError[S, X],
|
error: ReportErrors[S, X],
|
||||||
locations: LocationsAlgebra[S, State[X, *]]
|
locations: LocationsAlgebra[S, State[X, *]]
|
||||||
) extends TypesAlgebra[S, State[X, *]] {
|
) extends TypesAlgebra[S, State[X, *]] {
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import aqua.raw.arrow.ArrowRaw
|
|||||||
import aqua.raw.Raw
|
import aqua.raw.Raw
|
||||||
import aqua.raw.ops.{EmptyTag, FuncOp, RawTag}
|
import aqua.raw.ops.{EmptyTag, FuncOp, RawTag}
|
||||||
import aqua.semantics.expr.func.ClosureSem
|
import aqua.semantics.expr.func.ClosureSem
|
||||||
import aqua.semantics.rules.ReportError
|
|
||||||
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
||||||
import aqua.types.{ArrowType, ProductType}
|
import aqua.types.{ArrowType, ProductType}
|
||||||
import cats.data.*
|
import cats.data.*
|
||||||
|
@ -6,22 +6,68 @@ import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, OnTag, ParTag, RawTag, SeqGr
|
|||||||
import aqua.parser.Parser
|
import aqua.parser.Parser
|
||||||
import aqua.parser.lift.{LiftParser, Span}
|
import aqua.parser.lift.{LiftParser, Span}
|
||||||
import aqua.raw.value.{LiteralRaw, ValueRaw}
|
import aqua.raw.value.{LiteralRaw, ValueRaw}
|
||||||
import aqua.types.{ArrowType, ConsType, LiteralType, NilType, ScalarType}
|
import aqua.types.*
|
||||||
|
import aqua.raw.ops.*
|
||||||
|
|
||||||
import org.scalatest.flatspec.AnyFlatSpec
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import org.scalatest.Inside
|
||||||
import cats.~>
|
import cats.~>
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
|
import cats.data.NonEmptyChain
|
||||||
import cats.syntax.show.*
|
import cats.syntax.show.*
|
||||||
|
import cats.data.Validated
|
||||||
|
|
||||||
class SemanticsSpec extends AnyFlatSpec with Matchers {
|
class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||||
|
|
||||||
val emptyCall = Call(Nil, Nil)
|
val emptyCall = Call(Nil, Nil)
|
||||||
|
|
||||||
// use it to fix https://github.com/fluencelabs/aqua/issues/90
|
|
||||||
"sem" should "create right model" in {
|
|
||||||
implicit val fileLift: LiftParser[Span.S] = Span.spanLiftParser
|
implicit val fileLift: LiftParser[Span.S] = Span.spanLiftParser
|
||||||
val parser = Parser.parse(Parser.spanParser)
|
val parser = Parser.parse(Parser.spanParser)
|
||||||
|
|
||||||
|
val semantics = new RawSemantics[Span.S]()
|
||||||
|
|
||||||
|
def insideBody(script: String)(test: RawTag.Tree => Any): Unit =
|
||||||
|
inside(parser(script)) { case Validated.Valid(ast) =>
|
||||||
|
val init = RawContext.blank
|
||||||
|
inside(semantics.process(ast, init)) { case Validated.Valid(ctx) =>
|
||||||
|
inside(ctx.funcs.headOption) { case Some((_, func)) =>
|
||||||
|
test(func.arrow.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def insideSemErrors(script: String)(test: NonEmptyChain[SemanticError[Span.S]] => Any): Unit =
|
||||||
|
inside(parser(script)) { case Validated.Valid(ast) =>
|
||||||
|
val init = RawContext.blank
|
||||||
|
inside(semantics.process(ast, init)) { case Validated.Invalid(errors) =>
|
||||||
|
test(errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val testServiceDef = """
|
||||||
|
|service Test("test"):
|
||||||
|
| testCall()
|
||||||
|
| testCallStr(s: string) -> string
|
||||||
|
|
|
||||||
|
""".stripMargin
|
||||||
|
|
||||||
|
def testServiceCallStr(str: String) =
|
||||||
|
CallArrowRawTag
|
||||||
|
.service(
|
||||||
|
serviceId = LiteralRaw.quote("test"),
|
||||||
|
fnName = "testCallStr",
|
||||||
|
call = Call(LiteralRaw.quote(str) :: Nil, Nil),
|
||||||
|
name = "Test",
|
||||||
|
arrowType = ArrowType(
|
||||||
|
ProductType.labelled(("s" -> ScalarType.string) :: Nil),
|
||||||
|
ProductType(ScalarType.string :: Nil)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.leaf
|
||||||
|
|
||||||
|
// use it to fix https://github.com/fluencelabs/aqua/issues/90
|
||||||
|
"semantics" should "create right model" in {
|
||||||
val script =
|
val script =
|
||||||
"""service A("srv1"):
|
"""service A("srv1"):
|
||||||
| fn1: -> string
|
| fn1: -> string
|
||||||
@ -31,24 +77,12 @@ class SemanticsSpec extends AnyFlatSpec with Matchers {
|
|||||||
| A.fn1()
|
| A.fn1()
|
||||||
| par A.fn1()""".stripMargin
|
| par A.fn1()""".stripMargin
|
||||||
|
|
||||||
val ast = parser(script).toList.head
|
insideBody(script) { body =>
|
||||||
|
|
||||||
val ctx = RawContext.blank
|
|
||||||
|
|
||||||
val semantics = new RawSemantics[Span.S]()
|
|
||||||
|
|
||||||
val p = semantics.process(ast, ctx)
|
|
||||||
|
|
||||||
val func = p.toList.head.funcs("parFunc")
|
|
||||||
|
|
||||||
val proc = func.arrow.body
|
|
||||||
|
|
||||||
val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType))
|
val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType))
|
||||||
val serviceCall =
|
val serviceCall =
|
||||||
CallArrowRawTag.service(LiteralRaw.quote("srv1"), "fn1", emptyCall, "A", arrowType).leaf
|
CallArrowRawTag.service(LiteralRaw.quote("srv1"), "fn1", emptyCall, "A", arrowType).leaf
|
||||||
|
|
||||||
val expected =
|
val expected =
|
||||||
SeqGroupTag.wrap(
|
|
||||||
ParTag.wrap(
|
ParTag.wrap(
|
||||||
OnTag(
|
OnTag(
|
||||||
LiteralRaw("\"other-peer\"", LiteralType.string),
|
LiteralRaw("\"other-peer\"", LiteralType.string),
|
||||||
@ -58,9 +92,358 @@ class SemanticsSpec extends AnyFlatSpec with Matchers {
|
|||||||
),
|
),
|
||||||
serviceCall
|
serviceCall
|
||||||
)
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle if with else" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| if 1 == 2:
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
| else:
|
||||||
|
| Test.testCallStr("else")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
IfTag(LiteralRaw.number(1), LiteralRaw.number(2), true).wrap(
|
||||||
|
testServiceCallStr("if"),
|
||||||
|
testServiceCallStr("else")
|
||||||
)
|
)
|
||||||
|
|
||||||
proc.equalsOrShowDiff(expected) should be(true)
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle try with catch" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
| catch e:
|
||||||
|
| Test.testCallStr("catch")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
TryTag.wrap(
|
||||||
|
testServiceCallStr("try"),
|
||||||
|
SeqTag.wrap(
|
||||||
|
AssignmentTag(ValueRaw.LastError, "e").leaf,
|
||||||
|
testServiceCallStr("catch")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle try with more than one catch" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
| catch e:
|
||||||
|
| Test.testCallStr("catch1")
|
||||||
|
| catch e:
|
||||||
|
| Test.testCallStr("catch2")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
TryTag.wrap(
|
||||||
|
testServiceCallStr("try"),
|
||||||
|
SeqTag.wrap(
|
||||||
|
AssignmentTag(ValueRaw.LastError, "e").leaf,
|
||||||
|
testServiceCallStr("catch1")
|
||||||
|
),
|
||||||
|
SeqTag.wrap(
|
||||||
|
AssignmentTag(ValueRaw.LastError, "e").leaf,
|
||||||
|
testServiceCallStr("catch2")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle try with otherwise" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
TryTag.wrap(
|
||||||
|
testServiceCallStr("try"),
|
||||||
|
testServiceCallStr("otherwise")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle if without else" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| if 1 != 2:
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
IfTag(LiteralRaw.number(1), LiteralRaw.number(2), false).wrap(
|
||||||
|
testServiceCallStr("if")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle try without catch" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected =
|
||||||
|
TryTag.wrap(
|
||||||
|
testServiceCallStr("try")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle par" in {
|
||||||
|
val tests = List("two", "three", "four", "five")
|
||||||
|
|
||||||
|
(1 to tests.length)
|
||||||
|
.map(tests.take(_))
|
||||||
|
.foreach(test =>
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| Test.testCallStr("one")
|
||||||
|
|""".stripMargin +
|
||||||
|
test.map(n => s" par Test.testCallStr(\"$n\")\n").mkString
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected = ParTag.wrap(
|
||||||
|
testServiceCallStr("one") +: test.map(n => testServiceCallStr(n))
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle otherwise" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| Test.testCallStr("fail")
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected = TryTag.wrap(
|
||||||
|
testServiceCallStr("fail"),
|
||||||
|
testServiceCallStr("otherwise")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle if with otherwise" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| if "a" != "b":
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected = TryTag.wrap(
|
||||||
|
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||||
|
testServiceCallStr("if")
|
||||||
|
),
|
||||||
|
testServiceCallStr("otherwise")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle if and try with par" in {
|
||||||
|
val tests = List("two", "three", "four", "five")
|
||||||
|
val ifTry = List(
|
||||||
|
"""
|
||||||
|
| if "a" != "b":
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
|""".stripMargin ->
|
||||||
|
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||||
|
testServiceCallStr("if")
|
||||||
|
),
|
||||||
|
"""
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
|""".stripMargin ->
|
||||||
|
TryTag.wrap(
|
||||||
|
testServiceCallStr("try")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(1 to tests.length)
|
||||||
|
.map(tests.take(_))
|
||||||
|
.flatMap(test => ifTry.map(test -> _))
|
||||||
|
.foreach { case (test, (ifOrTry, tag)) =>
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
""".stripMargin +
|
||||||
|
ifOrTry +
|
||||||
|
test.map(n => s" par Test.testCallStr(\"$n\")\n").mkString
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected = ParTag.wrap(
|
||||||
|
tag +: test.map(n => testServiceCallStr(n))
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "forbid else without if" in {
|
||||||
|
val scriptTry = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| try:
|
||||||
|
| Test.testCallStr("try")
|
||||||
|
| else:
|
||||||
|
| Test.testCallStr("else")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
val scriptSingle = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| else:
|
||||||
|
| Test.testCallStr("else")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideSemErrors(scriptTry) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
|
||||||
|
insideSemErrors(scriptSingle) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "forbid catch without try" in {
|
||||||
|
val scriptIf = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| if 1 != 2:
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
| catch e:
|
||||||
|
| Test.testCallStr("catch")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
val scriptSingle = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| catch e:
|
||||||
|
| Test.testCallStr("catch")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideSemErrors(scriptIf) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
|
||||||
|
insideSemErrors(scriptSingle) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "forbid otherwise without previous instruction" in {
|
||||||
|
val scriptOtherwise = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideSemErrors(scriptOtherwise) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "forbid par without previous instruction" in {
|
||||||
|
val scriptOtherwise = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| par Test.testCallStr("par")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideSemErrors(scriptOtherwise) { errors =>
|
||||||
|
atLeast(1, errors.toChain.toList) shouldBe a[RulesViolated[Span.S]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "handle complex cases" in {
|
||||||
|
val script = testServiceDef +
|
||||||
|
"""
|
||||||
|
|func test():
|
||||||
|
| if "a" != "b":
|
||||||
|
| Test.testCallStr("if")
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise1")
|
||||||
|
| par Test.testCallStr("par1")
|
||||||
|
| otherwise:
|
||||||
|
| Test.testCallStr("otherwise2")
|
||||||
|
| par Test.testCallStr("par2")
|
||||||
|
| par Test.testCallStr("par3")
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
insideBody(script) { body =>
|
||||||
|
val expected = ParTag.wrap(
|
||||||
|
TryTag.wrap(
|
||||||
|
ParTag.wrap(
|
||||||
|
TryTag.wrap(
|
||||||
|
IfTag(LiteralRaw.quote("a"), LiteralRaw.quote("b"), false).wrap(
|
||||||
|
testServiceCallStr("if")
|
||||||
|
),
|
||||||
|
testServiceCallStr("otherwise1")
|
||||||
|
),
|
||||||
|
testServiceCallStr("par1")
|
||||||
|
),
|
||||||
|
testServiceCallStr("otherwise2")
|
||||||
|
),
|
||||||
|
testServiceCallStr("par2"),
|
||||||
|
testServiceCallStr("par3")
|
||||||
|
)
|
||||||
|
|
||||||
|
body.equalsOrShowDiff(expected) should be(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,23 +5,29 @@ import aqua.parser.lexer.{Name, Token}
|
|||||||
import aqua.parser.lift.Span
|
import aqua.parser.lift.Span
|
||||||
import aqua.raw.{Raw, RawContext}
|
import aqua.raw.{Raw, RawContext}
|
||||||
import aqua.semantics.expr.func.ClosureSem
|
import aqua.semantics.expr.func.ClosureSem
|
||||||
import aqua.semantics.rules.ReportError
|
import aqua.semantics.rules.errors.ReportErrors
|
||||||
import aqua.semantics.rules.abilities.{AbilitiesInterpreter, AbilitiesState}
|
import aqua.semantics.rules.abilities.{AbilitiesInterpreter, AbilitiesState}
|
||||||
import aqua.semantics.rules.locations.DummyLocationsInterpreter
|
import aqua.semantics.rules.locations.DummyLocationsInterpreter
|
||||||
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
||||||
import aqua.semantics.rules.types.{TypesInterpreter, TypesState}
|
import aqua.semantics.rules.types.{TypesInterpreter, TypesState}
|
||||||
import aqua.types.*
|
import aqua.types.*
|
||||||
import cats.data.State
|
import cats.data.State
|
||||||
import cats.{Id, ~>}
|
import cats.{~>, Id}
|
||||||
import monocle.Lens
|
import monocle.Lens
|
||||||
import monocle.macros.GenLens
|
import monocle.macros.GenLens
|
||||||
import monocle.syntax.all.*
|
import monocle.syntax.all.*
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
|
|
||||||
implicit val re: ReportError[Id, CompilerState[Id]] =
|
implicit val re: ReportErrors[Id, CompilerState[Id]] = new ReportErrors[Id, CompilerState[Id]] {
|
||||||
(st: CompilerState[Id], token: Token[Id], hints: List[String]) =>
|
|
||||||
|
override def apply(
|
||||||
|
st: CompilerState[Id],
|
||||||
|
token: Token[Id],
|
||||||
|
hints: List[String]
|
||||||
|
): CompilerState[Id] =
|
||||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||||
|
}
|
||||||
|
|
||||||
implicit val locationsInterpreter: DummyLocationsInterpreter[Id, CompilerState[Id]] =
|
implicit val locationsInterpreter: DummyLocationsInterpreter[Id, CompilerState[Id]] =
|
||||||
new DummyLocationsInterpreter[Id, CompilerState[Id]]()
|
new DummyLocationsInterpreter[Id, CompilerState[Id]]()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user