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
|
||||
|
||||
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 cats.data.State
|
||||
import monocle.Lens
|
||||
@ -10,7 +11,7 @@ import scribe.Logging
|
||||
|
||||
class LocationsInterpreter[S[_], X](implicit
|
||||
lens: Lens[X, LocationsState[S]],
|
||||
error: ReportError[S, X]
|
||||
error: ReportErrors[S, X]
|
||||
) extends LocationsAlgebra[S, State[X, *]] with Logging {
|
||||
|
||||
type SX[A] = State[X, A]
|
||||
@ -69,7 +70,7 @@ class LocationsInterpreter[S[_], X](implicit
|
||||
case frame if frame.tokens.contains(name) => frame.tokens(name)
|
||||
} orElse st.tokens.get(name)).map(token -> _)
|
||||
}
|
||||
|
||||
|
||||
st.copy(locations = st.locations ++ newLocs)
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package aqua.lsp
|
||||
import aqua.parser.Ast
|
||||
import aqua.parser.head.{ImportExpr, ImportFromExpr, UseExpr, UseFromExpr}
|
||||
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.{CompilerState, RulesViolated, SemanticError, Semantics}
|
||||
import aqua.semantics.{CompilerState, RawSemantics, RulesViolated, SemanticError, Semantics}
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.apply.*
|
||||
@ -57,14 +57,14 @@ class LspSemantics[S[_]] extends Semantics[S, LspContext[S]] {
|
||||
GenLens[CompilerState[S]](_.locations)
|
||||
|
||||
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.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||
|
||||
implicit val locationsInterpreter: LocationsInterpreter[S, CompilerState[S]] =
|
||||
new LocationsInterpreter[S, CompilerState[S]]()
|
||||
|
||||
Semantics
|
||||
RawSemantics
|
||||
.interpret(ast, initState, init.raw)
|
||||
.map { case (state, ctx) =>
|
||||
NonEmptyChain
|
||||
|
@ -10,12 +10,15 @@ import aqua.raw.value.*
|
||||
import aqua.types.{ArrayType, ArrowType, BoxType, CanonStreamType, StreamType}
|
||||
import cats.syntax.traverse.*
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.apply.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.option.*
|
||||
import cats.instances.list.*
|
||||
import cats.data.{Chain, State, StateT}
|
||||
import cats.syntax.show.*
|
||||
import scribe.{log, Logging}
|
||||
import aqua.model.inline.Inline.parDesugarPrefixOpt
|
||||
|
||||
/**
|
||||
* [[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 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]) =
|
||||
l match {
|
||||
@ -120,7 +176,7 @@ object TagInliner extends Logging {
|
||||
def tagToModel[S: Mangler: Arrows: Exports](
|
||||
tag: RawTag,
|
||||
treeFunctionName: String
|
||||
): State[S, (Option[OpModel], Option[OpModel.Tree])] =
|
||||
): State[S, TagInlined] =
|
||||
tag match {
|
||||
case OnTag(peerId, via) =>
|
||||
for {
|
||||
@ -133,21 +189,52 @@ object TagInliner extends Logging {
|
||||
viaD = Chain.fromSeq(viaDeFlattened.map(_._1))
|
||||
viaF = viaDeFlattened.flatMap(_._2)
|
||||
|
||||
} yield Some(OnModel(pid, viaD)) -> parDesugarPrefix(viaF.prependedAll(pif))
|
||||
|
||||
case MatchMismatchTag(left, right, shouldMatch) =>
|
||||
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
|
||||
} yield TagInlined.Single(
|
||||
model = OnModel(pid, viaD),
|
||||
prefix = parDesugarPrefix(viaF.prependedAll(pif))
|
||||
)
|
||||
|
||||
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) =>
|
||||
for {
|
||||
vp <- valueToModel(iterable)
|
||||
@ -164,18 +251,21 @@ object TagInliner extends Logging {
|
||||
iterable.`type`
|
||||
}
|
||||
_ <- Exports[S].resolved(item, VarModel(n, elementType))
|
||||
} yield {
|
||||
val m = mode.map {
|
||||
m = mode.map {
|
||||
case ForTag.WaitMode => ForModel.NeverMode
|
||||
case ForTag.PassMode => ForModel.NullMode
|
||||
}
|
||||
|
||||
Some(ForModel(n, v, m)) -> p
|
||||
}
|
||||
} yield TagInlined.Single(
|
||||
model = ForModel(n, v, m),
|
||||
prefix = p
|
||||
)
|
||||
|
||||
case PushToStreamTag(operand, exportTo) =>
|
||||
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) =>
|
||||
@ -184,9 +274,14 @@ object TagInliner extends Logging {
|
||||
case (l @ LiteralModel(_, _), p) =>
|
||||
for {
|
||||
_ <- Exports[S].resolved(exportTo.name, l)
|
||||
} yield None -> p
|
||||
} yield TagInlined.Empty(prefix = 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) =>
|
||||
@ -195,9 +290,14 @@ object TagInliner extends Logging {
|
||||
case (l @ LiteralModel(_, _), p) =>
|
||||
for {
|
||||
_ <- Exports[S].resolved(assignTo, l)
|
||||
} yield None -> p
|
||||
} yield TagInlined.Empty(prefix = p)
|
||||
case (v, p) =>
|
||||
State.pure(Some(FlattenModel(v, assignTo)) -> p)
|
||||
TagInlined
|
||||
.Single(
|
||||
model = FlattenModel(v, assignTo),
|
||||
prefix = p
|
||||
)
|
||||
.pure
|
||||
}
|
||||
|
||||
case JoinTag(operands) =>
|
||||
@ -205,13 +305,19 @@ object TagInliner extends Logging {
|
||||
.traverse(o => valueToModel(o))
|
||||
.map(nel => {
|
||||
logger.trace("join after " + nel.map(_._1))
|
||||
// None because join behaviour will be processed in ApplyPropertiesRawInliner
|
||||
None -> parDesugarPrefix(nel.toList.flatMap(_._2))
|
||||
// Empty because join behaviour will be processed in ApplyPropertiesRawInliner
|
||||
TagInlined.Empty(prefix = parDesugarPrefix(nel.toList.flatMap(_._2)))
|
||||
})
|
||||
|
||||
case CallArrowRawTag(exportTo, value: CallArrowRaw) =>
|
||||
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) =>
|
||||
@ -226,43 +332,41 @@ object TagInliner extends Logging {
|
||||
// NOTE: Name <assignTo> should not exist yet
|
||||
_ <- Mangler[S].forbidName(assignTo)
|
||||
_ <- Exports[S].resolved(assignTo, model)
|
||||
} yield None -> prefix
|
||||
} yield TagInlined.Empty(prefix = prefix)
|
||||
}
|
||||
|
||||
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
|
||||
for {
|
||||
t <- Mangler[S].findAndForbidName(arrow.name)
|
||||
_ <- Arrows[S].resolved(arrow, Some(t))
|
||||
} yield Some(CaptureTopologyModel(t)) -> None
|
||||
} yield TagInlined.Single(model = CaptureTopologyModel(t))
|
||||
|
||||
case NextTag(item) =>
|
||||
for {
|
||||
exps <- Exports[S].exports
|
||||
} yield {
|
||||
exps.get(item).collect { case VarModel(n, _, _) =>
|
||||
model = exps.get(item).collect { case VarModel(n, _, _) =>
|
||||
NextModel(n)
|
||||
} -> None
|
||||
}
|
||||
}
|
||||
} yield model.fold(TagInlined.Empty())(m => TagInlined.Single(model = m))
|
||||
|
||||
case RestrictionTag(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) =>
|
||||
value match
|
||||
case VarRaw(name, _) =>
|
||||
for {
|
||||
cd <- valueToModel(value)
|
||||
_ <- Exports[S].resolved(name, cd._1)
|
||||
} yield None -> cd._2
|
||||
} yield TagInlined.Empty(prefix = cd._2)
|
||||
case _ => none
|
||||
|
||||
case _: SeqGroupTag => pure(SeqModel)
|
||||
case ParTag.Detach => pure(DetachModel)
|
||||
case _: ParGroupTag => pure(ParModel)
|
||||
|
||||
case _: NoExecTag => none
|
||||
case _ =>
|
||||
logger.warn(s"Tag $tag must have been eliminated at this point")
|
||||
@ -271,16 +375,13 @@ object TagInliner extends Logging {
|
||||
|
||||
private def traverseS[S](
|
||||
cf: RawTag.Tree,
|
||||
f: RawTag => State[S, (Option[OpModel], Option[OpModel.Tree])]
|
||||
f: RawTag => State[S, TagInlined]
|
||||
): State[S, OpModel.Tree] =
|
||||
for {
|
||||
headTree <- f(cf.head)
|
||||
headInlined <- f(cf.head)
|
||||
tail <- StateT.liftF(cf.tail)
|
||||
tailTree <- tail.traverse(traverseS[S](_, f))
|
||||
} yield headTree match {
|
||||
case (Some(m), prefix) => SeqModel.wrap(prefix.toList :+ m.wrap(tailTree.toList: _*): _*)
|
||||
case (None, prefix) => SeqModel.wrap(prefix.toList ++ tailTree.toList: _*)
|
||||
}
|
||||
children <- tail.traverse(traverseS[S](_, f))
|
||||
} yield headInlined.build(children)
|
||||
|
||||
def handleTree[S: Exports: Mangler: Arrows](
|
||||
tree: RawTag.Tree,
|
||||
|
@ -8,13 +8,15 @@ import aqua.types.{ScalarType, StreamType}
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
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 {
|
||||
val canonTo = "canon_to"
|
||||
|
||||
val model = TagInliner
|
||||
val (state, inlined) = TagInliner
|
||||
.tagToModel[InliningState](
|
||||
CanonicalizeTag(ValueRaw.Nil, Call.Export(canonTo, StreamType(ScalarType.string))),
|
||||
""
|
||||
@ -22,15 +24,20 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers {
|
||||
.run(InliningState())
|
||||
.value
|
||||
|
||||
model._1.resolvedExports(canonTo) shouldBe LiteralModel(ValueRaw.Nil.value, ValueRaw.Nil.baseType)
|
||||
model._2._1 shouldBe None
|
||||
model._2._2 shouldBe None
|
||||
state.resolvedExports(canonTo) shouldBe LiteralModel(
|
||||
ValueRaw.Nil.value,
|
||||
ValueRaw.Nil.baseType
|
||||
)
|
||||
|
||||
inside(inlined) { case TagInlined.Empty(prefix) =>
|
||||
prefix shouldBe None
|
||||
}
|
||||
}
|
||||
|
||||
"FlattenTag" should "pass literals as is" in {
|
||||
val canonTo = "canon_to"
|
||||
|
||||
val model = TagInliner
|
||||
val (state, inlined) = TagInliner
|
||||
.tagToModel[InliningState](
|
||||
FlattenTag(ValueRaw.Nil, canonTo),
|
||||
""
|
||||
@ -38,11 +45,13 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers {
|
||||
.run(InliningState())
|
||||
.value
|
||||
|
||||
model._1.resolvedExports(canonTo) shouldBe LiteralModel(
|
||||
state.resolvedExports(canonTo) shouldBe LiteralModel(
|
||||
ValueRaw.Nil.value,
|
||||
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.StateT
|
||||
|
||||
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))
|
||||
}
|
||||
case class FuncOp(tree: RawTag.Tree) extends Raw
|
||||
|
@ -60,19 +60,51 @@ sealed trait ParGroupTag extends GroupTag
|
||||
|
||||
case object SeqTag extends SeqGroupTag {
|
||||
|
||||
override def wrap(children: Tree*): Tree =
|
||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyTag).toList, RawTag.empty)
|
||||
override def wrap(children: Chain[Tree]): Tree =
|
||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyTag), RawTag.empty)
|
||||
}
|
||||
|
||||
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 object LeftBiased extends GroupTag
|
||||
case class IfTag(left: ValueRaw, right: ValueRaw, equal: Boolean) 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 {
|
||||
|
||||
@ -97,13 +129,6 @@ case class RestrictionTag(name: String, isStream: Boolean) extends SeqGroupTag {
|
||||
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)
|
||||
extends SeqGroupTag {
|
||||
|
||||
|
@ -9,92 +9,34 @@ import cats.syntax.semigroup.*
|
||||
|
||||
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
|
||||
|
||||
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 flatY = SeqGroupTag.ungroupSingle(y)
|
||||
(flatX.head, flatY.head) match {
|
||||
case (_, XorParTag(xor, par)) => combine(combine(flatX, xor), par)
|
||||
case (XorParTag(xor, par), _) => combine(combine(xor, par), flatY)
|
||||
case (SeqTag, SeqTag) => flatY.copy(tail = (flatX.tail, flatY.tail).mapN(_ ++ _))
|
||||
case (SeqTag, SeqTag) => flatX.copy(tail = (flatX.tail, flatY.tail).mapN(_ ++ _))
|
||||
case (_, SeqTag) => flatY.copy(tail = flatY.tail.map(_.prepend(flatX)))
|
||||
case (SeqTag, _) => flatX.copy(tail = flatX.tail.map(_.append(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)
|
||||
|
||||
def toFuncOp: FuncOp = FuncOp(tree)
|
||||
|
||||
def rename(vals: Map[String, String]): RawTag.Tree =
|
||||
if (vals.isEmpty) tree
|
||||
else
|
||||
tree.map[RawTag](_.mapValues(_.renameVars(vals)).renameExports(vals))
|
||||
else tree.map(_.mapValues(_.renameVars(vals)).renameExports(vals))
|
||||
|
||||
def renameExports(vals: Map[String, String]): RawTag.Tree =
|
||||
if (vals.isEmpty) tree
|
||||
else
|
||||
tree.map[RawTag](_.renameExports(vals))
|
||||
else tree.map(_.renameExports(vals))
|
||||
|
||||
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)(_ ++ _))
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ object FuncArrow {
|
||||
): FuncArrow =
|
||||
FuncArrow(
|
||||
raw.name,
|
||||
RawTag.fixCornerCases(raw.arrow.body),
|
||||
raw.arrow.body,
|
||||
raw.arrow.`type`,
|
||||
raw.arrow.ret,
|
||||
arrows,
|
||||
|
@ -71,13 +71,8 @@ sealed trait ParGroupModel extends GroupOpModel
|
||||
|
||||
case object SeqModel extends SeqGroupModel {
|
||||
|
||||
override def wrap(children: Tree*): Tree =
|
||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyModel).toList, EmptyModel.leaf)
|
||||
|
||||
// EmptyModel allowed – useful for tests
|
||||
def wrapWithEmpty(children: Tree*): Tree =
|
||||
super.wrapNonEmpty(children.toList, EmptyModel.leaf)
|
||||
|
||||
override def wrap(children: Chain[Tree]): Tree =
|
||||
super.wrapNonEmpty(children.filterNot(_.head == EmptyModel), EmptyModel.leaf)
|
||||
}
|
||||
|
||||
case object ParModel extends ParGroupModel
|
||||
|
@ -1,6 +1,7 @@
|
||||
package aqua.tree
|
||||
|
||||
import cats.data.Chain
|
||||
import cats.data.Chain.*
|
||||
import cats.free.Cofree
|
||||
import cats.Eval
|
||||
|
||||
@ -16,10 +17,10 @@ trait TreeNode[T <: TreeNode[T]] {
|
||||
|
||||
def wrap(children: Chain[Tree]): Tree = Cofree(self, Eval.now(children))
|
||||
|
||||
protected def wrapNonEmpty(children: List[Tree], empty: Tree): Tree = children match {
|
||||
case Nil => empty
|
||||
case x :: Nil => x
|
||||
case _ => Cofree(self, Eval.now(Chain.fromSeq(children)))
|
||||
protected def wrapNonEmpty(children: Chain[Tree], empty: Tree): Tree = children match {
|
||||
case Chain.nil => empty
|
||||
case x ==: Chain.nil => x
|
||||
case _ => Cofree(self, Eval.now(children))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,35 +24,34 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
||||
}.value
|
||||
}
|
||||
|
||||
private def showDiffOffset(what: (Tree, Tree), offset: Int): String = {
|
||||
private def showDiffOffset(left: Tree, right: Tree, offset: Int): String = {
|
||||
val spaces = "| " * offset
|
||||
val leftShow = left.head.show
|
||||
val rightShow = right.head.show
|
||||
val head =
|
||||
if (what._1.head == what._2.head) what._1.head.show
|
||||
if (leftShow == rightShow) leftShow
|
||||
else {
|
||||
val lft = what._1.head.show
|
||||
val rgt = what._2.head.show
|
||||
val commonPrefixLen = lft.zip(rgt).takeWhile(_ == _).length
|
||||
val commonSuffixLen = rgt.reverse.zip(lft.reverse).takeWhile(_ == _).length
|
||||
val commonPrefix = lft.take(commonPrefixLen)
|
||||
val commonSuffix = rgt.takeRight(commonSuffixLen)
|
||||
val lSuffix = lft.length - commonSuffixLen
|
||||
val lftDiff =
|
||||
if (commonPrefixLen - lSuffix < lft.length) lft.substring(commonPrefixLen, lSuffix)
|
||||
else ""
|
||||
val rSuffix = rgt.length - commonSuffixLen
|
||||
val rgtDiff =
|
||||
if (commonPrefixLen + rSuffix < rgt.length) rgt.substring(commonPrefixLen, rSuffix)
|
||||
else ""
|
||||
val commonPrefix = (l: String, r: String) =>
|
||||
l.lazyZip(r).takeWhile(_ == _).map(_._1).mkString
|
||||
|
||||
val prefix = commonPrefix(leftShow, rightShow)
|
||||
val suffix = commonPrefix(leftShow.reverse, rightShow.reverse).reverse
|
||||
|
||||
val diff = (s: String) => s.drop(prefix.length).dropRight(suffix.length)
|
||||
|
||||
val lftDiff = diff(leftShow)
|
||||
val rgtDiff = diff(rightShow)
|
||||
|
||||
if (rgtDiff.isEmpty) {
|
||||
commonPrefix + Console.YELLOW + lftDiff + Console.RESET + commonSuffix
|
||||
prefix + Console.YELLOW + lftDiff + Console.RESET + suffix
|
||||
} else {
|
||||
commonPrefix +
|
||||
Console.YELLOW + lftDiff + Console.RED + " != " + Console.CYAN + rgtDiff + Console.RESET + commonSuffix
|
||||
prefix + Console.YELLOW + lftDiff + Console.RED +
|
||||
" != " + 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) =>
|
||||
@tailrec
|
||||
@ -62,7 +61,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
||||
case (Nil, y :: tail) =>
|
||||
nxt(tail, Nil, acc :+ (Console.CYAN + showOffset(y, offset + 1) + Console.RESET))
|
||||
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) =>
|
||||
val yh = yt.takeWhile(_.head != x.head)
|
||||
nxt(
|
||||
@ -82,7 +81,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
||||
)
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
@ -105,7 +104,7 @@ trait TreeNodeCompanion[T <: TreeNode[T]] {
|
||||
given Show[(Tree, Tree)] with
|
||||
|
||||
override def show(tt: (Tree, Tree)): String =
|
||||
showDiffOffset(tt, 0)
|
||||
showDiffOffset(tt._1, tt._2, 0)
|
||||
|
||||
extension (t: Tree)
|
||||
|
||||
|
@ -8,17 +8,35 @@ import aqua.parser.lift.LiftParser
|
||||
import aqua.parser.lift.LiftParser.*
|
||||
import cats.parse.Parser
|
||||
import cats.{~>, Comonad}
|
||||
import cats.syntax.comonad.*
|
||||
import cats.syntax.functor.*
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
|
||||
case class ElseOtherwiseExpr[F[_]](point: Token[F]) extends Expr[F](ElseOtherwiseExpr, point) {
|
||||
override def mapK[K[_]: Comonad](fk: F ~> K): ElseOtherwiseExpr[K] = copy(point.mapK(fk))
|
||||
case class ElseOtherwiseExpr[F[_]](kind: ElseOtherwiseExpr.Kind, point: Token[F])
|
||||
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 {
|
||||
|
||||
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 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[_]](
|
||||
item: Name[F],
|
||||
iterable: ValueToken[F],
|
||||
mode: Option[(F[ForExpr.Mode], ForExpr.Mode)]
|
||||
mode: Option[ForExpr.Mode]
|
||||
) extends Expr[F](ForExpr, item) {
|
||||
|
||||
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 {
|
||||
sealed trait Mode
|
||||
case object TryMode extends Mode
|
||||
case object ParMode extends Mode
|
||||
enum Mode { case ParMode, TryMode }
|
||||
|
||||
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]] =
|
||||
((`for` *> ` ` *> Name.p <* ` <- `) ~ ValueToken.`value` ~ (` ` *> (`par`
|
||||
.as(ParMode: Mode)
|
||||
.lift | `try`.as(TryMode: Mode).lift)).?).map { case ((item, iterable), mode) =>
|
||||
ForExpr(item, iterable, mode.map(m => m -> m.extract))
|
||||
((`for` *> ` ` *> Name.p <* ` <- `) ~ ValueToken.`value` ~ modeP.?).map {
|
||||
case ((item, iterable), mode) =>
|
||||
ForExpr(item, iterable, mode)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,19 @@ package aqua
|
||||
|
||||
import aqua.AquaSpec.spanToId
|
||||
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, UseFromExpr}
|
||||
import aqua.parser.lexer.*
|
||||
@ -10,7 +22,7 @@ import aqua.parser.lexer.Token.LiftToken
|
||||
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||
import aqua.types.LiteralType.{bool, number, string}
|
||||
import aqua.types.{LiteralType, ScalarType}
|
||||
import cats.{Id, ~>}
|
||||
import cats.{~>, Id}
|
||||
import org.scalatest.EitherValues
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
@ -128,7 +140,7 @@ trait AquaSpec extends EitherValues {
|
||||
def parseFor(str: String): ForExpr[Id] =
|
||||
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)
|
||||
|
||||
def parseFieldType(str: String): FieldTypeExpr[Id] =
|
||||
|
@ -13,12 +13,14 @@ class ElseOtherwiseExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
import AquaSpec._
|
||||
|
||||
"else" should "be parsed" in {
|
||||
parseElse("else") should be(
|
||||
ElseOtherwiseExpr[Id](Token.lift[Id, Unit](()))
|
||||
parseElseOtherwise("else") should be(
|
||||
ElseOtherwiseExpr[Id](ElseOtherwiseExpr.Kind.Else, Token.lift(()))
|
||||
)
|
||||
}
|
||||
|
||||
parseElse("otherwise") should be(
|
||||
ElseOtherwiseExpr[Id](Token.lift[Id, Unit](()))
|
||||
"otherwise" should "be parsed" in {
|
||||
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(
|
||||
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(
|
||||
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.names.{NamesAlgebra, NamesInterpreter, NamesState}
|
||||
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.data.*
|
||||
import cats.Reducible
|
||||
import cats.data.Chain.*
|
||||
import cats.data.Validated.{Invalid, Valid}
|
||||
import cats.kernel.Monoid
|
||||
import cats.syntax.applicative.*
|
||||
@ -29,6 +34,7 @@ import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.foldable.*
|
||||
import cats.syntax.reducible.*
|
||||
import cats.syntax.traverse.*
|
||||
import cats.free.CofreeInstances
|
||||
import cats.syntax.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]] =
|
||||
new DummyLocationsInterpreter[S, CompilerState[S]]()
|
||||
|
||||
Semantics
|
||||
RawSemantics
|
||||
.interpret(ast, CompilerState.init(init), init)
|
||||
.map { case (state, ctx) =>
|
||||
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
|
||||
A: AbilitiesAlgebra[S, G],
|
||||
N: NamesAlgebra[S, G],
|
||||
T: TypesAlgebra[S, G],
|
||||
D: DefinitionsAlgebra[S, G],
|
||||
L: LocationsAlgebra[S, G]
|
||||
): (Expr[S], Chain[G[Raw]]) => Eval[G[Raw]] = { case (expr, inners) =>
|
||||
L: LocationsAlgebra[S, G],
|
||||
E: ErrorsAlgebra[S, G]
|
||||
): (Expr[S], Chain[G[RawWithToken[S]]]) => Eval[G[RawWithToken[S]]] = (expr, inners) =>
|
||||
Eval later ExprSem
|
||||
.getProg[S, G](expr)
|
||||
.apply(
|
||||
// TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones
|
||||
// Then foldLeft just like now
|
||||
inners
|
||||
.foldRight[G[List[Raw]]](List.empty[Raw].pure[G]) { case (a, b) =>
|
||||
(a, b).mapN {
|
||||
case (prev: FuncOp, (next: FuncOp) :: tail) if next.isRightAssoc =>
|
||||
(prev :+: next) :: tail
|
||||
case (prev, acc) => prev :: acc
|
||||
}
|
||||
}
|
||||
.map(
|
||||
_.reduceLeftOption(_ |+| _)
|
||||
.getOrElse(Raw.empty("AST is empty"))
|
||||
)
|
||||
)
|
||||
}
|
||||
.apply(for {
|
||||
children <- inners.sequence
|
||||
resultState <- children
|
||||
.traverse(raw => StateT.modifyF((state: InnersFoldState[S]) => state.step(raw)))
|
||||
.runS(InnersFoldState())
|
||||
result <- resultState.result
|
||||
} yield result.getOrElse(Raw.empty("AST is empty")))
|
||||
.map(raw => RawWithToken(raw, expr.token))
|
||||
|
||||
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] = {
|
||||
import monocle.syntax.all.*
|
||||
|
||||
implicit val re: ReportError[S, CompilerState[S]] =
|
||||
(st: CompilerState[S], token: Token[S], hints: List[String]) =>
|
||||
implicit val re: ReportErrors[S, CompilerState[S]] = new ReportErrors[S, CompilerState[S]] {
|
||||
override def apply(
|
||||
st: CompilerState[S],
|
||||
token: Token[S],
|
||||
hints: List[String]
|
||||
): CompilerState[S] =
|
||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||
}
|
||||
|
||||
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]]
|
||||
implicit val definitionsInterpreter: 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
|
||||
@ -166,6 +383,7 @@ object Semantics extends Logging {
|
||||
) { case (ctx, p) =>
|
||||
ctx.copy(parts = ctx.parts :+ (ctx -> p))
|
||||
}
|
||||
|
||||
case (state: CompilerState[S], m) =>
|
||||
logger.error("Got unexpected " + m)
|
||||
state.copy(errors = state.errors :+ WrongAST(ast)) -> RawContext.blank.copy(
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.raw.value.ValueRaw
|
||||
import aqua.raw.Raw
|
||||
@ -22,22 +22,25 @@ class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal {
|
||||
): Prog[Alg, Raw] =
|
||||
Prog
|
||||
.around(
|
||||
N.beginScope(expr.name) >> L.beginScope() >>
|
||||
N.beginScope(expr.name) >>
|
||||
L.beginScope() >>
|
||||
N.define(expr.name, ValueRaw.LastError.baseType),
|
||||
(_: Boolean, g: Raw) =>
|
||||
g match {
|
||||
case FuncOp(op) =>
|
||||
N.endScope() >> L.endScope() as (XorTag
|
||||
.wrap(
|
||||
SeqTag.wrap(
|
||||
AssignmentTag(ValueRaw.LastError, expr.name.value).leaf,
|
||||
op
|
||||
(_, g: Raw) =>
|
||||
N.endScope() >> L.endScope() as (
|
||||
g match {
|
||||
case FuncOp(op) =>
|
||||
TryTag.Catch
|
||||
.wrap(
|
||||
SeqTag.wrap(
|
||||
AssignmentTag(ValueRaw.LastError, expr.name.value).leaf,
|
||||
op
|
||||
)
|
||||
)
|
||||
)
|
||||
.toFuncOp: Raw)
|
||||
case _ =>
|
||||
N.endScope() >> L.endScope() as g
|
||||
}
|
||||
.toFuncOp
|
||||
case _ =>
|
||||
Raw.error("Wrong body of the `catch` expression")
|
||||
}
|
||||
)
|
||||
)
|
||||
.abilitiesScope[S](expr.token)
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
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.raw.Raw
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
|
||||
import cats.syntax.applicative.*
|
||||
import cats.Monad
|
||||
|
||||
@ -19,8 +20,18 @@ class ElseOtherwiseSem[S[_]](val expr: ElseOtherwiseExpr[S]) extends AnyVal {
|
||||
): Prog[Alg, Raw] =
|
||||
Prog
|
||||
.after[Alg, Raw] {
|
||||
case FuncOp(g) => XorTag.wrap(g).toFuncOp.pure[Alg]
|
||||
case g => g.pure[Alg]
|
||||
case FuncOp(op) =>
|
||||
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)
|
||||
.namesScope(expr.token)
|
||||
|
@ -11,6 +11,7 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.{ArrayType, BoxType}
|
||||
|
||||
import cats.Monad
|
||||
import cats.data.Chain
|
||||
import cats.syntax.applicative.*
|
||||
@ -42,27 +43,19 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal {
|
||||
(stOpt: Option[ValueRaw], ops: Raw) =>
|
||||
N.streamsDefinedWithinScope()
|
||||
.map(_.keySet)
|
||||
.map((streams: Set[String]) =>
|
||||
.map(streams =>
|
||||
(stOpt, ops) match {
|
||||
case (Some(vm), FuncOp(op)) =>
|
||||
val innerTag = expr.mode.map(_._2).fold[RawTag](SeqTag) {
|
||||
case ForExpr.ParMode => ParTag
|
||||
case ForExpr.TryMode => XorTag
|
||||
}
|
||||
|
||||
val mode = expr.mode.map(_._2).flatMap {
|
||||
case ForExpr.ParMode => Some(WaitMode)
|
||||
case ForExpr.TryMode => None
|
||||
val innerTag = expr.mode.fold[RawTag](SeqTag) {
|
||||
case ForExpr.Mode.ParMode => ParTag
|
||||
case ForExpr.Mode.TryMode => TryTag
|
||||
}
|
||||
|
||||
val mode = expr.mode.collect { case ForExpr.Mode.ParMode => WaitMode }
|
||||
|
||||
val forTag =
|
||||
ForTag(expr.item.value, vm, mode).wrap(
|
||||
expr.mode
|
||||
.map(_._2)
|
||||
.fold[RawTag](SeqTag) {
|
||||
case ForExpr.ParMode => ParTag
|
||||
case ForExpr.TryMode => XorTag
|
||||
}
|
||||
innerTag
|
||||
.wrap(
|
||||
// Restrict the streams created within this scope
|
||||
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
|
||||
else forTag.toFuncOp
|
||||
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
|
||||
|
||||
import aqua.raw.ops.{FuncOp, MatchMismatchTag, XorTag}
|
||||
import aqua.raw.ops.{FuncOp, IfTag}
|
||||
import aqua.parser.expr.func.IfExpr
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.raw.Raw
|
||||
@ -11,10 +11,12 @@ import aqua.semantics.rules.locations.LocationsAlgebra
|
||||
import aqua.semantics.rules.names.NamesAlgebra
|
||||
import aqua.semantics.rules.types.TypesAlgebra
|
||||
import aqua.types.Type
|
||||
|
||||
import cats.Monad
|
||||
import cats.syntax.applicative.*
|
||||
import cats.syntax.flatMap.*
|
||||
import cats.syntax.functor.*
|
||||
import cats.syntax.apply.*
|
||||
|
||||
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
|
||||
.around(
|
||||
V.valueToRaw(expr.left).flatMap {
|
||||
case Some(lt) =>
|
||||
V.valueToRaw(expr.right).flatMap {
|
||||
case Some(rt) =>
|
||||
T.ensureValuesComparable(expr.right, lt.`type`, rt.`type`)
|
||||
.map(m => Some(lt -> rt).filter(_ => m))
|
||||
case None =>
|
||||
None.pure[Alg]
|
||||
}
|
||||
case None =>
|
||||
V.resolveType(expr.right).as[Option[(ValueRaw, ValueRaw)]](None)
|
||||
(V.valueToRaw(expr.left), V.valueToRaw(expr.right)).flatMapN {
|
||||
case (Some(lt), Some(rt)) =>
|
||||
T.ensureValuesComparable(
|
||||
token = expr.token,
|
||||
left = lt.`type`,
|
||||
right = rt.`type`
|
||||
).map(Option.when(_)(lt -> rt))
|
||||
case _ => None.pure
|
||||
},
|
||||
(r: Option[(ValueRaw, ValueRaw)], ops: Raw) =>
|
||||
r.fold(Raw.error("If expression errored in matching types").pure[Alg]) { case (lt, rt) =>
|
||||
ops match {
|
||||
case FuncOp(op) =>
|
||||
XorTag.LeftBiased
|
||||
.wrap(
|
||||
MatchMismatchTag(
|
||||
lt,
|
||||
rt,
|
||||
expr.eqOp.value
|
||||
).wrap(op)
|
||||
)
|
||||
.toFuncOp
|
||||
.pure[Alg]
|
||||
|
||||
case _ => Raw.error("Wrong body of the if expression").pure[Alg]
|
||||
(values: Option[(ValueRaw, ValueRaw)], ops: Raw) =>
|
||||
values
|
||||
.fold(
|
||||
Raw.error("`if` expression errored in matching types")
|
||||
) { case (lt, rt) =>
|
||||
ops match {
|
||||
case FuncOp(op) =>
|
||||
IfTag(
|
||||
left = lt,
|
||||
right = rt,
|
||||
equal = expr.eqOp.value
|
||||
).wrap(op).toFuncOp
|
||||
case _ => Raw.error("Wrong body of the `if` expression")
|
||||
}
|
||||
}
|
||||
}
|
||||
.pure
|
||||
)
|
||||
.abilitiesScope[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] =
|
||||
Prog.after[Alg, Raw] {
|
||||
case FuncOp(g) =>
|
||||
ParTag.wrap(g).toFuncOp.pure[Alg]
|
||||
ParTag.Par.wrap(g).toFuncOp.pure[Alg]
|
||||
case g => g.pure[Alg]
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.raw.Raw
|
||||
import aqua.semantics.Prog
|
||||
@ -22,9 +22,9 @@ class TrySem[S[_]](val expr: TryExpr[S]) extends AnyVal {
|
||||
Prog
|
||||
.after[Alg, Raw] {
|
||||
case FuncOp(o) =>
|
||||
XorTag.LeftBiased.wrap(o).toFuncOp.pure[Alg]
|
||||
TryTag.wrap(o).toFuncOp.pure[Alg]
|
||||
case _ =>
|
||||
Raw.error("Wrong body of the try expression").pure[Alg]
|
||||
Raw.error("Wrong body of the `try` expression").pure[Alg]
|
||||
}
|
||||
.abilitiesScope(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 monocle.Lens
|
||||
import cats.syntax.functor.*
|
||||
import aqua.semantics.rules.errors.ReportErrors
|
||||
|
||||
case class StackInterpreter[S[_], X, St, Fr](stackLens: Lens[St, List[Fr]])(implicit
|
||||
lens: Lens[X, St],
|
||||
error: ReportError[S, X]
|
||||
error: ReportErrors[S, X]
|
||||
) {
|
||||
type SX[A] = State[X, A]
|
||||
|
||||
|
@ -7,7 +7,8 @@ import aqua.raw.value.ValueRaw
|
||||
import aqua.semantics.Levenshtein
|
||||
import aqua.semantics.rules.definitions.DefinitionsAlgebra
|
||||
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 cats.data.{NonEmptyList, NonEmptyMap, State}
|
||||
import cats.syntax.functor.*
|
||||
@ -17,7 +18,7 @@ import monocle.macros.GenLens
|
||||
|
||||
class AbilitiesInterpreter[S[_], X](implicit
|
||||
lens: Lens[X, AbilitiesState[S]],
|
||||
error: ReportError[S, X],
|
||||
error: ReportErrors[S, X],
|
||||
locations: LocationsAlgebra[S, State[X, *]]
|
||||
) extends AbilitiesAlgebra[S, State[X, *]] {
|
||||
|
||||
@ -46,11 +47,14 @@ class AbilitiesInterpreter[S[_], X](implicit
|
||||
s.copy(
|
||||
services = s.services
|
||||
.updated(name.value, ServiceRaw(name.value, arrows.map(_._2), defaultId)),
|
||||
definitions =
|
||||
s.definitions.updated(name.value, name)
|
||||
definitions = s.definitions.updated(name.value, name)
|
||||
)
|
||||
).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)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
package aqua.semantics.rules.definitions
|
||||
|
||||
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.locations.{LocationsAlgebra, LocationsState}
|
||||
import aqua.semantics.rules.types.TypesState
|
||||
@ -18,7 +19,7 @@ import scala.collection.immutable.SortedMap
|
||||
|
||||
class DefinitionsInterpreter[S[_], X](implicit
|
||||
lens: Lens[X, DefinitionsState[S]],
|
||||
error: ReportError[S, X],
|
||||
error: ReportErrors[S, X],
|
||||
locations: LocationsAlgebra[S, State[X, *]]
|
||||
) extends DefinitionsAlgebra[S, State[X, *]] {
|
||||
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.semantics.Levenshtein
|
||||
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 cats.data.{OptionT, State}
|
||||
import cats.syntax.flatMap.*
|
||||
@ -14,7 +15,7 @@ import monocle.macros.GenLens
|
||||
|
||||
class NamesInterpreter[S[_], X](implicit
|
||||
lens: Lens[X, NamesState[S]],
|
||||
error: ReportError[S, X],
|
||||
error: ReportErrors[S, X],
|
||||
locations: LocationsAlgebra[S, State[X, *]]
|
||||
) extends NamesAlgebra[S, State[X, *]] {
|
||||
|
||||
|
@ -3,7 +3,8 @@ package aqua.semantics.rules.types
|
||||
import aqua.parser.lexer.*
|
||||
import aqua.raw.value.{FunctorRaw, IntoCopyRaw, IntoFieldRaw, IntoIndexRaw, PropertyRaw, ValueRaw}
|
||||
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.{
|
||||
ArrayType,
|
||||
ArrowType,
|
||||
@ -32,7 +33,7 @@ import scala.collection.immutable.SortedMap
|
||||
|
||||
class TypesInterpreter[S[_], X](implicit
|
||||
lens: Lens[X, TypesState[S]],
|
||||
error: ReportError[S, X],
|
||||
error: ReportErrors[S, X],
|
||||
locations: LocationsAlgebra[S, State[X, *]]
|
||||
) extends TypesAlgebra[S, State[X, *]] {
|
||||
|
||||
@ -66,8 +67,8 @@ class TypesInterpreter[S[_], X](implicit
|
||||
case Valid(t) =>
|
||||
val (tt, tokens) = t
|
||||
val tokensLocs = tokens.map { case (t, n) =>
|
||||
n.value -> t
|
||||
}
|
||||
n.value -> t
|
||||
}
|
||||
locations.pointLocations(tokensLocs).map(_ => Some(tt))
|
||||
case Invalid(errs) =>
|
||||
errs
|
||||
@ -117,7 +118,7 @@ class TypesInterpreter[S[_], X](implicit
|
||||
s"Field `${op.value}` not found in type `$name`, available: ${fields.toNel.toList.map(_._1).mkString(", ")}"
|
||||
).as(None)
|
||||
) { t =>
|
||||
locations.pointFieldLocation(name, op.value, op).as(Some(IntoFieldRaw(op.value, t)))
|
||||
locations.pointFieldLocation(name, op.value, op).as(Some(IntoFieldRaw(op.value, t)))
|
||||
}
|
||||
case t =>
|
||||
t.properties
|
||||
|
@ -6,7 +6,6 @@ import aqua.raw.arrow.ArrowRaw
|
||||
import aqua.raw.Raw
|
||||
import aqua.raw.ops.{EmptyTag, FuncOp, RawTag}
|
||||
import aqua.semantics.expr.func.ClosureSem
|
||||
import aqua.semantics.rules.ReportError
|
||||
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
||||
import aqua.types.{ArrowType, ProductType}
|
||||
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.lift.{LiftParser, Span}
|
||||
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.matchers.should.Matchers
|
||||
import org.scalatest.Inside
|
||||
import cats.~>
|
||||
import cats.data.Chain
|
||||
import cats.data.NonEmptyChain
|
||||
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)
|
||||
|
||||
// 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
|
||||
val parser = Parser.parse(Parser.spanParser)
|
||||
implicit val fileLift: LiftParser[Span.S] = Span.spanLiftParser
|
||||
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 =
|
||||
"""service A("srv1"):
|
||||
| fn1: -> string
|
||||
@ -31,24 +77,12 @@ class SemanticsSpec extends AnyFlatSpec with Matchers {
|
||||
| A.fn1()
|
||||
| par A.fn1()""".stripMargin
|
||||
|
||||
val ast = parser(script).toList.head
|
||||
insideBody(script) { body =>
|
||||
val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType))
|
||||
val serviceCall =
|
||||
CallArrowRawTag.service(LiteralRaw.quote("srv1"), "fn1", emptyCall, "A", arrowType).leaf
|
||||
|
||||
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 serviceCall =
|
||||
CallArrowRawTag.service(LiteralRaw.quote("srv1"), "fn1", emptyCall, "A", arrowType).leaf
|
||||
|
||||
val expected =
|
||||
SeqGroupTag.wrap(
|
||||
val expected =
|
||||
ParTag.wrap(
|
||||
OnTag(
|
||||
LiteralRaw("\"other-peer\"", LiteralType.string),
|
||||
@ -58,9 +92,358 @@ class SemanticsSpec extends AnyFlatSpec with Matchers {
|
||||
),
|
||||
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")
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
proc.equalsOrShowDiff(expected) should be(true)
|
||||
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.raw.{Raw, RawContext}
|
||||
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.locations.DummyLocationsInterpreter
|
||||
import aqua.semantics.rules.names.{NamesInterpreter, NamesState}
|
||||
import aqua.semantics.rules.types.{TypesInterpreter, TypesState}
|
||||
import aqua.types.*
|
||||
import cats.data.State
|
||||
import cats.{Id, ~>}
|
||||
import cats.{~>, Id}
|
||||
import monocle.Lens
|
||||
import monocle.macros.GenLens
|
||||
import monocle.syntax.all.*
|
||||
|
||||
object Utils {
|
||||
|
||||
implicit val re: ReportError[Id, CompilerState[Id]] =
|
||||
(st: CompilerState[Id], token: Token[Id], hints: List[String]) =>
|
||||
implicit val re: ReportErrors[Id, CompilerState[Id]] = new ReportErrors[Id, CompilerState[Id]] {
|
||||
|
||||
override def apply(
|
||||
st: CompilerState[Id],
|
||||
token: Token[Id],
|
||||
hints: List[String]
|
||||
): CompilerState[Id] =
|
||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hints)))
|
||||
}
|
||||
|
||||
implicit val locationsInterpreter: DummyLocationsInterpreter[Id, CompilerState[Id]] =
|
||||
new DummyLocationsInterpreter[Id, CompilerState[Id]]()
|
||||
|
Loading…
x
Reference in New Issue
Block a user