feat(compiler): Make on propagate errors [fixes LNG-203] (#788)

* Add fail model

* Make `on` propagate error

* Fix unit tests

* Fix TryTag inlining

* Update XorModel.wrap

* Add comments

* Remove wrapWithXor parameter

* Add unit tests

* Add integration tests

* Add comments

* Fix XorBranch topology
This commit is contained in:
InversionSpaces 2023-07-12 15:18:47 +02:00 committed by GitHub
parent a7dba14c7c
commit b8b0fafda0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1191 additions and 708 deletions

View File

@ -8,14 +8,13 @@ case class AquaAPIConfig(
targetType: TargetType = TargetType.AirType,
logLevel: String = "info",
constants: List[String] = Nil,
noXor: Boolean = false,
noXor: Boolean = false, // TODO: Remove
noRelay: Boolean = false,
tracing: Boolean = false
) {
def getTransformConfig: TransformConfig = {
val config = TransformConfig(
wrapWithXor = !noXor,
tracing = Option.when(tracing)(TransformConfig.TracingConfig.default)
)

View File

@ -24,6 +24,8 @@ object Keyword {
case object Ap extends Keyword("ap")
case object Fail extends Keyword("fail")
case object Canon extends Keyword("canon")
case object Seq extends Keyword("seq")
@ -110,6 +112,8 @@ object Air {
case class Ap(op: DataView, result: String) extends Air(Keyword.Ap)
case class Fail(op: DataView) extends Air(Keyword.Fail)
case class Canon(op: DataView, peerId: DataView, result: String) extends Air(Keyword.Canon)
case class Comment(comment: String, air: Air) extends Air(Keyword.NA)
@ -143,6 +147,7 @@ object Air {
case Air.Call(triplet, args, res)
s" ${triplet.show} [${args.map(_.show).mkString(" ")}]${res.fold("")(" " + _)}"
case Air.Ap(operand, result) s" ${operand.show} $result"
case Air.Fail(operand) => s" ${operand.show}"
case Air.Canon(operand, peerId, result) s" ${peerId.show} ${operand.show} $result"
case Air.Comment(_, _) => ";; Should not be displayed"
}) + ")\n"

View File

@ -118,6 +118,11 @@ object AirGen extends Logging {
ApGen(valueToData(operand), exportToString(exportTo))
)
case FailRes(operand) =>
Eval.later(
FailGen(valueToData(operand))
)
case CanonRes(operand, peerId, exportTo) =>
Eval.later(
CanonGen(valueToData(operand), valueToData(peerId), exportToString(exportTo))
@ -164,6 +169,12 @@ case class ApGen(operand: DataView, result: String) extends AirGen {
Air.Ap(operand, result)
}
case class FailGen(operand: DataView) extends AirGen {
override def generate: Air =
Air.Fail(operand)
}
case class CanonGen(operand: DataView, peerId: DataView, result: String) extends AirGen {
override def generate: Air =

View File

@ -74,8 +74,8 @@ lazy val cliJS = cli.js
.settings(
Compile / fastOptJS / artifactPath := baseDirectory.value / "../../cli-npm" / "aqua.js",
Compile / fullOptJS / artifactPath := baseDirectory.value / "../../cli-npm" / "aqua.js",
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule)),
scalaJSUseMainModuleInitializer := true
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule)),
scalaJSUseMainModuleInitializer := true
)
.dependsOn(`js-exports`, `js-imports`)
@ -155,9 +155,9 @@ lazy val `aqua-apiJS` = `aqua-api`.js
.settings(
Compile / fastOptJS / artifactPath := baseDirectory.value / "../../api-npm" / "aqua-api.js",
Compile / fullOptJS / artifactPath := baseDirectory.value / "../../api-npm" / "aqua-api.js",
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true,
Test / test := {}
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true,
Test / test := {}
)
.enablePlugins(ScalaJSPlugin)
.dependsOn(`js-exports`)
@ -252,7 +252,7 @@ lazy val compiler = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("compiler"))
.settings(commons: _*)
.dependsOn(semantics, linker, backend, transform % Test, res % "test->test")
.dependsOn(semantics, linker, backend, transform % "test->test", res % "test->test")
lazy val backend = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)

View File

@ -22,7 +22,7 @@ import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.{Id, Monad, ~>}
import cats.{~>, Id, Monad}
import com.monovore.decline.{Command, Opts}
import fs2.io.file.{Files, Path}
import scribe.Logging
@ -45,14 +45,15 @@ object RunOpts extends Logging {
): TransformConfig = {
val tc = TransformConfig(
constants =
onPeer.map(s => ConstantRaw(OnPeerConst, LiteralRaw.quote(s), false)).toList ++ constants,
wrapWithXor = !noXor
onPeer.map(s => ConstantRaw(OnPeerConst, LiteralRaw.quote(s), false)).toList ++ constants
)
tc.copy(relayVarName = tc.relayVarName.filterNot(_ => noRelay))
}
def runOptsCompose[F[_]: Files: Concurrent]
: Opts[F[ValidatedNec[String, (Option[AquaPath], List[Path], FuncWithData, Option[NonEmptyList[JsonService]], List[String])]]] = {
def runOptsCompose[F[_]: Files: Concurrent]: Opts[F[ValidatedNec[
String,
(Option[AquaPath], List[Path], FuncWithData, Option[NonEmptyList[JsonService]], List[String])
]]] = {
(
AppOpts.wrapWithOption(AppOpts.inputOpts[F]),
AppOpts.importOpts[F],
@ -72,8 +73,9 @@ object RunOpts extends Logging {
.getOrElse(validNec[String, Option[NonEmptyList[JsonService]]](None).pure[F])
pluginsPathsV <- pluginsOp.getOrElse(validNec[String, List[String]](Nil).pure[F])
} yield {
(inputV, importV, funcWithArgsV, jsonServiceV, pluginsPathsV).mapN { case (i, im, f, j, p) =>
(i, im, f, j, p)
(inputV, importV, funcWithArgsV, jsonServiceV, pluginsPathsV).mapN {
case (i, im, f, j, p) =>
(i, im, f, j, p)
}
}
}

View File

@ -17,15 +17,7 @@ import aqua.raw.ops.{Call, CallArrowRawTag}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.res.{AquaRes, FuncRes}
import aqua.run.RunOpts.logger
import aqua.run.{
CliFunc,
FuncCompiler,
GeneralOptions,
GeneralOpts,
RunCommand,
RunConfig,
RunOpts
}
import aqua.run.{CliFunc, FuncCompiler, GeneralOptions, GeneralOpts, RunCommand, RunConfig, RunOpts}
import aqua.types.{ArrowType, LiteralType, NilType, ScalarType}
import cats.data.*
import cats.data.Validated.{invalid, invalidNec, valid, validNec, validNel}
@ -126,7 +118,7 @@ object ScriptOpts extends Logging {
imports: List[Path],
funcWithArgs: FuncWithLiteralArgs
): F[ValidatedNec[String, String]] = {
val tConfig = TransformConfig(relayVarName = None, wrapWithXor = false)
val tConfig = TransformConfig(relayVarName = None)
val funcCompiler =
new FuncCompiler[F](
Option(RelativePath(input)),

View File

@ -27,7 +27,7 @@ object Test extends IOApp.Simple {
List(Path("./aqua")),
Option(Path("./target")),
TypeScriptBackend(false, "IFluenceClient$$"),
TransformConfig(wrapWithXor = false),
TransformConfig(),
false
)
.map {

View File

@ -94,7 +94,7 @@ object AquaCli extends IOApp with Logging {
compileToAir,
compileToJs,
noRelay,
noXorWrapper,
noXorWrapper, // TODO: Remove
tracing,
isOldFluenceJs,
wrapWithOption(helpOpt),
@ -137,7 +137,6 @@ object AquaCli extends IOApp with Logging {
else TypescriptTarget
val bc = {
val bc = TransformConfig(
wrapWithXor = !noXor,
constants = constants,
tracing = Option.when(tracingEnabled)(TransformConfig.TracingConfig.default)
)

View File

@ -9,6 +9,7 @@ import aqua.model.{
ValueModel,
VarModel
}
import aqua.model.transform.ModelBuilder
import aqua.model.transform.TransformConfig
import aqua.model.transform.Transform
import aqua.parser.ParserError
@ -42,6 +43,7 @@ import cats.instances.string.*
import cats.syntax.show.*
class AquaCompilerSpec extends AnyFlatSpec with Matchers {
import ModelBuilder.*
private def aquaSource(src: Map[String, String], imports: Map[String, String]) = {
new AquaSources[Id, String, String] {
@ -150,8 +152,8 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
ctxs.length should be(1)
val ctx = ctxs.headOption.get
val aquaRes =
Transform.contextRes(ctx, TransformConfig(wrapWithXor = false))
val transformCfg = TransformConfig()
val aquaRes = Transform.contextRes(ctx, transformCfg)
val Some(exec) = aquaRes.funcs.find(_.funcName == "exec")
@ -167,47 +169,49 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
SeqRes.wrap(
getDataSrv("-relay-", ScalarType.string),
getDataSrv(peers.name, peers.`type`),
RestrictionRes(results.name, resultsType).wrap(
SeqRes.wrap(
ParRes.wrap(
FoldRes(peer.name, peers, Some(ForModel.NeverMode)).wrap(
ParRes.wrap(
// better if first relay will be outside `for`
SeqRes.wrap(
through(ValueModel.fromRaw(relay)),
CallServiceRes(
LiteralModel.fromRaw(LiteralRaw.quote("op")),
"identity",
CallRes(
LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil,
Some(CallModel.Export(results.name, results.`type`))
XorRes.wrap(
RestrictionRes(results.name, resultsType).wrap(
SeqRes.wrap(
ParRes.wrap(
FoldRes(peer.name, peers, Some(ForModel.NeverMode)).wrap(
ParRes.wrap(
XorRes.wrap(
// better if first relay will be outside `for`
SeqRes.wrap(
through(ValueModel.fromRaw(relay)),
CallServiceRes(
LiteralModel.fromRaw(LiteralRaw.quote("op")),
"identity",
CallRes(
LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil,
Some(CallModel.Export(results.name, results.`type`))
),
peer
).leaf,
through(ValueModel.fromRaw(relay)),
through(initPeer)
),
peer
).leaf,
through(ValueModel.fromRaw(relay)),
through(initPeer)
),
NextRes(peer.name).leaf
SeqRes.wrap(
through(ValueModel.fromRaw(relay)),
through(initPeer),
failLastErrorRes
)
),
NextRes(peer.name).leaf
)
)
)
),
join(results, LiteralModel.fromRaw(LiteralRaw.number(2))),
CanonRes(results, init, CallModel.Export(canonResult.name, canonResult.`type`)).leaf,
ApRes(
canonResult,
CallModel.Export(flatResult.name, flatResult.`type`)
).leaf
)
),
CallServiceRes(
LiteralModel.fromRaw(LiteralRaw.quote("callbackSrv")),
"response",
CallRes(
flatResult :: Nil,
None
),
join(results, LiteralModel.fromRaw(LiteralRaw.number(2))),
CanonRes(results, init, CallModel.Export(canonResult.name, canonResult.`type`)).leaf,
ApRes(
canonResult,
CallModel.Export(flatResult.name, flatResult.`type`)
).leaf
)
),
initPeer
).leaf
errorCall(transformCfg, 0, initPeer)
),
respCall(transformCfg, flatResult, initPeer)
)
exec.body.equalsOrShowDiff(expected) shouldBe (true)
@ -267,8 +271,8 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
ctxs.length should be(1)
val ctx = ctxs.headOption.get
val aquaRes =
Transform.contextRes(ctx, TransformConfig(wrapWithXor = false, relayVarName = None))
val transformCfg = TransformConfig(relayVarName = None)
val aquaRes = Transform.contextRes(ctx, transformCfg)
val Some(funcWrap) = aquaRes.funcs.find(_.funcName == "wrap")
val Some(barfoo) = aquaRes.funcs.find(_.funcName == "barfoo")
@ -278,8 +282,8 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
val resCanonVM = VarModel("-res-fix-0", CanonStreamType(ScalarType.string))
val resFlatVM = VarModel("-res-flat-0", ArrayType(ScalarType.string))
barfoo.body.equalsOrShowDiff(
SeqRes.wrap(
val expected = SeqRes.wrap(
XorRes.wrap(
RestrictionRes(resVM.name, resStreamType).wrap(
SeqRes.wrap(
// res <- foo()
@ -305,14 +309,12 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers {
).leaf
)
),
CallServiceRes(
LiteralModel.fromRaw(LiteralRaw.quote("callbackSrv")),
"response",
CallRes(resFlatVM :: Nil, None),
LiteralModel.fromRaw(ValueRaw.InitPeerId)
).leaf
)
) should be(true)
errorCall(transformCfg, 0, initPeer)
),
respCall(transformCfg, resFlatVM, initPeer)
)
barfoo.body.equalsOrShowDiff(expected) should be(true)
}
}

View File

@ -0,0 +1,39 @@
service Test("test-service"):
fail(err: string)
func onPropagate(peer: string, relay: string) -> u16:
res: *u16
on peer via relay:
res <<- 0 + 1
Test.fail("propagated error")
res <<- 0 + 2
join res[3] -- Unreachable
<- res[3]
func nestedOnPropagate(peer: string, relay: string, iPeer: string, iRelay: string, friend: string) -> u16:
res: *u16
on iPeer via iRelay:
res <<- 40 + 2
on friend:
res <<- 2 + 40
on peer via relay:
Test.fail("propagated error")
res <<- 30 + 7
join res[3] -- Unreachable
<- res[3]
func seqOnPropagate(peer: string, relay: string, iPeer: string, iRelay: string) -> u16:
res: *u16
on iPeer via iRelay:
res <<- 40 + 2
on peer via relay:
Test.fail("propagated error")
res <<- 30 + 7
join res[2] -- Unreachable
<- res[2]

View File

@ -4,6 +4,7 @@ import {getObjAssignCall, getObjCall, getObjRelayCall} from "../examples/objectC
import {callArrowCall, reproArgsBug426Call} from '../examples/callArrowCall.js';
import {dataAliasCall} from '../examples/dataAliasCall.js';
import {onCall} from '../examples/onCall.js';
import {onPropagateCall, nestedOnPropagateCall, seqOnPropagateCall} from '../examples/onErrorPropagation.js';
import {funcCall} from '../examples/funcCall.js';
import {registerPrintln} from '../compiled/examples/println.js';
import {helloWorldCall} from '../examples/helloWorldCall.js';
@ -467,6 +468,27 @@ describe('Testing examples', () => {
expect(onCallResult).toEqual(config.externalAddressesRelay1);
});
it('onErrorPropagate.aqua', async () => {
let call = onPropagateCall(peer2, relay2.peerId);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
});
it('onErrorPropagate.aqua nested', async () => {
let call = nestedOnPropagateCall(peer2, relay2.peerId, config.relays[3].peerId, config.relays[4].peerId, config.relays[5].peerId);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
});
it('onErrorPropagate.aqua sequential', async () => {
let call = seqOnPropagateCall(peer2, relay2.peerId, config.relays[3].peerId, config.relays[4].peerId);
expect(call).rejects.toMatchObject({
message: expect.stringContaining("propagated error")
})
});
it('complex.aqua', async () => {
let complexCallResult = await complexCall(selfPeerId, relayPeerId1);
expect(complexCallResult).toEqual(['some str', '3', '1', '4', '1', '1', '3', '2', '4', '2', '2', selfPeerId]);

View File

@ -0,0 +1,43 @@
import {IFluenceClient} from '@fluencelabs/js-client.api';
import {registerTest, onPropagate, nestedOnPropagate, seqOnPropagate} from "../compiled/examples/onErrorPropagation.js"
export async function onPropagateCall(peer2: IFluenceClient, relay2: string): Promise<number> {
registerTest(peer2, {
fail(err, callParams) {
return Promise.reject(err);
},
})
return onPropagate(peer2.getPeerId(), relay2)
}
export async function nestedOnPropagateCall(
peer2: IFluenceClient,
relay2: string,
iPeer: string,
iRelay: string,
friend: string
): Promise<number> {
registerTest(peer2, {
fail(err, callParams) {
return Promise.reject(err);
},
})
return nestedOnPropagate(peer2.getPeerId(), relay2, iPeer, iRelay, friend)
}
export async function seqOnPropagateCall(
peer2: IFluenceClient,
relay2: string,
iPeer: string,
iRelay: string
): Promise<number> {
registerTest(peer2, {
fail(err, callParams) {
return Promise.reject(err);
},
})
return seqOnPropagate(peer2.getPeerId(), relay2, iPeer, iRelay)
}

View File

@ -17,6 +17,7 @@ import cats.syntax.option.*
import cats.instances.list.*
import cats.data.{Chain, State, StateT}
import cats.syntax.show.*
import cats.syntax.bifunctor.*
import scribe.{log, Logging}
import aqua.model.inline.Inline.parDesugarPrefixOpt
@ -186,11 +187,19 @@ object TagInliner extends Logging {
flat(vm, tree, true)
}
(pid, pif) = peerIdDe
viaD = Chain.fromSeq(viaDeFlattened.map(_._1))
viaF = viaDeFlattened.flatMap(_._2)
} yield TagInlined.Single(
model = OnModel(pid, viaD),
(viaD, viaF) = viaDeFlattened.unzip
.bimap(Chain.fromSeq, _.flatten)
toModel = (children: Chain[OpModel.Tree]) =>
XorModel.wrap(
OnModel(pid, viaD).wrap(
children
),
// This will return to previous topology
// and propagate error up
FailModel(ValueModel.lastError).leaf
)
} yield TagInlined.Mapping(
toModel = toModel,
prefix = parDesugarPrefix(viaF.prependedAll(pif))
)

View File

@ -42,20 +42,21 @@ object ConstantRaw {
val lastError: ConstantRaw =
ConstantRaw(
"LAST_ERROR",
ValueRaw.LastError,
ValueRaw.lastError,
false
)
// Host peer id holds %init_peer_id% in case Aqua is not compiled to be executed behind a relay,
// or relay's variable otherwise
def hostPeerId(relayVarName: Option[String]): ConstantRaw =
ConstantRaw(
"HOST_PEER_ID",
relayVarName.fold[ValueRaw](ValueRaw.InitPeerId)(r => VarRaw(r, ScalarType.string)),
false
)
ConstantRaw(
"HOST_PEER_ID",
relayVarName.fold[ValueRaw](ValueRaw.InitPeerId)(r => VarRaw(r, ScalarType.string)),
false
)
def defaultConstants(relayVarName: Option[String]): List[ConstantRaw] =
hostPeerId(relayVarName) :: initPeerId :: particleTtl :: particleTimestamp :: nil :: lastError :: Nil
hostPeerId(
relayVarName
) :: initPeerId :: particleTtl :: particleTimestamp :: nil :: lastError :: Nil
}

View File

@ -27,21 +27,22 @@ object ValueRaw {
val Nil: LiteralRaw = LiteralRaw("[]", StreamType(BottomType))
val LastError: VarRaw = VarRaw(
"%last_error%",
StructType(
"LastError",
NonEmptyMap.of(
// These two fields are mandatory for all errors
"message" -> ScalarType.string,
"error_code" -> ScalarType.i64,
// These fields are specific to AquaVM's errors only
"instruction" -> ScalarType.string,
"peer_id" -> ScalarType.string
)
val lastErrorType = StructType(
"LastError",
NonEmptyMap.of(
// These two fields are mandatory for all errors
"message" -> ScalarType.string,
"error_code" -> ScalarType.i64,
// These fields are specific to AquaVM's errors only
"instruction" -> ScalarType.string,
"peer_id" -> ScalarType.string
)
)
val lastError: VarRaw = VarRaw(
"%last_error%",
lastErrorType
)
}
case class ApplyPropertyRaw(value: ValueRaw, property: PropertyRaw) extends ValueRaw {

View File

@ -76,6 +76,8 @@ object MakeRes {
ApRes(operand, CallModel.Export(assignTo, ArrayType(el))).leaf
case FlattenModel(operand, assignTo) =>
ApRes(operand, CallModel.Export(assignTo, operand.`type`)).leaf
case FailModel(value) =>
FailRes(value).leaf
case CallServiceModel(serviceId, funcName, CallModel(args, exportTo)) =>
CallServiceRes(
serviceId,

View File

@ -54,6 +54,10 @@ case class ApRes(operand: ValueModel, exportTo: CallModel.Export) extends Resolv
override def toString: String = s"(ap $operand $exportTo)"
}
case class FailRes(operand: ValueModel) extends ResolvedOp {
override def toString: String = s"(fail $operand)"
}
case class CanonRes(operand: ValueModel, peerId: ValueModel, exportTo: CallModel.Export)
extends ResolvedOp {
override def toString: String = s"(canon $peerId $operand $exportTo)"

View File

@ -1,13 +1,15 @@
package aqua.model
import aqua.model.OpModel.Tree
import aqua.tree.{TreeNode, TreeNodeCompanion}
import aqua.types.*
import cats.data.Chain
import cats.free.Cofree
import cats.Show
import cats.Eval
import cats.data.NonEmptyList
import aqua.tree.{TreeNode, TreeNodeCompanion}
import aqua.types.*
import cats.syntax.functor.*
import scala.annotation.tailrec
@ -79,7 +81,15 @@ case object ParModel extends ParGroupModel
case object DetachModel extends ParGroupModel
case object XorModel extends GroupOpModel
case object XorModel extends GroupOpModel {
// If left branch is empty, return empty
override def wrap(children: Chain[Tree]): Tree =
children.headOption
.filterNot(_.head == EmptyModel)
.as(super.wrap(children))
.getOrElse(EmptyModel.leaf)
}
case class OnModel(peerId: ValueModel, via: Chain[ValueModel]) extends SeqGroupModel {
@ -146,6 +156,12 @@ case class FlattenModel(value: ValueModel, assignTo: String) extends OpModel {
override def exportsVarNames: Set[String] = Set(assignTo)
}
case class FailModel(value: ValueModel) extends OpModel {
override def usesVarNames: Set[String] = value.usesVarNames
override def exportsVarNames: Set[String] = Set.empty
}
case class PushToStreamModel(value: ValueModel, exportTo: CallModel.Export) extends OpModel {
override def usesVarNames: Set[String] = value.usesVarNames

View File

@ -2,8 +2,10 @@ package aqua.model
import aqua.raw.value.*
import aqua.types.*
import cats.Eq
import cats.data.{Chain, NonEmptyMap}
import cats.syntax.option.*
import scribe.Logging
sealed trait ValueModel {
@ -18,6 +20,19 @@ sealed trait ValueModel {
object ValueModel {
def errorCode(error: VarModel): Option[VarModel] =
error.intoField("error_code")
val lastError = VarModel(
name = ValueRaw.lastError.name,
baseType = ValueRaw.lastError.baseType
)
val lastErrorType = ValueRaw.lastErrorType
// NOTE: It should be safe as %last_error% should have `error_code` field
val lastErrorCode = errorCode(lastError).get
implicit object ValueModelEq extends Eq[ValueModel] {
override def eqv(x: ValueModel, y: ValueModel): Boolean = x == y
}
@ -46,9 +61,16 @@ case class LiteralModel(value: String, `type`: Type) extends ValueModel {
}
object LiteralModel {
// AquaVM will return empty string for
// %last_error%.$.error_code if there is no %last_error%
val emptyErrorCode = quote("")
def fromRaw(raw: LiteralRaw): LiteralModel = LiteralModel(raw.value, raw.baseType)
def quote(str: String): LiteralModel = LiteralModel(s"\"$str\"", LiteralType.string)
def number(n: Int): LiteralModel = LiteralModel(n.toString, LiteralType.number)
}
sealed trait PropertyModel {
@ -117,6 +139,17 @@ case class VarModel(name: String, baseType: Type, properties: Chain[PropertyMode
private def deriveFrom(vm: VarModel): VarModel =
vm.copy(properties = vm.properties ++ properties)
def intoField(field: String): Option[VarModel] = `type` match {
case StructType(_, fields) =>
fields(field)
.map(fieldType =>
copy(
properties = properties :+ IntoFieldModel(field, fieldType)
)
)
case _ => none
}
override def resolveWith(vals: Map[String, ValueModel]): ValueModel =
vals.get(name) match {
case Some(vv: VarModel) =>

View File

@ -19,6 +19,7 @@ import cats.free.Cofree
import cats.syntax.option.*
import scribe.Logging
import aqua.model.transform.TransformConfig.TracingConfig
import aqua.model.transform.pre.{CallbackErrorHandler, ErrorHandler}
// API for transforming RawTag to Res
object Transform extends Logging {
@ -82,11 +83,30 @@ object Transform extends Logging {
goThrough = Chain.fromOption(relayVar)
)
val errorsCatcher = ErrorsCatcher(
enabled = conf.wrapWithXor,
serviceId = conf.errorHandlingCallback,
funcName = conf.errorFuncName,
callable = initCallable
val argsProvider: ArgsProvider = ArgsFromService(
dataServiceId = conf.dataSrvId
)
val resultsHandler: ResultsHandler = CallbackResultsHandler(
callbackSrvId = conf.callbackSrvId,
funcName = conf.respFuncName
)
val errorHandler: ErrorHandler = CallbackErrorHandler(
serviceId = conf.errorHandlingSrvId,
funcName = conf.errorFuncName
)
// Callback on the init peer id, either done via relay or not
val callback = initCallable.service(conf.callbackSrvId)
// preTransformer is applied before function is inlined
val preTransformer = FuncPreTransformer(
argsProvider,
resultsHandler,
errorHandler,
callback,
conf.relayVarName
)
val tracing = Tracing(
@ -94,31 +114,16 @@ object Transform extends Logging {
initCallable = initCallable
)
val argsProvider: ArgsProvider = ArgsFromService(
dataServiceId = conf.dataSrvId,
names = relayVar.toList ::: func.arrowType.domain.labelledData
)
// Transform the body of the function: wrap it with initCallable, provide function arguments via service calls
val transform: RawTag.Tree => RawTag.Tree =
argsProvider.transform andThen initCallable.transform
// Callback on the init peer id, either done via relay or not
val callback = initCallable.service(conf.callbackSrvId)
// preTransformer is applied before function is inlined
val preTransformer = FuncPreTransformer(
transform,
callback,
conf.respFuncName
)
for {
// Pre transform and inline the function
model <- funcToModelTree(func, preTransformer)
// Post transform the function
errorsModel = errorsCatcher.transform(model)
tracingModel <- tracing(errorsModel)
// Post transform the function.
// We should wrap `model` with `onInitPeer` here
// so that TagInliner would not wrap it with `xor`.
// Topology module needs this `on`
// as a starting point.
initModel = initCallable.onInitPeer.wrap(model)
tracingModel <- tracing(initModel)
// Resolve topology
resolved <- Topology.resolve(tracingModel)
// Clear the tree

View File

@ -15,17 +15,14 @@ case class TransformConfig(
errorFuncName: String = "error",
respFuncName: String = "response",
relayVarName: Option[String] = Some("-relay-"),
wrapWithXor: Boolean = true,
tracing: Option[TransformConfig.TracingConfig] = None,
constants: List[ConstantRaw] = Nil
) {
import LiteralRaw.quote
val errorId: ValueRaw = quote(errorFuncName)
val errorHandlingCallback: ValueModel = LiteralModel fromRaw quote(errorHandlingService)
val callbackSrvId: ValueRaw = quote(callbackService)
val dataSrvId: ValueRaw = quote(getDataService)
val errorId: ValueRaw = LiteralRaw.quote(errorFuncName)
val errorHandlingSrvId: ValueRaw = LiteralRaw.quote(errorHandlingService)
val callbackSrvId: ValueRaw = LiteralRaw.quote(callbackService)
val dataSrvId: ValueRaw = LiteralRaw.quote(getDataService)
val constantsList: List[ConstantRaw] =
ConstantRaw.defaultConstants(relayVarName) ::: constants

View File

@ -1,77 +0,0 @@
package aqua.model.transform.funcop
import aqua.model.transform.pre.InitPeerCallable
import aqua.model.{
CallModel,
CallServiceModel,
ForceExecModel,
LiteralModel,
MatchMismatchModel,
NoExecModel,
OnModel,
OpModel,
SeqModel,
ValueModel,
VarModel,
XorModel
}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.types.LiteralType
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
case class ErrorsCatcher(
enabled: Boolean,
serviceId: ValueModel,
funcName: String,
callable: InitPeerCallable
) {
private def hasExec(children: Chain[OpModel.Tree]): Boolean =
children.exists {
case Cofree(head: ForceExecModel, _) =>
true
case Cofree(_, tail) =>
hasExec(tail.value)
}
def transform(op: OpModel.Tree): OpModel.Tree =
if (enabled) {
var i = 0
Cofree
.cata[Chain, OpModel, OpModel.Tree](op) {
case (ot @ (OnModel(_, _) | MatchMismatchModel(_, _, _)), children)
if hasExec(children) =>
i = i + 1
Eval now ot.wrap(
XorModel.wrap(
SeqModel.wrap(children.toList: _*),
callable.onInitPeer.wrap(
CallServiceModel(
serviceId,
funcName,
ErrorsCatcher.lastErrorCall(i)
).leaf
)
)
)
case (tag, children) =>
Eval.now(Cofree(tag, Eval.now(children)))
}
.value
} else op
}
object ErrorsCatcher {
val lastErrorArg: ValueModel =
VarModel(ValueRaw.LastError.name, ValueRaw.LastError.baseType, Chain.empty)
def lastErrorCall(i: Int): CallModel = CallModel(
lastErrorArg :: LiteralModel.fromRaw(LiteralRaw.number(i)) :: Nil,
Nil
)
}

View File

@ -5,10 +5,11 @@ import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.types.{ArrayType, DataType, StreamType}
import cats.data.Chain
trait ArgsProvider extends PreTransform
trait ArgsProvider {
def provideArgs(args: List[(String, DataType)]): List[RawTag.Tree]
}
case class ArgsFromService(dataServiceId: ValueRaw, names: List[(String, DataType)])
extends ArgsProvider {
case class ArgsFromService(dataServiceId: ValueRaw) extends ArgsProvider {
private def getStreamDataOp(name: String, t: StreamType): RawTag.Tree = {
val iter = s"$name-iter"
@ -44,9 +45,7 @@ case class ArgsFromService(dataServiceId: ValueRaw, names: List[(String, DataTyp
.leaf
}
def transform(op: RawTag.Tree): RawTag.Tree =
SeqTag.wrap(
names.map((getDataOp _).tupled) :+ op: _*
)
override def provideArgs(args: List[(String, DataType)]): List[RawTag.Tree] =
args.map(getDataOp.tupled)
}

View File

@ -0,0 +1,28 @@
package aqua.model.transform.pre
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.raw.ops.{Call, CallArrowRawTag, RawTag}
import aqua.types.LiteralType
import cats.Eval
import cats.data.Chain
import cats.free.Cofree
trait ErrorHandler {
def handleLastError: RawTag.Tree
}
case class CallbackErrorHandler(
serviceId: ValueRaw,
funcName: String
) extends ErrorHandler {
override def handleLastError: RawTag.Tree = {
val call = Call(
args = ValueRaw.lastError :: LiteralRaw.number(0) :: Nil,
exportTo = Nil
)
CallArrowRawTag.service(serviceId, funcName, call).leaf
}
}

View File

@ -2,36 +2,27 @@ package aqua.model.transform.pre
import aqua.model.FuncArrow
import aqua.model.ArgsCall
import aqua.raw.ops.{Call, CallArrowRawTag, RawTag, SeqTag}
import aqua.raw.ops.{Call, CallArrowRawTag, RawTag, SeqTag, TryTag}
import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.types.*
import cats.syntax.show.*
import cats.syntax.option.*
// TODO: doc
case class FuncPreTransformer(
transform: RawTag.Tree => RawTag.Tree,
argsProvider: ArgsProvider,
resultsHandler: ResultsHandler,
errorHandler: ErrorHandler,
callback: (String, Call) => RawTag.Tree,
respFuncName: String,
relayVarName: Option[String],
wrapCallableName: String = "funcAround",
arrowCallbackPrefix: String = "init_peer_callable_"
) {
private val returnVar: String = "-return-"
/**
* Wraps return values of a function to a call on itin peer's side
*
* @param retModel List of returned values
* @return AST that consumes return values, passing them to the client
*/
private def returnCallback(retModel: List[ValueRaw]): RawTag.Tree =
callback(
respFuncName,
Call(
retModel,
Nil
)
)
private val relayVar = relayVarName.map(_ -> ScalarType.string)
/**
* Convert an arrow-type argument to init user's callback
@ -68,23 +59,35 @@ case class FuncPreTransformer(
case t => t
}).toLabelledList(returnVar)
val retModel = returnType.map { case (l, t) => VarRaw(l, t) }
val funcCall = Call(
func.arrowType.domain.toLabelledList().map(ad => VarRaw(ad._1, ad._2)),
returnType.map { case (l, t) => Call.Export(l, t) }
)
val provideArgs = argsProvider.provideArgs(
relayVar.toList ::: func.arrowType.domain.labelledData
)
val handleResults = resultsHandler.handleResults(
returnType
)
val handleError = errorHandler.handleLastError
val call = CallArrowRawTag.func(func.funcName, funcCall).leaf
val body = SeqTag.wrap(
provideArgs ++ List(
TryTag.wrap(
call,
handleError
)
) ++ handleResults
)
FuncArrow(
wrapCallableName,
transform(
SeqTag.wrap(
CallArrowRawTag.func(func.funcName, funcCall).leaf ::
returnType.headOption
.map(_ => returnCallback(retModel))
.toList: _*
)
),
body,
ArrowType(ConsType.cons(func.funcName, func.arrowType, NilType), NilType),
Nil,
func.arrowType.domain

View File

@ -0,0 +1,27 @@
package aqua.model.transform.pre
import aqua.types.Type
import aqua.raw.ops.{Call, CallArrowRawTag, RawTag}
import aqua.raw.value.{ValueRaw, VarRaw}
import cats.syntax.option.*
trait ResultsHandler {
def handleResults(results: List[(String, Type)]): Option[RawTag.Tree]
}
case class CallbackResultsHandler(callbackSrvId: ValueRaw, funcName: String)
extends ResultsHandler {
override def handleResults(results: List[(String, Type)]): Option[RawTag.Tree] =
if (results.isEmpty) none
else {
val resultVars = results.map(VarRaw.apply.tupled)
val call = Call(
args = resultVars,
exportTo = Nil
)
CallArrowRawTag.service(callbackSrvId, funcName, call).leaf.some
}
}

View File

@ -187,43 +187,58 @@ object Topology extends Logging {
.filterNot(lastPeerId => before.headOption.exists(_.peerId == lastPeerId))
)
// Return strategy for calculating `beforeOn` for
// node pointed on by `cursor`
private def decideBefore(cursor: OpModelTreeCursor): Before =
cursor.parentOp match {
case Some(XorModel) => XorBranch
case Some(_: SeqGroupModel) => SeqGroupBranch
case None => Root
case _ => Default
}
// Return strategy for calculating `beginsOn` for
// node pointed on by `cursor`
private def decideBegins(cursor: OpModelTreeCursor): Begins =
(cursor.parentOp, cursor.op) match {
case (_, _: FailModel) => Fail
case (Some(_: SeqGroupModel), _: NextModel) => SeqNext
case (_, _: ForModel) => For
// No begin optimization for detach
case (_, ParModel) => ParGroup
case _ => Default
}
// Return strategy for calculating `endsOn` for
// node pointed on by `cursor`
private def decideEnds(cursor: OpModelTreeCursor): Ends =
cursor.op match {
case _: SeqGroupModel => SeqGroup
case XorModel => XorGroup
case _: ParGroupModel => ParGroup
case _ if cursor.parentOp.isEmpty => Root
case _ => Default
}
// Return strategy for calculating `afterOn` for
// node pointed on by `cursor`
private def decideAfter(cursor: OpModelTreeCursor): After =
(cursor.parentOp, cursor.op) match {
case (_, _: FailModel) => Fail
case (Some(_: ParGroupModel), _) => ParGroupBranch
case (Some(XorModel), _) => XorBranch
case (Some(_: SeqGroupModel), _) => SeqGroupBranch
case (None, _) => Root
case _ => Default
}
def make(cursor: OpModelTreeCursor): Topology =
Topology(
cursor,
// Before
cursor.parentOp match {
case Some(XorModel) => XorBranch
case Some(_: SeqGroupModel) => SeqGroupBranch
case None => Root
case _ => Default
},
// Begin
(cursor.parentOp, cursor.op) match {
case (Some(_: SeqGroupModel), _: NextModel) =>
SeqNext
case (_, _: ForModel) =>
For
case (_, ParModel) => // No begin optimization for detach
ParGroup
case _ =>
Default
},
// End
cursor.op match {
case _: SeqGroupModel => SeqGroup
case XorModel => XorGroup
case _: ParGroupModel => ParGroup
case _ if cursor.parentOp.isEmpty => Root
case _ => Default
},
// After
cursor.parentOp match {
case Some(_: ParGroupModel) => ParGroupBranch
case Some(XorModel) => XorBranch
case Some(_: SeqGroupModel) => SeqGroupBranch
case None => Root
case _ => Default
}
cursor = cursor,
before = decideBefore(cursor),
begins = decideBegins(cursor),
ends = decideEnds(cursor),
after = decideAfter(cursor)
)
def resolve(op: OpModel.Tree, debug: Boolean = false): Eval[Res] =

View File

@ -6,21 +6,24 @@ import aqua.model.{OnModel, ValueModel}
import cats.Eval
import cats.data.Chain
import cats.syntax.apply.*
import cats.syntax.functor.*
import cats.syntax.monad.*
import cats.instances.tuple.*
trait Begins {
def beginsOn(current: Topology): Eval[List[OnModel]] = current.pathOn
def pathBefore(current: Topology): Eval[Chain[ValueModel]] =
(current.beforeOn, current.beginsOn).mapN { case (bef, beg) =>
(PathFinder.findPath(bef, beg), bef, beg)
}.flatMap { case (pb, bef, beg) =>
// Handle the case when we need to go through the relay, but miss the hop as it's the first
// peer where we go, but there's no service calls there
current.firstExecutesOn.map {
case Some(where) if where != beg =>
pb ++ Topology.findRelayPathEnforcement(bef, beg)
case _ => pb
(current.beforeOn, current.beginsOn).tupled
.fproduct(PathFinder.findPath.tupled)
.flatMap { case ((bef, beg), path) =>
// Handle the case when we need to go through the relay, but miss the hop as it's the first
// peer where we go, but there's no service calls there
current.firstExecutesOn.map {
case Some(where) if where != beg =>
path ++ Topology.findRelayPathEnforcement(bef, beg)
case _ => path
}
}
}
}

View File

@ -0,0 +1,33 @@
package aqua.model.transform.topology.strategy
import aqua.model.transform.topology.Topology
import aqua.model.ValueModel
import aqua.model.{OnModel, XorModel}
import cats.data.Chain
import cats.Eval
import cats.syntax.apply.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.syntax.option.*
import cats.syntax.applicative.*
object Fail extends Begins with After {
// override just to be explicit
override def forceExit(current: Topology): Eval[Boolean] =
Eval.now(false) // There is no need to insert hops after `fail`
override def pathBefore(current: Topology): Eval[Chain[ValueModel]] =
for {
path <- super.pathBefore(current)
begins <- current.beginsOn
// Get last hop to final peer
// if it is not in the path
// TODO: Add option to enforce last hop to [[PathFinder]]
hop = begins.headOption
.map(_.peerId)
.filterNot(peer => path.lastOption.contains(peer) || path.isEmpty)
} yield path ++ Chain.fromOption(hop)
}

View File

@ -14,7 +14,7 @@ object XorBranch extends Before with After {
override def toString: String = Console.RED + "<xor>/*" + Console.RESET
override def beforeOn(current: Topology): Eval[List[OnModel]] =
current.prevSibling.map(_.endsOn) getOrElse super.beforeOn(current)
current.prevSibling.map(_.beginsOn) getOrElse super.beforeOn(current)
// Find closest par exit up and return its branch current is in
// Returns none if there is no par up

View File

@ -1,31 +1,21 @@
package aqua.model.transform
import aqua.model.{
CallModel,
CallServiceModel,
DetachModel,
ForModel,
LiteralModel,
NextModel,
OpModel,
ParModel,
SeqModel,
ValueModel,
VarModel
}
import aqua.model.transform.funcop.ErrorsCatcher
import aqua.model.*
import aqua.raw.ops.Call
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.{model, res}
import aqua.res.{CallRes, CallServiceRes, MakeRes}
import aqua.types.{ArrayType, LiteralType, ScalarType}
import scala.language.implicitConversions
import aqua.types.StreamType
import aqua.model.IntoIndexModel
import aqua.model.inline.raw.ApplyGateRawInliner
import scala.language.implicitConversions
import cats.data.Chain
import cats.data.Chain.==:
import aqua.model.inline.raw.ApplyGateRawInliner
import aqua.model.OnModel
import aqua.model.FailModel
import aqua.res.ResolvedOp
object ModelBuilder {
implicit def rawToValue(raw: ValueRaw): ValueModel = ValueModel.fromRaw(raw)
@ -40,10 +30,13 @@ object ModelBuilder {
val otherPeer = VarRaw("other-peer", ScalarType.string)
val otherPeerL = LiteralRaw("\"other-peer\"", LiteralType.string)
val otherRelay = LiteralRaw("other-relay", ScalarType.string)
val otherPeer2 = LiteralRaw("other-peer-2", ScalarType.string)
val otherRelay2 = LiteralRaw("other-relay-2", ScalarType.string)
def otherPeerN(n: Int) = LiteralRaw.quote(s"other-peer-$n")
def otherRelayN(n: Int) = LiteralRaw.quote(s"other-relay-$n")
val otherPeerL = LiteralRaw.quote("other-peer")
val otherRelay = LiteralRaw.quote("other-relay")
val otherPeer2 = otherPeerN(2)
val otherRelay2 = otherRelayN(2)
val iRelay = VarRaw("i", ScalarType.string)
val varNode = VarRaw("node-id", ScalarType.string)
val viaList = VarRaw("other-relay-2", ArrayType(ScalarType.string))
@ -84,10 +77,10 @@ object ModelBuilder {
def errorCall(bc: TransformConfig, i: Int, on: ValueModel = initPeer) =
res
.CallServiceRes(
bc.errorHandlingCallback,
ValueModel.fromRaw(bc.errorHandlingSrvId),
bc.errorFuncName,
CallRes(
ErrorsCatcher.lastErrorArg :: LiteralModel(
ValueModel.lastError :: LiteralModel(
i.toString,
LiteralType.number
) :: Nil,
@ -117,6 +110,22 @@ object ModelBuilder {
)
.leaf
val failLastErrorModel = FailModel(ValueModel.lastError).leaf
val failLastErrorRes = res.FailRes(ValueModel.lastError).leaf
def onRethrowModel(
peer: ValueModel,
via: ValueModel*
): OpModel.Tree => OpModel.Tree =
child =>
XorModel.wrap(
OnModel(peer, Chain.fromSeq(via)).wrap(
child
),
failLastErrorModel
)
def fold(item: String, iter: ValueRaw, mode: Option[ForModel.Mode], body: OpModel.Tree*) = {
val ops = SeqModel.wrap(body: _*)
ForModel(item, ValueModel.fromRaw(iter), mode).wrap(ops, NextModel(item).leaf)
@ -130,7 +139,7 @@ object ModelBuilder {
)
}
def through(peer: ValueModel) =
def through(peer: ValueModel): ResolvedOp.Tree =
MakeRes.hop(peer)
/**

View File

@ -6,11 +6,13 @@ import aqua.model.{CallModel, FuncArrow, LiteralModel, VarModel}
import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, OnTag, RawTag, SeqTag}
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.types.{ArrowType, NilType, ProductType, ScalarType}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.res.{CallRes, CallServiceRes, MakeRes, SeqRes, XorRes}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import cats.data.Chain
import cats.syntax.show.*
class TransformSpec extends AnyFlatSpec with Matchers {
@ -47,13 +49,13 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val procFC = fc.value.body
val expectedFC =
XorRes.wrap(
SeqRes.wrap(
dataCall(bc, "-relay-", initPeer),
through(relayV),
through(otherRelay),
SeqRes.wrap(
dataCall(bc, "-relay-", initPeer),
XorRes.wrap(
XorRes.wrap(
SeqRes.wrap(
through(relayV),
through(otherRelay),
callRes(1, otherPeer),
through(otherRelay),
through(relayV)
@ -61,15 +63,13 @@ class TransformSpec extends AnyFlatSpec with Matchers {
SeqRes.wrap(
through(otherRelay),
through(relayV),
errorCall(bc, 1, initPeer)
through(initPeer),
failLastErrorRes
)
),
XorRes.wrap(
respCall(bc, ret, initPeer),
errorCall(bc, 2, initPeer)
)
errorCall(bc, 0, initPeer)
),
errorCall(bc, 3, initPeer)
respCall(bc, ret, initPeer)
)
procFC.equalsOrShowDiff(expectedFC) should be(true)
@ -90,7 +90,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
None
)
val bc = TransformConfig(wrapWithXor = false)
val bc = TransformConfig()
val fc = Transform.funcRes(func, bc)
@ -99,10 +99,24 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val expectedFC =
SeqRes.wrap(
dataCall(bc, "-relay-", initPeer),
callRes(0, initPeer),
through(relayV),
callRes(1, otherPeer),
through(relayV),
XorRes.wrap(
SeqRes.wrap(
callRes(0, initPeer),
XorRes.wrap(
SeqRes.wrap(
through(relayV),
callRes(1, otherPeer),
through(relayV)
),
SeqRes.wrap(
through(relayV),
through(initPeer),
failLastErrorRes
)
)
),
errorCall(bc, 0, initPeer)
),
respCall(bc, ret, initPeer)
)
@ -124,13 +138,7 @@ class TransformSpec extends AnyFlatSpec with Matchers {
val f1: FuncArrow =
FuncArrow(
"f1",
CallArrowRawTag
.service(
LiteralRaw.quote("srv1"),
"foo",
Call(Nil, Call.Export("v", ScalarType.string) :: Nil)
)
.leaf,
callOp(1).leaf,
stringArrow,
VarRaw("v", ScalarType.string) :: Nil,
Map.empty,
@ -151,22 +159,20 @@ class TransformSpec extends AnyFlatSpec with Matchers {
None
)
val bc = TransformConfig(wrapWithXor = false)
val bc = TransformConfig()
val res = Transform.funcRes(f2, bc).value.body
val procFC = Transform.funcRes(f2, bc).value.body
res.equalsOrShowDiff(
SeqRes.wrap(
dataCall(bc, "-relay-", initPeer),
CallServiceRes(
LiteralRaw.quote("srv1"),
"foo",
CallRes(Nil, Some(CallModel.Export("v", ScalarType.string))),
initPeer
).leaf,
respCall(bc, VarRaw("v", ScalarType.string), initPeer)
)
) should be(true)
val expectedFC = SeqRes.wrap(
dataCall(bc, "-relay-", initPeer),
XorRes.wrap(
callRes(1, initPeer),
errorCall(bc, 0, initPeer)
),
respCall(bc, VarRaw("v", ScalarType.string), initPeer)
)
procFC.equalsOrShowDiff(expectedFC) should be(true)
}
}

View File

@ -24,7 +24,7 @@ class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal {
.around(
N.beginScope(expr.name) >>
L.beginScope() >>
N.define(expr.name, ValueRaw.LastError.baseType),
N.define(expr.name, ValueRaw.lastError.baseType),
(_, g: Raw) =>
N.endScope() >> L.endScope() as (
g match {
@ -32,7 +32,7 @@ class CatchSem[S[_]](val expr: CatchExpr[S]) extends AnyVal {
TryTag.Catch
.wrap(
SeqTag.wrap(
AssignmentTag(ValueRaw.LastError, expr.name.value).leaf,
AssignmentTag(ValueRaw.lastError, expr.name.value).leaf,
op
)
)

View File

@ -133,7 +133,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
TryTag.wrap(
testServiceCallStr("try"),
SeqTag.wrap(
AssignmentTag(ValueRaw.LastError, "e").leaf,
AssignmentTag(ValueRaw.lastError, "e").leaf,
testServiceCallStr("catch")
)
)
@ -159,11 +159,11 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
TryTag.wrap(
testServiceCallStr("try"),
SeqTag.wrap(
AssignmentTag(ValueRaw.LastError, "e").leaf,
AssignmentTag(ValueRaw.lastError, "e").leaf,
testServiceCallStr("catch1")
),
SeqTag.wrap(
AssignmentTag(ValueRaw.LastError, "e").leaf,
AssignmentTag(ValueRaw.lastError, "e").leaf,
testServiceCallStr("catch2")
)
)