diff --git a/api/aqua-api-npm/aqua-api.d.ts b/api/aqua-api-npm/aqua-api.d.ts index 11daff69..578f5bd2 100644 --- a/api/aqua-api-npm/aqua-api.d.ts +++ b/api/aqua-api-npm/aqua-api.d.ts @@ -1,14 +1,21 @@ -import type {FunctionCallDef, ServiceDef} from "@fluencelabs/fluence/dist/internal/compilerSupport/v3impl/interface" +import type { FunctionCallDef, ServiceDef } from "@fluencelabs/fluence/dist/internal/compilerSupport/v3impl/interface" export class AquaConfig { - constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean); - constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean, targetType: string); + constructor( + logLevel?: string, + constants?: string[], + noXor?: boolean, + noRelay?: boolean, + targetType?: string, + tracing?: boolean + ); logLevel?: string constants?: string[] noXor?: boolean noRelay?: boolean targetType?: string + tracing?: boolean } export class AquaFunction { @@ -45,8 +52,8 @@ export class Path { export class Call { constructor(functionCall: string, - arguments: any, - input: Input | Path); + arguments: any, + input: Input | Path); functionCall: string arguments: any diff --git a/api/aqua-api/.js/src/main/scala/api/types/InputTypes.scala b/api/aqua-api/.js/src/main/scala/api/types/InputTypes.scala index 96c2f852..f0ad685a 100644 --- a/api/aqua-api/.js/src/main/scala/api/types/InputTypes.scala +++ b/api/aqua-api/.js/src/main/scala/api/types/InputTypes.scala @@ -4,7 +4,7 @@ import aqua.api.AquaAPIConfig import aqua.api.TargetType.* import aqua.js.{FunctionDefJs, ServiceDefJs} import aqua.model.transform.TransformConfig -import cats.data.Validated.{Invalid, Valid, invalidNec, validNec} +import cats.data.Validated.{invalidNec, validNec} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} import scala.scalajs.js @@ -45,7 +45,9 @@ class AquaConfig( @JSExport val noRelay: js.UndefOr[Boolean], @JSExport - val targetType: js.UndefOr[String] + val targetType: js.UndefOr[String], + @JSExport + val tracing: js.UndefOr[Boolean] ) object AquaConfig { @@ -62,11 +64,12 @@ object AquaConfig { .getOrElse(validNec(AirType)) .map { target => AquaAPIConfig( - target, - cjs.logLevel.getOrElse("info"), - cjs.constants.map(_.toList).getOrElse(Nil), - cjs.noXor.getOrElse(false), - cjs.noRelay.getOrElse(false) + targetType = target, + logLevel = cjs.logLevel.getOrElse("info"), + constants = cjs.constants.map(_.toList).getOrElse(Nil), + noXor = cjs.noXor.getOrElse(false), + noRelay = cjs.noRelay.getOrElse(false), + tracing = cjs.tracing.getOrElse(false) ) } } diff --git a/api/aqua-api/src/main/scala/aqua/api/AquaAPIConfig.scala b/api/aqua-api/src/main/scala/aqua/api/AquaAPIConfig.scala index 9129c965..86abed3b 100644 --- a/api/aqua-api/src/main/scala/aqua/api/AquaAPIConfig.scala +++ b/api/aqua-api/src/main/scala/aqua/api/AquaAPIConfig.scala @@ -9,10 +9,17 @@ case class AquaAPIConfig( logLevel: String = "info", constants: List[String] = Nil, noXor: Boolean = false, - noRelay: Boolean = false + noRelay: Boolean = false, + tracing: Boolean = false ) { - def getTransformConfig: TransformConfig = - if (noRelay) TransformConfig(relayVarName = None, wrapWithXor = !noXor) - else TransformConfig(wrapWithXor = !noXor) + def getTransformConfig: TransformConfig = { + val config = TransformConfig( + wrapWithXor = !noXor, + tracing = Option.when(tracing)(TransformConfig.TracingConfig.default) + ) + + if (noRelay) config.copy(relayVarName = None) + else config + } } diff --git a/aqua-run/src/main/scala/aqua/run/CallPreparer.scala b/aqua-run/src/main/scala/aqua/run/CallPreparer.scala index cde5b98d..f686d592 100644 --- a/aqua-run/src/main/scala/aqua/run/CallPreparer.scala +++ b/aqua-run/src/main/scala/aqua/run/CallPreparer.scala @@ -7,7 +7,7 @@ import aqua.model.transform.{Transform, TransformConfig} import aqua.model.{FuncArrow, ValueModel, VarModel} import aqua.parser.lexer.CallArrowToken import aqua.parser.lift.Span -import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, SeqTag} +import aqua.raw.ops.{Call, CallArrowRawTag, SeqTag} import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} import aqua.types.* import cats.data.Validated.{invalid, invalidNec, invalidNel, validNec, validNel} @@ -18,7 +18,7 @@ import cats.syntax.flatMap.* import cats.syntax.partialOrder.* import cats.syntax.show.* import cats.syntax.traverse.* -import cats.{Id, ~>} +import cats.{~>, Id} import scala.collection.immutable.SortedMap import scala.concurrent.ExecutionContext diff --git a/aqua-run/src/main/scala/aqua/run/RunPreparer.scala b/aqua-run/src/main/scala/aqua/run/RunPreparer.scala index 14c97aeb..003d448e 100644 --- a/aqua-run/src/main/scala/aqua/run/RunPreparer.scala +++ b/aqua-run/src/main/scala/aqua/run/RunPreparer.scala @@ -7,7 +7,7 @@ import aqua.model.transform.{Transform, TransformConfig} import aqua.model.{FuncArrow, ValueModel, VarModel} import aqua.parser.lexer.CallArrowToken import aqua.parser.lift.Span -import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, SeqTag} +import aqua.raw.ops.{Call, CallArrowRawTag, SeqTag} import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw} import aqua.types.* import cats.data.Validated.{invalid, invalidNec, invalidNel, validNec, validNel} @@ -101,9 +101,10 @@ class RunPreparer( val returnCodomain = ProductType(results.map(_.`type`)) // arguments is only variables, without literals - val argumentsType = ProductType.labelled(func.args.zip(funcCallable.arrowType.domain.labelledData).collect { - case (VarRaw(name, _), (_, t)) => (name, t) - }) + val argumentsType = + ProductType.labelled(func.args.zip(funcCallable.arrowType.domain.labelledData).collect { + case (VarRaw(name, _), (_, t)) => (name, t) + }) FuncArrow( func.name + "Run", diff --git a/cli/cli/src/main/scala/aqua/AppOpts.scala b/cli/cli/src/main/scala/aqua/AppOpts.scala index e853b29b..8beb60c9 100644 --- a/cli/cli/src/main/scala/aqua/AppOpts.scala +++ b/cli/cli/src/main/scala/aqua/AppOpts.scala @@ -144,6 +144,12 @@ object AppOpts { .map(_ => true) .withDefault(false) + val tracing: Opts[Boolean] = + Opts + .flag("trace", "Generate tace events calls") + .map(_ => true) + .withDefault(false) + val isOldFluenceJs: Opts[Boolean] = Opts .flagOption[String]( @@ -172,7 +178,7 @@ object AppOpts { Opts .flag( "scheduled", - "Generate air code for script storage. Without error handling wrappers and hops on relay. Will ignore other options" + "Generate air code for script storage. Without error handling wrappers, hops on relay and tracing. Will ignore other options" ) .map(_ => true) .withDefault(false) diff --git a/cli/cli/src/main/scala/aqua/AquaCli.scala b/cli/cli/src/main/scala/aqua/AquaCli.scala index b4e87abd..37efa8f1 100644 --- a/cli/cli/src/main/scala/aqua/AquaCli.scala +++ b/cli/cli/src/main/scala/aqua/AquaCli.scala @@ -17,7 +17,7 @@ import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* import cats.syntax.traverse.* -import cats.{Functor, Id, Monad, ~>} +import cats.{~>, Functor, Id, Monad} import com.monovore.decline import com.monovore.decline.effect.CommandIOApp import com.monovore.decline.effect.CommandIOApp.printHelp @@ -95,6 +95,7 @@ object AquaCli extends IOApp with Logging { compileToJs, noRelay, noXorWrapper, + tracing, isOldFluenceJs, wrapWithOption(helpOpt), wrapWithOption(versionOpt), @@ -112,6 +113,7 @@ object AquaCli extends IOApp with Logging { toJs, noRelayOp, noXorOp, + tracingOp, isOldFluenceJsOp, h, v, @@ -124,6 +126,7 @@ object AquaCli extends IOApp with Logging { val toAir = toAirOp || isScheduled val noXor = noXorOp || isScheduled val noRelay = noRelayOp || isScheduled + val tracingEnabled = tracingOp && !isScheduled // if there is `--help` or `--version` flag - show help and version // otherwise continue program execution @@ -133,7 +136,11 @@ object AquaCli extends IOApp with Logging { else if (toJs) JavaScriptTarget else TypescriptTarget val bc = { - val bc = TransformConfig(wrapWithXor = !noXor, constants = constants) + val bc = TransformConfig( + wrapWithXor = !noXor, + constants = constants, + tracing = Option.when(tracingEnabled)(TransformConfig.TracingConfig.default) + ) bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay)) } LogFormatter.initLogger(Some(logLevel.compiler)) @@ -205,8 +212,8 @@ object AquaCli extends IOApp with Logging { ExitCode.Error } } - .getOrElse{ - ConsoleEff[IO].print(h).map{_ => + .getOrElse { + ConsoleEff[IO].print(h).map { _ => // hack to show last string in `help` println() ExitCode.Success diff --git a/model/inline/src/main/scala/aqua/model/inline/RawValueInliner.scala b/model/inline/src/main/scala/aqua/model/inline/RawValueInliner.scala index a7efdd2a..bb83ed45 100644 --- a/model/inline/src/main/scala/aqua/model/inline/RawValueInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/RawValueInliner.scala @@ -2,7 +2,13 @@ package aqua.model.inline import aqua.model.inline.state.{Arrows, Counter, Exports, Mangler} import aqua.model.* -import aqua.model.inline.raw.{ApplyFunctorRawInliner, ApplyGateRawInliner, ApplyPropertiesRawInliner, CallArrowRawInliner, CollectionRawInliner} +import aqua.model.inline.raw.{ + ApplyFunctorRawInliner, + ApplyGateRawInliner, + ApplyPropertiesRawInliner, + CallArrowRawInliner, + CollectionRawInliner +} import aqua.raw.ops.* import aqua.raw.value.* import aqua.types.{ArrayType, OptionType, StreamType} @@ -86,10 +92,10 @@ object RawValueInliner extends Logging { case (vv, _) => FlattenModel(vv, name).leaf } - }.map{ predo => + }.map { predo => inline.mergeMode match case SeqMode => - SeqModel.wrap((inline.predo.toList ++ predo):_*) :: Nil + SeqModel.wrap((inline.predo.toList ++ predo): _*) :: Nil case ParMode => inline.predo.toList ::: predo } } diff --git a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala index 3634363c..843264f5 100644 --- a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala @@ -10,8 +10,11 @@ import aqua.raw.value.* import aqua.types.{ArrayType, ArrowType, BoxType, CanonStreamType, StreamType} import cats.syntax.traverse.* import cats.syntax.applicative.* +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} /** @@ -218,10 +221,10 @@ object TagInliner extends Logging { collectionToModel(c, Some(assignTo)) case v => valueToModel(v, false) - }).flatMap { cd => - for { - _ <- Exports[S].resolved(assignTo, cd._1) - } yield None -> cd._2 + }).flatMap { case (model, prefix) => + Exports[S] + .resolved(assignTo, model) + .as(None -> prefix) } case ClosureTag(arrow, detach) => diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala index f6cf2702..0007bd19 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/CallArrowRawInliner.scala @@ -1,7 +1,7 @@ package aqua.model.inline.raw import aqua.model.inline.Inline.parDesugarPrefixOpt -import aqua.model.{CallServiceModel, FuncArrow, SeqModel, ValueModel, VarModel} +import aqua.model.{CallServiceModel, FuncArrow, MetaModel, SeqModel, ValueModel, VarModel} import aqua.model.inline.{ArrowInliner, Inline, TagInliner} import aqua.model.inline.RawValueInliner.{callToModel, valueToModel} import aqua.model.inline.state.{Arrows, Exports, Mangler} @@ -48,7 +48,10 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging { } } - private def resolveFuncArrow[S: Mangler: Exports: Arrows](fn: FuncArrow, call: Call) = { + private def resolveFuncArrow[S: Mangler: Exports: Arrows]( + fn: FuncArrow, + call: Call + ): State[S, (List[ValueModel], Inline)] = { logger.trace(Console.YELLOW + s"Call arrow ${fn.funcName}" + Console.RESET) callToModel(call, false).flatMap { case (cm, p) => ArrowInliner @@ -56,13 +59,23 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging { .map { case (body, vars) => vars -> Inline( ListMap.empty, - Chain.one(SeqModel.wrap(p.toList :+ body: _*)) + Chain.one( + // Leave meta information in tree after inlining + MetaModel + .CallArrowModel(fn.funcName) + .wrap( + SeqModel.wrap(p.toList :+ body: _*) + ) + ) ) } } } - private def resolveArrow[S: Mangler: Exports: Arrows](funcName: String, call: Call) = + private def resolveArrow[S: Mangler: Exports: Arrows]( + funcName: String, + call: Call + ): State[S, (List[ValueModel], Inline)] = Arrows[S].arrows.flatMap(arrows => arrows.get(funcName) match { case Some(fn) => diff --git a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala index da4405e3..316aa2cd 100644 --- a/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/ArrowInlinerSpec.scala @@ -9,6 +9,8 @@ import cats.syntax.show.* import cats.data.{Chain, NonEmptyList, NonEmptyMap} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import aqua.raw.value.{CallArrowRaw, ValueRaw} +import aqua.raw.arrow.{ArrowRaw, FuncRaw} class ArrowInlinerSpec extends AnyFlatSpec with Matchers { @@ -33,7 +35,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( CallServiceModel( - LiteralModel("\"dumb_srv_id\"", LiteralType.string), + LiteralModel.quote("dumb_srv_id"), "dumb", CallModel(Nil, Nil) ).leaf @@ -113,14 +115,21 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( RestrictionModel(streamVar.name, true).wrap( - SeqModel.wrap( - CanonicalizeModel(streamModel, CallModel.Export(canonModel.name, canonModel.`type`)).leaf, - CallServiceModel( - LiteralModel("\"test-service\"", LiteralType.string), - "some-call", - CallModel(canonModel :: Nil, Nil) - ).leaf - ) + MetaModel + .CallArrowModel("cb") + .wrap( + SeqModel.wrap( + CanonicalizeModel( + streamModel, + CallModel.Export(canonModel.name, canonModel.`type`) + ).leaf, + CallServiceModel( + LiteralModel.quote("test-service"), + "some-call", + CallModel(canonModel :: Nil, Nil) + ).leaf + ) + ) ) ) should be(true) @@ -130,7 +139,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { func stream-callback(cb: string -> ()): records: *string cb(records!) - */ + */ ignore /*"arrow inliner"*/ should "pass stream with gate to callback properly" in { val streamType = StreamType(ScalarType.string) val streamVar = VarRaw("records", streamType) @@ -210,7 +219,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( RestrictionModel(streamVar.name, true).wrap( CallServiceModel( - LiteralModel("\"test-service\"", LiteralType.string), + LiteralModel.quote("test-service"), "some-call", CallModel(streamModel :: Nil, Nil) ).leaf @@ -291,15 +300,19 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( SeqModel.wrap( - CallServiceModel( - LiteralModel("\"test-service\"", LiteralType.string), - "get_records", - CallModel(Nil, CallModel.Export(recordsModel.name, recordsModel.`type`) :: Nil) - ).leaf, + MetaModel + .CallArrowModel(innerName) + .wrap( + CallServiceModel( + LiteralModel.quote("test-service"), + "get_records", + CallModel(Nil, CallModel.Export(recordsModel.name, recordsModel.`type`) :: Nil) + ).leaf + ), SeqModel.wrap( CanonicalizeModel(recordsModel, CallModel.Export(canonModel.name, canonType)).leaf, CallServiceModel( - LiteralModel("\"callbackSrv\"", LiteralType.string), + LiteralModel.quote("callbackSrv"), "response", CallModel(canonModel :: Nil, Nil) ).leaf @@ -309,6 +322,642 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { } + /** + * service Test("test-service"): + * get_number() -> u16 + * + * func inner() -> u16: + * res <- Test.get_number() + * <- res + * + * func outer() -> u16 + * res1 <- inner() -- Meta should be left here + * res2 <- Test.get_number() + * res3 <- inner() -- Meta should be left here + * retval = res1 + res2 + res3 + * <- retval + */ + "arrow inliner" should "leave meta after function inlining" in { + val innerName = "inner" + val innerRes = VarRaw("res", ScalarType.u16) + + val serviceName = "test-service" + val serviceMethod = "get_number" + + val serviceCall = (res: VarRaw) => + CallArrowRawTag + .service( + LiteralRaw.quote(serviceName), + serviceMethod, + Call(Nil, Call.Export(res.name, ScalarType.u16) :: Nil) + ) + .leaf + + val innerBody = SeqTag.wrap( + serviceCall(innerRes), + ReturnTag( + NonEmptyList.one( + innerRes + ) + ).leaf + ) + + val inner = FuncArrow( + funcName = innerName, + body = innerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(innerRes), + capturedArrows = Map.empty, + capturedValues = Map.empty, + capturedTopology = None + ) + + val outterRes1 = VarRaw("res1", ScalarType.u16) + val outterRes2 = VarRaw("res2", ScalarType.u16) + val outterRes3 = VarRaw("res3", ScalarType.u16) + val outterRetVal = VarRaw("retval", ScalarType.u16) + + val innerCall = (res: VarRaw) => + CallArrowRawTag + .func( + innerName, + Call(Nil, Call.Export(res.name, ScalarType.u16) :: Nil) + ) + .leaf + + val outerBody = SeqTag.wrap( + innerCall(outterRes1), + serviceCall(outterRes2), + innerCall(outterRes3), + AssignmentTag( + RawBuilder.add( + outterRes1, + RawBuilder.add( + outterRes2, + outterRes3 + ) + ), + outterRetVal.name + ).leaf, + ReturnTag( + NonEmptyList + .one( + outterRetVal + ) + ).leaf + ) + + val outer = FuncArrow( + funcName = "outer", + body = outerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(outterRetVal), + capturedArrows = Map(innerName -> inner), + capturedValues = Map.empty, + capturedTopology = None + ) + + val model = ArrowInliner + .callArrow[InliningState]( + outer, + CallModel(Nil, Nil) + ) + .runA(InliningState()) + .value + + val serviceCallModel = (res: VarModel) => + CallServiceModel( + LiteralModel.quote(serviceName), + serviceMethod, + CallModel(Nil, CallModel.Export(res.name, res.`type`) :: Nil) + ).leaf + + /* WARNING: This naming is unstable */ + val res1 = VarModel("res", ScalarType.u16) + val res2 = VarModel("res2", ScalarType.u16) + val res3 = VarModel("res-0", ScalarType.u16) + val tempAdd = VarModel("add-0", ScalarType.u16) + + val expected = SeqModel.wrap( + MetaModel + .CallArrowModel(innerName) + .wrap( + serviceCallModel(res1) + ), + serviceCallModel(res2), + MetaModel + .CallArrowModel(innerName) + .wrap( + serviceCallModel(res3) + ), + SeqModel.wrap( + ModelBuilder.add(res2, res3)(tempAdd).leaf, + ModelBuilder.add(res1, tempAdd)(VarModel("add", ScalarType.u16)).leaf + ) + ) + + model.equalsOrShowDiff(expected) shouldEqual true + } + + /** + * func inner() -> u16: + * res = 42 + * <- res + * + * func outer() -> u16: + * retval = inner() + inner() + 37 + * <- retval + */ + "arrow inliner" should "omit meta if arrow was completely erased" in { + val innerName = "inner" + val innerRes = VarRaw("res", ScalarType.u16) + val innerRet = "42" + + val innerBody = SeqTag.wrap( + AssignmentTag( + LiteralRaw(innerRet, ScalarType.u16), + innerRes.name + ).leaf, + ReturnTag( + NonEmptyList.one( + innerRes + ) + ).leaf + ) + + val inner = FuncArrow( + funcName = innerName, + body = innerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(innerRes), + capturedArrows = Map.empty, + capturedValues = Map.empty, + capturedTopology = None + ) + + val innerCall = CallArrowRaw( + ability = None, + name = innerName, + arguments = Nil, + baseType = ArrowType( + domain = NilType, + codomain = ProductType(List(ScalarType.u16)) + ), + serviceId = None + ) + + val outerAdd = "37" + val outterRetVal = VarRaw("retval", ScalarType.u16) + + val outerBody = SeqTag.wrap( + AssignmentTag( + RawBuilder.add( + innerCall, + RawBuilder.add( + innerCall, + LiteralRaw(outerAdd, ScalarType.u16) + ) + ), + outterRetVal.name + ).leaf, + ReturnTag( + NonEmptyList + .one( + outterRetVal + ) + ).leaf + ) + + val outer = FuncArrow( + funcName = "outer", + body = outerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(outterRetVal), + capturedArrows = Map(innerName -> inner), + capturedValues = Map.empty, + capturedTopology = None + ) + + val model = ArrowInliner + .callArrow[InliningState]( + outer, + CallModel(Nil, Nil) + ) + .runA(InliningState()) + .value + + /* WARNING: This naming is unstable */ + val tempAdd0 = VarModel("add-0", ScalarType.u16) + val tempAdd = VarModel("add", ScalarType.u16) + + val expected = SeqModel.wrap( + ModelBuilder + .add( + LiteralModel(innerRet, ScalarType.u16), + LiteralModel(outerAdd, ScalarType.u16) + )(tempAdd0) + .leaf, + ModelBuilder + .add( + LiteralModel(innerRet, ScalarType.u16), + tempAdd0 + )(tempAdd) + .leaf + ) + + model.equalsOrShowDiff(expected) shouldEqual true + } + + /** + * func inner(arg: u16) -> u16 -> u16: + * closure = (x: u16) -> u16: + * retval = x + arg + * <- retval + * <- closure + * + * func outer() -> u16: + * c <- inner(42) + * retval = 37 + c(1) + c(2) + * <- retval + */ + "arrow inliner" should "leave meta after returned closure inlining" in { + val innerName = "inner" + val closureName = "closure" + + val closureArg = VarRaw( + "x", + ScalarType.u16 + ) + val innerArg = VarRaw( + "arg", + ScalarType.u16 + ) + + val closureRes = VarRaw( + "retval", + ScalarType.u16 + ) + val closureType = ArrowType( + domain = ProductType(List(closureArg.`type`)), + codomain = ProductType(List(ScalarType.u16)) + ) + val closureTypeLablled = closureType.copy( + domain = ProductType.labelled(List(closureArg.name -> closureArg.`type`)) + ) + + val innerRes = VarRaw( + closureName, + closureTypeLablled + ) + val innerType = ArrowType( + domain = ProductType.labelled(List(innerArg.name -> innerArg.`type`)), + codomain = ProductType(List(closureType)) + ) + + val closureBody = SeqTag.wrap( + AssignmentTag( + RawBuilder.add( + closureArg, + innerArg + ), + closureRes.name + ).leaf, + ReturnTag( + NonEmptyList.one(closureRes) + ).leaf + ) + + val closureFunc = FuncRaw( + name = closureName, + arrow = ArrowRaw( + `type` = closureTypeLablled, + ret = List(closureRes), + body = closureBody + ) + ) + + val innerBody = SeqTag.wrap( + ClosureTag( + func = closureFunc, + detach = false + ).leaf, + ReturnTag( + NonEmptyList.one(innerRes) + ).leaf + ) + + val inner = FuncArrow( + funcName = innerName, + body = innerBody, + arrowType = innerType, + ret = List(innerRes), + capturedArrows = Map.empty, + capturedValues = Map.empty, + capturedTopology = None + ) + + val outterClosure = VarRaw( + "c", + closureType + ) + val outterRes = VarRaw( + "retval", + ScalarType.u16 + ) + + val innerCall = + CallArrowRawTag( + List(Call.Export(outterClosure.name, outterClosure.`type`)), + CallArrowRaw( + ability = None, + name = innerName, + arguments = List(LiteralRaw("42", LiteralType.number)), + baseType = innerType, + serviceId = None + ) + ).leaf + + val closureCall = (i: String) => + CallArrowRaw( + ability = None, + name = outterClosure.name, + arguments = List(LiteralRaw(i, LiteralType.number)), + baseType = closureType, + serviceId = None + ) + + val outerBody = SeqTag.wrap( + innerCall, + AssignmentTag( + RawBuilder.add( + RawBuilder.add( + LiteralRaw("37", LiteralType.number), + closureCall("1") + ), + closureCall("2") + ), + outterRes.name + ).leaf, + ReturnTag( + NonEmptyList + .one( + outterRes + ) + ).leaf + ) + + val outer = FuncArrow( + funcName = "outer", + body = outerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(outterRes), + capturedArrows = Map(innerName -> inner), + capturedValues = Map.empty, + capturedTopology = None + ) + + val model = ArrowInliner + .callArrow[InliningState]( + outer, + CallModel(Nil, Nil) + ) + .runA(InliningState()) + .value + + val closureCallModel = (x: String, o: VarModel) => + MetaModel + .CallArrowModel(closureName) + .wrap( + ApplyTopologyModel(closureName) + .wrap( + ModelBuilder + .add( + LiteralModel(x, LiteralType.number), + LiteralModel("42", LiteralType.number) + )(o) + .leaf + ) + ) + + /* WARNING: This naming is unstable */ + val tempAdd0 = VarModel("add-0", ScalarType.u16) + val tempAdd1 = VarModel("add-1", ScalarType.u16) + val tempAdd2 = VarModel("add-2", ScalarType.u16) + val tempAdd = VarModel("add", ScalarType.u16) + + val expected = SeqModel.wrap( + MetaModel + .CallArrowModel(innerName) + .wrap( + CaptureTopologyModel(closureName).leaf + ), + SeqModel.wrap( + ParModel.wrap( + SeqModel.wrap( + closureCallModel("1", tempAdd1), + ModelBuilder + .add( + LiteralModel("37", LiteralType.number), + tempAdd1 + )(tempAdd0) + .leaf + ), + closureCallModel("2", tempAdd2) + ), + ModelBuilder + .add( + tempAdd0, + tempAdd2 + )(tempAdd) + .leaf + ) + ) + + model.equalsOrShowDiff(expected) shouldEqual true + } + + /** + * func inner() -> () -> u16: + * closure = func () -> u16: + * <- 42 + * <- closure + * + * func outer() -> u16: + * c <- inner() + * retval = 37 + c() + c() + * <- retval + */ + "arrow inliner" should "omit meta if returned closure was completely erased" in { + val innerName = "inner" + val closureName = "closure" + + val closureRes = LiteralRaw( + "42", + LiteralType.number + ) + val closureType = ArrowType( + domain = NilType, + codomain = ProductType(List(ScalarType.u16)) + ) + + val innerRes = VarRaw( + closureName, + closureType + ) + val innerType = ArrowType( + domain = NilType, + codomain = ProductType(List(closureType)) + ) + + val closureBody = SeqTag.wrap( + ReturnTag( + NonEmptyList.one(closureRes) + ).leaf + ) + + val closureFunc = FuncRaw( + name = closureName, + arrow = ArrowRaw( + `type` = closureType, + ret = List(closureRes), + body = closureBody + ) + ) + + val innerBody = SeqTag.wrap( + ClosureTag( + func = closureFunc, + detach = true + ).leaf, + ReturnTag( + NonEmptyList.one(innerRes) + ).leaf + ) + + val inner = FuncArrow( + funcName = innerName, + body = innerBody, + arrowType = innerType, + ret = List(innerRes), + capturedArrows = Map.empty, + capturedValues = Map.empty, + capturedTopology = None + ) + + val outterClosure = VarRaw( + "c", + closureType + ) + val outterRes = VarRaw( + "retval", + ScalarType.u16 + ) + + val innerCall = + CallArrowRawTag( + List(Call.Export(outterClosure.name, outterClosure.`type`)), + CallArrowRaw( + ability = None, + name = innerName, + arguments = Nil, + baseType = innerType, + serviceId = None + ) + ).leaf + + val closureCall = + CallArrowRaw( + ability = None, + name = outterClosure.name, + arguments = Nil, + baseType = closureType, + serviceId = None + ) + + val outerBody = SeqTag.wrap( + innerCall, + AssignmentTag( + RawBuilder.add( + RawBuilder.add( + LiteralRaw("37", LiteralType.number), + closureCall + ), + closureCall + ), + outterRes.name + ).leaf, + ReturnTag( + NonEmptyList + .one( + outterRes + ) + ).leaf + ) + + val outer = FuncArrow( + funcName = "outer", + body = outerBody, + arrowType = ArrowType( + ProductType(Nil), + ProductType(List(ScalarType.u16)) + ), + ret = List(outterRes), + capturedArrows = Map(innerName -> inner), + capturedValues = Map.empty, + capturedTopology = None + ) + + val model = ArrowInliner + .callArrow[InliningState]( + outer, + CallModel(Nil, Nil) + ) + .runA(InliningState()) + .value + + /* WARNING: This naming is unstable */ + val tempAdd0 = VarModel("add-0", ScalarType.u16) + val tempAdd = VarModel("add", ScalarType.u16) + + val number = (v: String) => + LiteralModel( + v, + LiteralType.number + ) + + val expected = SeqModel.wrap( + ModelBuilder + .add( + number("37"), + number("42") + )(tempAdd0) + .leaf, + ModelBuilder + .add( + tempAdd0, + number("42") + )(tempAdd) + .leaf + ) + + model.equalsOrShowDiff(expected) shouldEqual true + } + /* data Prod: value: string @@ -322,6 +971,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { OpHa.identity(v) */ "arrow inliner" should "hold lambda" in { + val innerName = "inner" // lambda that will be assigned to another variable val objectVarLambda = @@ -348,7 +998,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { // function where we assign object lambda to value and call service val inner = FuncArrow( - "inner", + innerName, SeqTag.wrap( AssignmentTag( objectVarLambda, @@ -399,18 +1049,22 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( SeqModel.wrap( CallServiceModel( - LiteralModel("\"getSrv\"", LiteralType.string), + LiteralModel.quote("getSrv"), "getObj", CallModel(Nil, CallModel.Export(objectVar.name, objectVar.`type`) :: Nil) ).leaf, - SeqModel.wrap( - FlattenModel(ValueModel.fromRaw(objectVarLambda), flattenObject.name).leaf, - CallServiceModel( - LiteralModel("\"callbackSrv\"", LiteralType.string), - "response", - CallModel(ValueModel.fromRaw(flattenObject) :: Nil, Nil) - ).leaf - ) + MetaModel + .CallArrowModel(innerName) + .wrap( + SeqModel.wrap( + FlattenModel(ValueModel.fromRaw(objectVarLambda), flattenObject.name).leaf, + CallServiceModel( + LiteralModel.quote("callbackSrv"), + "response", + CallModel(ValueModel.fromRaw(flattenObject) :: Nil, Nil) + ).leaf + ) + ) ) ) should be(true) @@ -421,6 +1075,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { join nodes[idx] */ "arrow inliner" should "not rename value in index array lambda" in { + val innerName = "inner" // lambda that will be assigned to another variable val argArray = VarRaw( @@ -453,7 +1108,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { // function where we assign object lambda to value and call service val inner = FuncArrow( - "inner", + innerName, JoinTag(NonEmptyList.one(arrIdx)).leaf, ArrowType( ProductType.labelled( @@ -495,12 +1150,12 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers { model.equalsOrShowDiff( SeqModel.wrap( CallServiceModel( - LiteralModel("\"getSrv\"", LiteralType.string), + LiteralModel.quote("getSrv"), "getArr", CallModel(Nil, CallModel.Export(argArray.name, argArray.`type`) :: Nil) ).leaf, CallServiceModel( - LiteralModel("\"getSrv\"", LiteralType.string), + LiteralModel.quote("getSrv"), "getIdx", CallModel(Nil, CallModel.Export(idxVar.name, idxVar.`type`) :: Nil) ).leaf diff --git a/model/inline/src/test/scala/aqua/model/inline/CopyInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/CopyInlinerSpec.scala index 1779f755..bdfd805b 100644 --- a/model/inline/src/test/scala/aqua/model/inline/CopyInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/CopyInlinerSpec.scala @@ -24,7 +24,13 @@ class CopyInlinerSpec extends AnyFlatSpec with Matchers { val length = FunctorRaw("length", ScalarType.u32) val lengthValue = VarRaw("l", arrType).withProperty(length) - val getField = CallArrowRaw(None, "get_field", Nil, ArrowType(NilType, UnlabeledConsType(ScalarType.string, NilType)), Option(LiteralRaw("\"serv\"", ScalarType.string))) + val getField = CallArrowRaw( + None, + "get_field", + Nil, + ArrowType(NilType, UnlabeledConsType(ScalarType.string, NilType)), + Option(LiteralRaw.quote("serv")) + ) val copyRaw = IntoCopyRaw(structType, NonEmptyMap.of("field1" -> lengthValue, "field2" -> getField)) @@ -44,7 +50,7 @@ class CopyInlinerSpec extends AnyFlatSpec with Matchers { ParModel.wrap( SeqModel.wrap( FlattenModel(VarModel("l", arrType), "l_to_functor").leaf, - FlattenModel(VarModel("l_to_functor", arrType, Chain.one(lengthModel)), "l_length").leaf, + FlattenModel(VarModel("l_to_functor", arrType, Chain.one(lengthModel)), "l_length").leaf ), CallServiceModel( "serv", diff --git a/model/inline/src/test/scala/aqua/model/inline/MakeStructInlinerSpec.scala b/model/inline/src/test/scala/aqua/model/inline/MakeStructInlinerSpec.scala index 19bc926c..dd02e7ee 100644 --- a/model/inline/src/test/scala/aqua/model/inline/MakeStructInlinerSpec.scala +++ b/model/inline/src/test/scala/aqua/model/inline/MakeStructInlinerSpec.scala @@ -29,7 +29,7 @@ class MakeStructInlinerSpec extends AnyFlatSpec with Matchers { "get_field", Nil, ArrowType(NilType, UnlabeledConsType(ScalarType.string, NilType)), - Option(LiteralRaw("\"serv\"", ScalarType.string)) + Option(LiteralRaw.quote("serv")) ) val makeStruct = diff --git a/model/inline/src/test/scala/aqua/model/inline/ModelBuilder.scala b/model/inline/src/test/scala/aqua/model/inline/ModelBuilder.scala new file mode 100644 index 00000000..2d147c24 --- /dev/null +++ b/model/inline/src/test/scala/aqua/model/inline/ModelBuilder.scala @@ -0,0 +1,14 @@ +package aqua.model.inline + +import aqua.model.{CallModel, CallServiceModel, LiteralModel, ValueModel, VarModel} + +object ModelBuilder { + + def add(l: ValueModel, r: ValueModel)(o: VarModel): CallServiceModel = + CallServiceModel( + serviceId = "math", + funcName = "add", + args = List(l, r), + result = o + ) +} diff --git a/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala b/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala new file mode 100644 index 00000000..a5de04df --- /dev/null +++ b/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala @@ -0,0 +1,21 @@ +package aqua.model.inline + +import aqua.raw.value.{CallArrowRaw, LiteralRaw, ValueRaw} +import aqua.types.{ArrowType, ProductType, ScalarType} + +object RawBuilder { + + def add(l: ValueRaw, r: ValueRaw): ValueRaw = + CallArrowRaw( + ability = Some("math"), + name = "add", + arguments = List(l, r), + baseType = ArrowType( + ProductType(List(ScalarType.i64, ScalarType.i64)), + ProductType( + List(l.`type` `∪` r.`type`) + ) + ), + serviceId = Some(LiteralRaw.quote("math")) + ) +} diff --git a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala index 190a1805..ebd50058 100644 --- a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala @@ -172,7 +172,8 @@ case class CollectionRaw(values: NonEmptyList[ValueRaw], boxType: BoxType) exten copy(values = values.map(_.renameVars(map))) } -case class MakeStructRaw(fields: NonEmptyMap[String, ValueRaw], structType: StructType) extends ValueRaw { +case class MakeStructRaw(fields: NonEmptyMap[String, ValueRaw], structType: StructType) + extends ValueRaw { override def baseType: Type = structType diff --git a/model/src/main/scala/aqua/model/AquaContext.scala b/model/src/main/scala/aqua/model/AquaContext.scala index cd9632ef..6de18da7 100644 --- a/model/src/main/scala/aqua/model/AquaContext.scala +++ b/model/src/main/scala/aqua/model/AquaContext.scala @@ -1,7 +1,7 @@ package aqua.model import aqua.raw.arrow.FuncRaw -import aqua.raw.ops.{CallArrowRawTag, FuncOp} +import aqua.raw.ops.CallArrowRawTag import aqua.raw.value.ValueRaw import aqua.raw.value.CallArrowRaw import aqua.raw.{ConstantRaw, RawContext, RawPart, ServiceRaw, TypeRaw} diff --git a/model/src/main/scala/aqua/model/FuncArrow.scala b/model/src/main/scala/aqua/model/FuncArrow.scala index 31c0c866..76772978 100644 --- a/model/src/main/scala/aqua/model/FuncArrow.scala +++ b/model/src/main/scala/aqua/model/FuncArrow.scala @@ -2,7 +2,7 @@ package aqua.model import aqua.raw.Raw import aqua.raw.arrow.FuncRaw -import aqua.raw.ops.{FuncOp, RawTag} +import aqua.raw.ops.RawTag import aqua.raw.value.ValueRaw import aqua.types.{ArrowType, Type} diff --git a/model/src/main/scala/aqua/model/OpModel.scala b/model/src/main/scala/aqua/model/OpModel.scala index f2a5ffcd..b3093b4a 100644 --- a/model/src/main/scala/aqua/model/OpModel.scala +++ b/model/src/main/scala/aqua/model/OpModel.scala @@ -39,6 +39,26 @@ object OpModel extends TreeNodeCompanion[OpModel] { } } +/** + * Meta information embedded in a tree + */ +enum MetaModel extends OpModel { + + /** + * Wraps subtree that was produced after inlining arrow + * + * @param name Name of arrow inlined + */ + case CallArrowModel(name: String) + + override def wrap(children: Tree*): Tree = + // NOTE: Consider leaving some meta info if call is completely erased? + children.filter(_.head != EmptyModel) match { + case Nil => EmptyModel.leaf + case filtered => super.wrap(filtered: _*) + } +} + sealed trait NoExecModel extends OpModel sealed trait ForceExecModel extends OpModel @@ -95,9 +115,14 @@ case class MatchMismatchModel(left: ValueModel, right: ValueModel, shouldMatch: left.usesVarNames ++ right.usesVarNames } -case class ForModel(item: String, iterable: ValueModel, mode: Option[ForModel.Mode] = Some(ForModel.NullMode)) extends SeqGroupModel { +case class ForModel( + item: String, + iterable: ValueModel, + mode: Option[ForModel.Mode] = Some(ForModel.NullMode) +) extends SeqGroupModel { - override def toString: String = s"for $item <- $iterable${mode.map(m => " " + m.toString).getOrElse("")}" + override def toString: String = + s"for $item <- $iterable${mode.map(m => " " + m.toString).getOrElse("")}" override def restrictsVarNames: Set[String] = Set(item) @@ -142,9 +167,15 @@ case class CallServiceModel(serviceId: ValueModel, funcName: String, call: CallM } object CallServiceModel { - def apply(serviceId: String, funcName: String, args: List[ValueModel], result: VarModel): CallServiceModel = + + def apply( + serviceId: String, + funcName: String, + args: List[ValueModel], + result: VarModel + ): CallServiceModel = CallServiceModel( - LiteralModel(s"\"$serviceId\"", ScalarType.string), + LiteralModel.quote(serviceId), funcName, CallModel( args, diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index 137c3023..c15af153 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -47,6 +47,8 @@ case class LiteralModel(value: String, `type`: Type) extends ValueModel { object LiteralModel { def fromRaw(raw: LiteralRaw): LiteralModel = LiteralModel(raw.value, raw.baseType) + + def quote(str: String): LiteralModel = LiteralModel(s"\"$str\"", LiteralType.string) } sealed trait PropertyModel { diff --git a/model/transform/src/main/scala/aqua/model/transform/Transform.scala b/model/transform/src/main/scala/aqua/model/transform/Transform.scala index b1f9e5c6..b717dcd0 100644 --- a/model/transform/src/main/scala/aqua/model/transform/Transform.scala +++ b/model/transform/src/main/scala/aqua/model/transform/Transform.scala @@ -16,7 +16,9 @@ import aqua.types.ScalarType import cats.Eval import cats.data.Chain import cats.free.Cofree +import cats.syntax.option.* import scribe.Logging +import aqua.model.transform.TransformConfig.TracingConfig // API for transforming RawTag to Res object Transform extends Logging { @@ -87,6 +89,11 @@ object Transform extends Logging { callable = initCallable ) + val tracing = Tracing( + enabledConfig = conf.tracing, + initCallable = initCallable + ) + val argsProvider: ArgsProvider = ArgsFromService( dataServiceId = conf.dataSrvId, names = relayVar.toList ::: func.arrowType.domain.labelledData @@ -110,9 +117,10 @@ object Transform extends Logging { // Pre transform and inline the function model <- funcToModelTree(func, preTransformer) // Post transform the function - postModel = errorsCatcher.transform(model) + errorsModel = errorsCatcher.transform(model) + tracingModel <- tracing(errorsModel) // Resolve topology - resolved <- Topology.resolve(postModel) + resolved <- Topology.resolve(tracingModel) // Clear the tree result = clear(resolved) } yield FuncRes( diff --git a/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala b/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala index 3fd2eabb..506c9954 100644 --- a/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala +++ b/model/transform/src/main/scala/aqua/model/transform/TransformConfig.scala @@ -16,6 +16,7 @@ case class TransformConfig( respFuncName: String = "response", relayVarName: Option[String] = Some("-relay-"), wrapWithXor: Boolean = true, + tracing: Option[TransformConfig.TracingConfig] = None, constants: List[ConstantRaw] = Nil ) { @@ -29,3 +30,15 @@ case class TransformConfig( val constantsList: List[ConstantRaw] = ConstantRaw.defaultConstants(relayVarName) ::: constants } + +object TransformConfig { + + final case class TracingConfig( + serviceId: String = "tracingSrv", + serviceFuncName: String = "tracingEvent" + ) + + object TracingConfig { + lazy val default = TracingConfig() + } +} diff --git a/model/transform/src/main/scala/aqua/model/transform/funcop/OpTransform.scala b/model/transform/src/main/scala/aqua/model/transform/funcop/OpTransform.scala new file mode 100644 index 00000000..0e108db5 --- /dev/null +++ b/model/transform/src/main/scala/aqua/model/transform/funcop/OpTransform.scala @@ -0,0 +1,34 @@ +package aqua.model.transform.funcop + +import aqua.model.OpModel + +import cats.data.Chain +import cats.free.Cofree +import cats.Eval + +/** + * Base type for [[OpModel.Tree]] -> [[OpModel.Tree]] transformation + */ +trait OpTransform { + + /** + * Transformation step + * (node, child results) => node result + */ + def folder: OpTransform.OpFolder + + def apply(tree: OpModel.Tree): Eval[OpModel.Tree] = + Cofree.cata[Chain, OpModel, OpModel.Tree](tree)((op, children) => + folder + .lift(op, children) + .getOrElse( + Eval.now( + op.wrap(children.toList: _*) + ) + ) + ) +} + +object OpTransform { + type OpFolder = PartialFunction[(OpModel, Chain[OpModel.Tree]), Eval[OpModel.Tree]] +} diff --git a/model/transform/src/main/scala/aqua/model/transform/funcop/Tracing.scala b/model/transform/src/main/scala/aqua/model/transform/funcop/Tracing.scala new file mode 100644 index 00000000..9e066e89 --- /dev/null +++ b/model/transform/src/main/scala/aqua/model/transform/funcop/Tracing.scala @@ -0,0 +1,86 @@ +package aqua.model.transform.funcop + +import cats.data.Chain + +import cats.Eval + +import aqua.model.{ + CallModel, + CallServiceModel, + LiteralModel, + MetaModel, + OpModel, + SeqModel, + ValueModel +} +import aqua.model.transform.pre.InitPeerCallable +import aqua.model.ParModel +import aqua.model.DetachModel +import aqua.model.transform.TransformConfig.TracingConfig + +final case class Tracing( + enabledConfig: Option[TracingConfig], + initCallable: InitPeerCallable +) extends OpTransform { + import Tracing.* + + private def getChild(children: Chain[OpModel.Tree]): OpModel.Tree = + children.headOption + .filter(_ => children.length == 1) + .getOrElse( + SeqModel.wrap(children.toList: _*) + ) + + override def folder: OpTransform.OpFolder = { + case (MetaModel.CallArrowModel(arrowName), children) => + val child = getChild(children) + + Eval.now( + enabledConfig + .map(traceCallModel(_, arrowName)) + .fold(child)(traceCall => + /* seq: + detach: call tracing enter + + detach: call tracing exit */ + SeqModel.wrap( + DetachModel.wrap( + initCallable.onInitPeer.wrap( + traceCall(Event.Enter) + ) + ), + child, + DetachModel.wrap( + initCallable.onInitPeer.wrap( + traceCall(Event.Exit) + ) + ) + ) + ) + ) + } +} + +object Tracing { + + enum Event { + case Enter, Exit + + def toArg: ValueModel = LiteralModel.quote(this match { + case Enter => "enter" + case Exit => "exit" + }) + } + + def traceCallModel(config: TracingConfig, arrowName: String)( + event: Event + ): OpModel.Tree = + CallServiceModel( + LiteralModel.quote(config.serviceId), + config.serviceFuncName, + CallModel( + args = List(LiteralModel.quote(arrowName), event.toArg), + exportTo = Nil + ) + ).leaf +} diff --git a/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala b/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala index 73ee8bcf..c8bad469 100644 --- a/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala +++ b/model/transform/src/main/scala/aqua/model/transform/pre/FuncPreTransformer.scala @@ -2,7 +2,7 @@ package aqua.model.transform.pre import aqua.model.FuncArrow import aqua.model.ArgsCall -import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, RawTag, SeqTag} +import aqua.raw.ops.{Call, CallArrowRawTag, RawTag, SeqTag} import aqua.raw.value.{ValueRaw, VarRaw} import aqua.types.* import cats.syntax.show.* diff --git a/model/tree/src/main/scala/aqua/tree/TreeNodeCompanion.scala b/model/tree/src/main/scala/aqua/tree/TreeNodeCompanion.scala index b487cc97..0187de8f 100644 --- a/model/tree/src/main/scala/aqua/tree/TreeNodeCompanion.scala +++ b/model/tree/src/main/scala/aqua/tree/TreeNodeCompanion.scala @@ -16,16 +16,16 @@ trait TreeNodeCompanion[T <: TreeNode[T]] { type Tree = Cofree[Chain, T] private def showOffset(what: Tree, offset: Int): String = { - val spaces = " " * offset + val spaces = "| " * offset spaces + what.head.show + what.tail.map { case ch if ch.nonEmpty => - " :\n" + ch.toList.map(showOffset(_, offset + 1)).mkString("") + "\n" + " :\n" + ch.toList.map(showOffset(_, offset + 1)).mkString("") case ch => "\n" }.value } private def showDiffOffset(what: (Tree, Tree), offset: Int): String = { - val spaces = " " * offset + val spaces = "| " * offset val head = if (what._1.head == what._2.head) what._1.head.show else { diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index a60f7ecf..ba10b1f1 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -23,6 +23,7 @@ import cats.Reducible import cats.data.Validated.{Invalid, Valid} import cats.kernel.Monoid import cats.syntax.applicative.* +import cats.syntax.option.* import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* @@ -59,9 +60,7 @@ class RawSemantics[S[_]](implicit p: Picker[RawContext]) extends Semantics[S, Ra .map { case (state, ctx) => NonEmptyChain .fromChain(state.errors) - .fold[ValidatedNec[SemanticError[S], RawContext]]( - Valid(ctx) - )(Invalid(_)) + .toInvalid(ctx) } // TODO: return as Eval .value diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala index f637b173..3e62b52a 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr.func import aqua.raw.Raw -import aqua.raw.ops.{AbilityIdTag, FuncOp} +import aqua.raw.ops.AbilityIdTag import aqua.parser.expr.func.AbilityIdExpr import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala index 949fa450..91976379 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala @@ -133,13 +133,15 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal { ( SeqTag.wrap( - bodyAcc :: CanonicalizeTag( + bodyAcc, + CanonicalizeTag( VarRaw(streamName, streamType), Call.Export(canonReturnVar.name, canonReturnVar.`type`) - ).leaf :: FlattenTag( + ).leaf, + FlattenTag( canonReturnVar, returnVar.name - ).leaf :: Nil: _* + ).leaf ), returnAcc :+ returnVar, idx + 1 diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/AssignmentSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/AssignmentSem.scala index d903c79e..2186926b 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/AssignmentSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/AssignmentSem.scala @@ -3,7 +3,7 @@ package aqua.semantics.expr.func import aqua.raw.Raw import aqua.types.ArrowType import aqua.raw.value.CallArrowRaw -import aqua.raw.ops.{AssignmentTag, ClosureTag, FuncOp} +import aqua.raw.ops.{AssignmentTag, ClosureTag} import aqua.parser.expr.func.AssignmentExpr import aqua.raw.arrow.FuncRaw import aqua.semantics.Prog diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/CallArrowSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/CallArrowSem.scala index 863a2dd4..5d8fa4e8 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/CallArrowSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/CallArrowSem.scala @@ -16,34 +16,42 @@ import cats.syntax.flatMap.* import cats.syntax.functor.* import cats.syntax.traverse.* import cats.{Monad, Traverse} +import aqua.raw.value.CallArrowRaw class CallArrowSem[S[_]](val expr: CallArrowExpr[S]) extends AnyVal { import expr.* + private def getExports[Alg[_]: Monad](callArrow: CallArrowRaw)(implicit + N: NamesAlgebra[S, Alg], + T: TypesAlgebra[S, Alg] + ): Alg[List[Call.Export]] = + (variables zip callArrow.baseType.codomain.toList).traverse { case (v, t) => + N.read(v, mustBeDefined = false).flatMap { + case Some(stream @ StreamType(st)) => + T.ensureTypeMatches(v, st, t).as(Call.Export(v.value, stream)) + case _ => + N.define(v, t).as(Call.Export(v.value, t)) + } + } + private def toModel[Alg[_]: Monad](implicit N: NamesAlgebra[S, Alg], A: AbilitiesAlgebra[S, Alg], T: TypesAlgebra[S, Alg], V: ValuesAlgebra[S, Alg] - ): Alg[Option[FuncOp]] = V.callArrowToRaw(callArrow).flatMap { - case None => None.pure[Alg] - case Some(car) => + ): Alg[Option[FuncOp]] = for { + callArrowRaw <- V.callArrowToRaw(callArrow) + maybeOp <- callArrowRaw.traverse(car => variables .drop(car.baseType.codomain.length) .headOption - .fold( - (variables zip car.baseType.codomain.toList).traverse { case (v, t) => - N.read(v, mustBeDefined = false).flatMap { - case Some(stream @ StreamType(st)) => - T.ensureTypeMatches(v, st, t).as(Call.Export(v.value, stream)) - case _ => - N.define(v, t).as(Call.Export(v.value, t)) - } - } - )(T.expectNoExport(_).as(Nil)) - .map(maybeExport => Some(CallArrowRawTag(maybeExport, car).funcOpLeaf)) - } + .fold(getExports(car))( + T.expectNoExport(_).as(Nil) + ) + .map(maybeExports => CallArrowRawTag(maybeExports, car).funcOpLeaf) + ) + } yield maybeOp def program[Alg[_]: Monad](implicit N: NamesAlgebra[S, Alg], diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala index f9847ba6..60d23278 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ClosureSem.scala @@ -1,7 +1,7 @@ package aqua.semantics.expr.func import aqua.raw.Raw -import aqua.raw.ops.{ClosureTag, FuncOp} +import aqua.raw.ops.ClosureTag import aqua.raw.arrow.{ArrowRaw, FuncRaw} import aqua.parser.expr.FuncExpr import aqua.parser.expr.func.ClosureExpr diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/DeclareStreamSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/DeclareStreamSem.scala index 4832f4f0..f3db63db 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/DeclareStreamSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/DeclareStreamSem.scala @@ -1,6 +1,6 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{DeclareStreamTag, FuncOp} +import aqua.raw.ops.DeclareStreamTag import aqua.parser.expr.func.DeclareStreamExpr import aqua.raw.Raw import aqua.raw.value.VarRaw diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/JoinSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/JoinSem.scala index 6747b413..aa94e077 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/JoinSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/JoinSem.scala @@ -2,7 +2,7 @@ package aqua.semantics.expr.func import aqua.parser.expr.func.JoinExpr import aqua.raw.Raw -import aqua.raw.ops.{FuncOp, JoinTag} +import aqua.raw.ops.JoinTag import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.types.TypesAlgebra diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/PushToStreamSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/PushToStreamSem.scala index a6d01ad6..be8eb3df 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/PushToStreamSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/PushToStreamSem.scala @@ -1,6 +1,6 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{Call, FuncOp, PushToStreamTag} +import aqua.raw.ops.{Call, PushToStreamTag} import aqua.parser.expr.func.PushToStreamExpr import aqua.parser.lexer.Token import aqua.raw.Raw diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ReturnSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ReturnSem.scala index 1fe2b3fe..6428a288 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ReturnSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ReturnSem.scala @@ -1,6 +1,6 @@ package aqua.semantics.expr.func -import aqua.raw.ops.{FuncOp, ReturnTag} +import aqua.raw.ops.ReturnTag import aqua.parser.expr.func.ReturnExpr import aqua.raw.Raw import aqua.semantics.Prog diff --git a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala index 55a66a85..e3c25ccd 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala @@ -14,6 +14,7 @@ import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* import cats.syntax.traverse.* +import cats.syntax.option.* import cats.instances.list.* import cats.data.{NonEmptyList, NonEmptyMap} @@ -209,18 +210,18 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit } - def callArrowToRaw(ca: CallArrowToken[S]): Alg[Option[CallArrowRaw]] = - ca.ability + def callArrowToRaw(ca: CallArrowToken[S]): Alg[Option[CallArrowRaw]] = for { + raw <- ca.ability .fold( N.readArrow(ca.funcName) .map( - _.map( + _.map(bt => CallArrowRaw( - None, - ca.funcName.value, - Nil, - _, - None + ability = None, + name = ca.funcName.value, + arguments = Nil, + baseType = bt, + serviceId = None ) ) ) @@ -228,49 +229,48 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](implicit (A.getArrow(ab, ca.funcName), A.getServiceId(ab)).mapN { case (Some(at), Right(sid)) => // Service call, actually - Some( - CallArrowRaw( - Some(ab.value), - ca.funcName.value, - Nil, - at, - Some(sid) - ) - ) + CallArrowRaw( + ability = Some(ab.value), + name = ca.funcName.value, + arguments = Nil, + baseType = at, + serviceId = Some(sid) + ).some case (Some(at), Left(true)) => // Ability function call, actually - Some( - CallArrowRaw( - Some(ab.value), - ca.funcName.value, - Nil, - at, - None - ) - ) - case _ => None + CallArrowRaw( + ability = Some(ab.value), + name = ca.funcName.value, + arguments = Nil, + baseType = at, + serviceId = None + ).some + case _ => none } ) - .flatMap { - case Some(r) => - val arr = r.baseType - T.checkArgumentsNumber(ca.funcName, arr.domain.length, ca.args.length).flatMap { - case false => Option.empty[CallArrowRaw].pure[Alg] - case true => - (ca.args zip arr.domain.toList).traverse { case (tkn, tp) => - valueToRaw(tkn).flatMap { - case Some(v) => T.ensureTypeMatches(tkn, tp, v.`type`).map(Option.when(_)(v)) - case None => None.pure[Alg] - } - }.map(_.toList.flatten).map { - case args: List[ValueRaw] if args.length == arr.domain.length => - Some(r.copy(arguments = args)) - case _ => None - } - } - - case None => Option.empty[CallArrowRaw].pure[Alg] - } + result <- raw.flatTraverse(r => + val arr = r.baseType + for { + argsCheck <- T.checkArgumentsNumber(ca.funcName, arr.domain.length, ca.args.length) + args <- Option + .when(argsCheck)(ca.args zip arr.domain.toList) + .traverse( + _.flatTraverse { case (tkn, tp) => + for { + maybeValueRaw <- valueToRaw(tkn) + checked <- maybeValueRaw.flatTraverse(v => + T.ensureTypeMatches(tkn, tp, v.`type`) + .map(Option.when(_)(v)) + ) + } yield checked.toList + } + ) + result = args + .filter(_.length == arr.domain.length) + .map(args => r.copy(arguments = args)) + } yield result + ) + } yield result def checkArguments(token: Token[S], arr: ArrowType, args: List[ValueToken[S]]): Alg[Boolean] = // TODO: do we really need to check this? diff --git a/types/src/main/scala/aqua/types/Type.scala b/types/src/main/scala/aqua/types/Type.scala index 08dce354..49b056e7 100644 --- a/types/src/main/scala/aqua/types/Type.scala +++ b/types/src/main/scala/aqua/types/Type.scala @@ -169,7 +169,7 @@ object ScalarType { } case class LiteralType private (oneOf: Set[ScalarType], name: String) extends DataType { - override def toString: String = name + override def toString: String = s"$name:lt" } object LiteralType {