diff --git a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala index 5be419d6..382d9dbf 100644 --- a/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala +++ b/compiler/src/main/scala/aqua/compiler/AquaCompiler.scala @@ -10,6 +10,7 @@ import aqua.raw.{RawContext, RawPart} import aqua.res.AquaRes import aqua.semantics.{CompilerState, Semantics} import aqua.semantics.header.{HeaderHandler, HeaderSem, Picker} + import cats.data.* import cats.data.Validated.{validNec, Invalid, Valid} import cats.parse.Parser0 diff --git a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala index e74da781..dccf940f 100644 --- a/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala +++ b/compiler/src/test/scala/aqua/compiler/AquaCompilerSpec.scala @@ -153,6 +153,7 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers { val canonResult = VarModel("-" + results.name + "-fix-0", CanonStreamType(resultsType.element)) val flatResult = VarModel("-results-flat-0", ArrayType(ScalarType.string)) val initPeer = LiteralModel.fromRaw(ValueRaw.InitPeerId) + val retVar = VarModel("ret", ScalarType.string) val expected = SeqRes.wrap( @@ -173,10 +174,11 @@ class AquaCompilerSpec extends AnyFlatSpec with Matchers { "identity", CallRes( LiteralModel.fromRaw(LiteralRaw.quote("hahahahah")) :: Nil, - Some(CallModel.Export(results.name, results.`type`)) + Some(CallModel.Export(retVar.name, retVar.`type`)) ), peer ).leaf, + ApRes(retVar, CallModel.Export(results.name, results.`type`)).leaf, through(ValueModel.fromRaw(relay)), through(initPeer) ), diff --git a/integration-tests/aqua/examples/servicesAsAbilities.aqua b/integration-tests/aqua/examples/servicesAsAbilities.aqua new file mode 100644 index 00000000..651da77e --- /dev/null +++ b/integration-tests/aqua/examples/servicesAsAbilities.aqua @@ -0,0 +1,95 @@ +aqua Test + +export test, testCapture, TestService + +service TestService("default-id"): + getId() -> string + concatId(s: string) -> string + +ability MatchingAbility: + getId() -> string + concatId(s: string) -> string + +func acceptClosure(closure: string -> string, arg: string) -> string: + <- closure(arg) + +func acceptAbility{MatchingAbility}(arg: string) -> string: + <- MatchingAbility.concatId(arg) + +func test() -> []string: + result: *string + + -- Test service + result <- TestService.concatId("call") + capture = TestService.concatId + result <- capture("capture call") + result <- acceptClosure(TestService.concatId, "accept closure call") + result <- acceptAbility{TestService}("accept ability call") + + -- Test renamed service + Renamed = TestService + result <- Renamed.concatId("call") + captureRenamed = Renamed.concatId + result <- captureRenamed("capture call") + result <- acceptClosure(Renamed.concatId, "accept closure call") + result <- acceptAbility{Renamed}("accept ability call") + + -- Test resolved service + TestService "resolved-id-1" + result <- TestService.concatId("call") + captureResolved = TestService.concatId + result <- captureResolved("capture call") + result <- acceptClosure(TestService.concatId, "accept closure call") + result <- acceptAbility{TestService}("accept ability call") + + -- Test renamed resolved service + Renamed1 = TestService + result <- Renamed1.concatId("call") + captureRenamed1 = Renamed1.concatId + result <- captureRenamed1("capture call") + result <- acceptClosure(Renamed1.concatId, "accept closure call") + result <- acceptAbility{Renamed1}("accept ability call") + + -- Test renamed service again (should save id) + result <- Renamed.concatId("call") + captureRenamedAgain = Renamed.concatId + result <- captureRenamedAgain("capture call") + result <- acceptClosure(Renamed.concatId, "accept closure call") + result <- acceptAbility{Renamed}("accept ability call") + + -- Test resolved in scope service + for i <- ["iter-id-1", "iter-id-2"]: + TestService i + RenamedI = TestService + result <- RenamedI.concatId("call") + captureI = RenamedI.concatId + result <- captureI("capture call") + result <- acceptClosure(RenamedI.concatId, "accept closure call") + result <- acceptAbility{RenamedI}("accept ability call") + + -- Test resolved service again (should save id) + result <- TestService.concatId("call") + captureAgain = TestService.concatId + result <- captureAgain("capture call") + result <- acceptClosure(TestService.concatId, "accept closure call") + result <- acceptAbility{TestService}("accept ability call") + + -- Test re resolved service in same scope + TestService "resolved-id-2" + result <- TestService.concatId("call") + captureReResolved = TestService.concatId + result <- captureReResolved("capture call") + result <- acceptClosure(TestService.concatId, "accept closure call") + result <- acceptAbility{TestService}("accept ability call") + + <- result + +func callCapture{MatchingAbility}() -> string, string: + TestService "resolved-id-in-capture" + res1 <- TestService.concatId("in capture") + res2 <- MatchingAbility.concatId("in capture") + <- res1, res2 + +func testCapture() -> string, string: + res1, res2 <- callCapture{TestService}() + <- res1, res2 \ No newline at end of file diff --git a/integration-tests/src/__test__/examples.spec.ts b/integration-tests/src/__test__/examples.spec.ts index c5119a33..105e3ab3 100644 --- a/integration-tests/src/__test__/examples.spec.ts +++ b/integration-tests/src/__test__/examples.spec.ts @@ -1,76 +1,144 @@ -import { Fluence, IFluenceClient, createClient } from '@fluencelabs/js-client'; -import { getObjAssignCall, getObjCall, getObjRelayCall } from '../examples/objectCall.js'; -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'; -import { foldBug499Call, foldCall } from '../examples/foldCall.js'; -import { bugNG69Call, ifCall, ifWrapCall } from '../examples/ifCall.js'; -import { parCall, testTimeoutCall } from '../examples/parCall.js'; -import { complexCall } from '../examples/complex.js'; -import { constantsCall, particleTtlAndTimestampCall } from '../examples/constantsCall.js'; -import { abilityCall, complexAbilityCall, checkAbCallsCall } from '../examples/abilityCall.js'; +import { Fluence, IFluenceClient, createClient } from "@fluencelabs/js-client"; import { - nilLengthCall, - nilLiteralCall, - returnNilCall, - returnNoneCall, - streamAssignmentCall, - streamCall, - streamFunctorCall, - streamIntFunctorCall, - streamJoinCall, - streamReturnFromInnerFunc, -} from '../examples/streamCall.js'; -import { topologyBug205Call, topologyBug394Call, topologyBug427Call, topologyCall } from '../examples/topologyCall.js'; -import { foldJoinCall } from '../examples/foldJoinCall.js'; -import { registerHandlers, returnNull, returnOptionalCall, useOptionalCall } from '../examples/useOptionalCall.js'; -import { viaArrCall, viaOptCall, viaOptNullCall, viaStreamCall } from '../examples/viaCall.js'; -import { nestedFuncsCall } from '../examples/nestedFuncsCall.js'; -import { assignmentCall } from '../examples/assignment.js'; -import { boolAlgebraCall, compareStreamsCall, compareStructsCall } from '../examples/boolAlgebra.js'; -import { tryCatchCall } from '../examples/tryCatchCall.js'; -import { tryOtherwiseCall } from '../examples/tryOtherwiseCall.js'; -import { coCall } from '../examples/coCall.js'; -import { bugLNG60Call, passArgsCall } from '../examples/passArgsCall.js'; -import { streamArgsCall } from '../examples/streamArgsCall.js'; -import { streamResultsCall } from '../examples/streamResultsCall.js'; -import { structuralTypingCall } from '../examples/structuralTypingCall'; -import { streamReturnCall } from '../examples/streamReturn.js'; -import { streamCaptureSimpleCall, streamCaptureReturnCall } from '../examples/streamCapture.js'; -import { streamIfCall, streamForCall, streamTryCall, streamComplexCall } from '../examples/streamScopes.js'; -import { pushToStreamCall } from '../examples/pushToStreamCall.js'; -import { literalCall } from '../examples/returnLiteralCall.js'; -import { multiReturnCall } from '../examples/multiReturnCall.js'; -import { declareCall } from '../examples/declareCall.js'; -import { genOptions, genOptionsEmptyString } from '../examples/optionsCall.js'; -import { lng193BugCall } from '../examples/closureReturnRename.js'; -import { closuresCall } from '../examples/closures.js'; -import { bugLNG63_2Call, bugLNG63_3Call, bugLNG63Call, streamCanCall } from '../examples/streamCanCall.js'; -import { streamCallbackCall } from '../examples/streamCallback.js'; -import { streamResCall } from '../examples/streamRestrictionsCall.js'; -import { joinIdxCall, joinIdxLocalCall, joinIdxRelayCall } from '../examples/joinCall.js'; -import { recursiveStreamsCall } from '../examples/recursiveStreamsCall.js'; -import { renameVarsCall } from '../examples/renameVars.js'; -import { arraySugarCall, bugLNG59Call, optionSugarCall, streamSugarCall } from '../examples/collectionSugarCall.js'; -import { funcsCall } from '../examples/funcsCall.js'; -import { nestedDataCall } from '../examples/nestedDataCall.js'; + getObjAssignCall, + getObjCall, + getObjRelayCall, +} from "../examples/objectCall.js"; import { - mathTest1Call, - mathTest2Call, - mathTestI16Call, - mathTestI32Call, - mathTestI64Call, - mathTestU64Call, -} from '../examples/mathCall.js'; -import { lng58Bug } from '../compiled/examples/closures.js'; -import { config, isEphemeral } from '../config.js'; -import { bugLng79Call } from '../examples/canonCall.js'; -import { bugLng119Call } from '../examples/functorsCall.js'; -import { returnArrowCall, returnArrowChainCall } from '../examples/returnArrowCall.js'; + 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"; +import { foldBug499Call, foldCall } from "../examples/foldCall.js"; +import { bugNG69Call, ifCall, ifWrapCall } from "../examples/ifCall.js"; +import { parCall, testTimeoutCall } from "../examples/parCall.js"; +import { complexCall } from "../examples/complex.js"; +import { + constantsCall, + particleTtlAndTimestampCall, +} from "../examples/constantsCall.js"; +import { + abilityCall, + complexAbilityCall, + checkAbCallsCall, +} from "../examples/abilityCall.js"; +import { + nilLengthCall, + nilLiteralCall, + returnNilCall, + returnNoneCall, + streamAssignmentCall, + streamCall, + streamFunctorCall, + streamIntFunctorCall, + streamJoinCall, + streamReturnFromInnerFunc, +} from "../examples/streamCall.js"; +import { + topologyBug205Call, + topologyBug394Call, + topologyBug427Call, + topologyCall, +} from "../examples/topologyCall.js"; +import { foldJoinCall } from "../examples/foldJoinCall.js"; +import { + registerHandlers, + returnNull, + returnOptionalCall, + useOptionalCall, +} from "../examples/useOptionalCall.js"; +import { + viaArrCall, + viaOptCall, + viaOptNullCall, + viaStreamCall, +} from "../examples/viaCall.js"; +import { nestedFuncsCall } from "../examples/nestedFuncsCall.js"; +import { assignmentCall } from "../examples/assignment.js"; +import { + boolAlgebraCall, + compareStreamsCall, + compareStructsCall, +} from "../examples/boolAlgebra.js"; +import { tryCatchCall } from "../examples/tryCatchCall.js"; +import { tryOtherwiseCall } from "../examples/tryOtherwiseCall.js"; +import { coCall } from "../examples/coCall.js"; +import { bugLNG60Call, passArgsCall } from "../examples/passArgsCall.js"; +import { streamArgsCall } from "../examples/streamArgsCall.js"; +import { streamResultsCall } from "../examples/streamResultsCall.js"; +import { structuralTypingCall } from "../examples/structuralTypingCall"; +import { + servicesAsAbilitiesCall, + expectedServiceResults, + servicesAsAbilitiesCaptureCall, + expectedServiceCaptureResults, +} from "../examples/servicesAsAbilities.js"; +import { streamReturnCall } from "../examples/streamReturn.js"; +import { + streamCaptureSimpleCall, + streamCaptureReturnCall, +} from "../examples/streamCapture.js"; +import { + streamIfCall, + streamForCall, + streamTryCall, + streamComplexCall, +} from "../examples/streamScopes.js"; +import { pushToStreamCall } from "../examples/pushToStreamCall.js"; +import { literalCall } from "../examples/returnLiteralCall.js"; +import { multiReturnCall } from "../examples/multiReturnCall.js"; +import { declareCall } from "../examples/declareCall.js"; +import { genOptions, genOptionsEmptyString } from "../examples/optionsCall.js"; +import { lng193BugCall } from "../examples/closureReturnRename.js"; +import { closuresCall } from "../examples/closures.js"; +import { + bugLNG63_2Call, + bugLNG63_3Call, + bugLNG63Call, + streamCanCall, +} from "../examples/streamCanCall.js"; +import { streamCallbackCall } from "../examples/streamCallback.js"; +import { streamResCall } from "../examples/streamRestrictionsCall.js"; +import { + joinIdxCall, + joinIdxLocalCall, + joinIdxRelayCall, +} from "../examples/joinCall.js"; +import { recursiveStreamsCall } from "../examples/recursiveStreamsCall.js"; +import { renameVarsCall } from "../examples/renameVars.js"; +import { + arraySugarCall, + bugLNG59Call, + optionSugarCall, + streamSugarCall, +} from "../examples/collectionSugarCall.js"; +import { funcsCall } from "../examples/funcsCall.js"; +import { nestedDataCall } from "../examples/nestedDataCall.js"; +import { + mathTest1Call, + mathTest2Call, + mathTestI16Call, + mathTestI32Call, + mathTestI64Call, + mathTestU64Call, +} from "../examples/mathCall.js"; +import { lng58Bug } from "../compiled/examples/closures.js"; +import { config, isEphemeral } from "../config.js"; +import { bugLng79Call } from "../examples/canonCall.js"; +import { bugLng119Call } from "../examples/functorsCall.js"; +import { + returnArrowCall, + returnArrowChainCall, +} from "../examples/returnArrowCall.js"; var selfPeerId: string; var peer1: IFluenceClient; @@ -81,662 +149,723 @@ const relayPeerId1 = relay1.peerId; export const relay2 = config.relays[1]; const relayPeerId2 = relay2.peerId; -import log from 'loglevel'; +import log from "loglevel"; // log.setDefaultLevel("debug") async function start() { - console.log('CONNECTING TO FIRST:'); - Fluence.onConnectionStateChange((s) => { - console.log(s); - }); - await Fluence.connect(relay1, {}); - const cl = await Fluence.getClient(); - peer1 = cl; - selfPeerId = cl.getPeerId(); - console.log('CONNECTED'); + console.log("CONNECTING TO FIRST:"); + Fluence.onConnectionStateChange((s) => { + console.log(s); + }); + await Fluence.connect(relay1, {}); + const cl = await Fluence.getClient(); + peer1 = cl; + selfPeerId = cl.getPeerId(); + console.log("CONNECTED"); - peer2 = await createClient(relay2, {}); - console.log('CONNECTING TO SECOND:'); - peer2.onConnectionStateChange((s) => { - console.log(s); - }); - console.log('CONNECTED'); + peer2 = await createClient(relay2, {}); + console.log("CONNECTING TO SECOND:"); + peer2.onConnectionStateChange((s) => { + console.log(s); + }); + console.log("CONNECTED"); } async function stop() { - await Fluence.disconnect(); - if (peer2) { - await peer2.disconnect(); - } + await Fluence.disconnect(); + if (peer2) { + await peer2.disconnect(); + } } -describe('Testing examples', () => { - beforeAll(async () => { - await start(); - - // this could be called from `println.aqua` - registerPrintln({ - print: (arg0) => { - console.dir(arg0); - }, - }); - }, 12000); - - afterAll(async () => { - await stop(); - }); - - it('callArrow.aqua args bug 426', async () => { - let argResult = await reproArgsBug426Call(); - - expect(argResult).toBe('privet'); - }); - - it('returnArrow.aqua', async () => { - let [result1, result2] = await returnArrowCall(); - - expect(result1).toBe('arg for closure '); - expect(result2).toBe('arg for closure arg for func literal'); - }); - - it('returnArrow.aqua chain', async () => { - let argResult = await returnArrowChainCall(); - - expect(argResult).toStrictEqual([ - 'first', - 'firstarg for func1 literal', - 'second', - 'secondarg for func2 second literal', - 'third', - 'thirdarg for func2 second literal', - 'fourth', - 'fourth from second literal', - ]); - }); - - it('streamRestrictions.aqua', async () => { - let streamResResult = await streamResCall(); - - expect(streamResResult).toEqual([[], ['a', 'b', 'c']]); - }); - - it('streamScopes.aqua streamIf', async () => { - let streamIfResult = await streamIfCall(); - - expect(streamIfResult).toEqual(5); - }); - - it('streamScopes.aqua streamTry', async () => { - let streamTryResult = await streamTryCall(); - - expect(streamTryResult).toEqual(4); - }); - - it('streamScopes.aqua streamFor', async () => { - let streamTryResult = await streamForCall(); - - expect(streamTryResult).toEqual(4); - }); - - it('streamScopes.aqua streamComplex', async () => { - let streamTryResult = await streamComplexCall(); - - expect(streamTryResult).toEqual(13); - }); - - it('if.aqua', async () => { - await ifCall(); - }); - - it('if.aqua xor wrap', async () => { - let res = await ifWrapCall(relay2.peerId); - expect(res).toBe('1x'); - }); - - it('if.aqua bug LNG-69', async () => { - let res = await bugNG69Call(relay2.peerId); - expect(res).toBe(true); - }); - - it('helloWorld.aqua', async () => { - let helloWorldResult = await helloWorldCall(); - expect(helloWorldResult).toBe('Hello, NAME!'); - }); - - it('func.aqua', async () => { - let funcCallResult = await funcCall(); - expect(funcCallResult).toBe('some str'); - }); - - it('dataAlias.aqua', async () => { - let dataAliasResult = await dataAliasCall(); - expect(dataAliasResult).toBe('peer id str'); - }); - - it('constants.aqua', async () => { - let constantCallResult = await constantsCall(); - expect(constantCallResult).toEqual(['5', 'default-str']); - }); - - it('PARTICLE_TTL and PARTICLE_TIMESTAMP', async () => { - const ttl = 1234; - let result = await particleTtlAndTimestampCall(ttl); - expect(result[1]).toBeDefined(); - expect(result[0]).toEqual(ttl); - }); - - it('stream.aqua return stream from inner func', async () => { - let streamResult = await streamReturnFromInnerFunc(); - expect(streamResult).toEqual([1, 2, 3, 4]); - }); - - it('stream.aqua functor', async () => { - let streamResult = await streamFunctorCall(); - expect(streamResult).toEqual('123'); - }); - - it('stream.aqua assignment', async () => { - let streamResult = await streamAssignmentCall(); - expect(streamResult).toEqual('333'); - }); - - it('stream.aqua nil literal', async () => { - let result = await nilLiteralCall(); - expect(result).toEqual([]); - }); - - it('structuraltyping.aqua', async () => { - let result = await structuralTypingCall(); - expect(result).toEqual('some_stringsome_stringsome_stringab_string'); - }); - - it('collectionSugar array', async () => { - let result = await arraySugarCall(); - expect(result).toEqual([ - [1, 2, 3], - [4, 5, 6], - ]); - }); - - it('object creation getObj', async () => { - let result = await getObjCall(); - expect(result).toEqual({ - str: 'some str', - num: 5, - inner: { - arr: ['a', 'b', 'c'], - num: 6, - }, - }); - }); - - it('object creation getObjAssign', async () => { - let result = await getObjAssignCall(); - expect(result).toEqual([ - { - str: 'first str', - num: 5, - inner: { - arr: ['d', 'e', 'f'], - num: 7, - }, - }, - { - str: 'some str', - num: 6, - inner: { - arr: ['a', 'b', 'c'], - num: 7, - }, - }, - 1, - ]); - }); - - it('collectionSugar stream', async () => { - let result = await streamSugarCall(); - expect(result).toEqual([ - [1, 2, 3], - [4, 5, 6], - ]); - }); - - it('update bug collectionSugar option', async () => { - let result = await optionSugarCall(); - expect(result).toEqual([[1], ['some'], []]); - }); - - it('math.aqua test 1', async () => { - let res = await mathTest1Call(); - - expect(res).toEqual(-10); - }); - - it('math.aqua test 2', async () => { - let res = await mathTest2Call(); - - expect(res).toEqual(3); - }); - - it('math.aqua test I16', async () => { - let res = await mathTestI16Call(relay1.peerId); - - expect(res).toEqual([-32, -64, -8, -8]); - }); - - it('math.aqua test I32', async () => { - let res = await mathTestI32Call(relay1.peerId); - - expect(res).toEqual([-16, -256, -8, 16]); - }); - - it('math.aqua test I64', async () => { - let res = await mathTestI64Call(relay1.peerId); - - expect(res).toEqual([0, -512, 0, 72]); - }); - - it('math.aqua test U64', async () => { - let res = await mathTestU64Call(relay1.peerId); - - expect(res).toEqual([96, 4096, 0, -56]); - }); - - it('multiReturn.aqua', async () => { - let multiReturnResult = await multiReturnCall(); - expect(multiReturnResult).toEqual([['some-str', 'random-str', 'some-str'], 5, 'some-str', [1, 2], null, 10]); - }); - - it('option_gen.aqua', async () => { - let optionGenResult = await genOptions(); - expect(optionGenResult).toEqual(['none', 'some']); - }); - - it('option_gen.aqua emptyString', async () => { - let optionGenResult = await genOptionsEmptyString(); - expect(optionGenResult).toEqual(null); - }); - - it('option.aqua', async () => { - registerHandlers(); - let optionResult = await useOptionalCall(); - let optionalResult = await returnOptionalCall(); - let noneResult = await returnNull(); - expect(optionResult).toBe('hello'); - expect(optionalResult).toBe('optional'); - expect(noneResult).toBe(null); - }); - - it('nestedFuncs.aqua', async () => { - let nestedFuncsResult = await nestedFuncsCall(); - expect(nestedFuncsResult).toBe('some-str'); - }); - - it('nestedData.aqua', async () => { - let nestedDataResult = await nestedDataCall(); - expect(nestedDataResult).toEqual({ - one: { - val: 'hellohello', - }, - }); - }); - - it('ability.aqua', async () => { - let result = await abilityCall(); - expect(result).toStrictEqual(['declare_const123', 'efre123', 'declare_const123', 12]); - }); +describe("Testing examples", () => { + beforeAll(async () => { + await start(); - it('ability.aqua complex', async () => { - let result = await complexAbilityCall(); - expect(result).toStrictEqual([false, true]); + // this could be called from `println.aqua` + registerPrintln({ + print: (arg0) => { + console.dir(arg0); + }, }); - - it('ability.aqua ability calls', async () => { - let result = await checkAbCallsCall(); - expect(result).toStrictEqual([true, false]); - }); - - it('functors.aqua LNG-119 bug', async () => { - let result = await bugLng119Call(); - expect(result).toEqual([1]); - }); - - it('passArgsCall.aqua', async () => { - let passArgsResult = await passArgsCall(); - expect(passArgsResult).toBe('client-utilsid'); - }); - - it('passArgsCall.aqua bugLNG60', async () => { - let result = await bugLNG60Call(relayPeerId1); - expect(result).toBe(true); - }); - - it('streamArgs.aqua', async () => { - let streamArgsResult = await streamArgsCall(); - expect(streamArgsResult).toEqual([['peer_id', 'peer_id']]); - }); - - it('streamResults.aqua', async () => { - let streamResultsResult = await streamResultsCall(); - expect(streamResultsResult).toEqual(['new_name', 'new_name', 'new_name']); - }); - - it('streamReturn.aqua', async () => { - let streamReturnResult = await streamReturnCall(); - expect(streamReturnResult).toEqual(['one', 'two', 'three', 'four']); - }); - - it('streamCapture.aqua simple', async () => { - let streamCaptureResult = await streamCaptureSimpleCall(); - expect(streamCaptureResult).toEqual(['one', 'two', 'three']); - }); - - // TODO: Unskip this after LNG-226 is fixed - it.skip('streamCapture.aqua return', async () => { - let streamCaptureResult = await streamCaptureReturnCall(); - expect(streamCaptureResult).toEqual(['one', 'two', 'three', 'four', 'five']); - }); - - it('assignment.aqua', async () => { - let assignmentResult = await assignmentCall(); - expect(assignmentResult).toEqual(['abc', 'hello']); - }); - - it('boolAlgebra.aqua', async () => { - let boolAlgebraResult = await boolAlgebraCall(relayPeerId1); - expect(boolAlgebraResult).toEqual([ - true, - true, - true, - true, - false, - true, - true, - false, - true, - true, - false, - true, - false, - true, - false, - true, - false, - true, - false, - ]); - }); - - it('boolAlgebra.aqua compareStreams', async () => { - let result = await compareStreamsCall(relayPeerId1); - expect(result).toEqual(true); - }); - - it('boolAlgebra.aqua compareStructs', async () => { - let result = await compareStructsCall(relayPeerId1, 'struct'); - expect(result).toEqual(false); - }); - - it('join.aqua local', async () => { - let joinLocalCallResult = await joinIdxLocalCall(relayPeerId1); - expect(joinLocalCallResult.length).toBeGreaterThanOrEqual(2); - }); - - it('stream.aqua', async () => { - let streamResult = await streamCall(); - expect(streamResult).toEqual(['first updated', 'second updated', 'third updated', 'fourth updated']); - // bug LNG-84 - let returnNilResult = await returnNilCall(); - expect(returnNilResult).toEqual([]); - let returnNoneResult = await returnNoneCall(); - expect(returnNoneResult).toBe(null); - }); - - it('stream.aqua nil length', async () => { - let result = await nilLengthCall(); - expect(result).toEqual(0); - }); - - it('stream.aqua int functor', async () => { - let streamResult = await streamIntFunctorCall(); - expect(streamResult).toEqual('123'); - }); - - it('streamCan.aqua LNG-63', async () => { - let result = await bugLNG63Call(); - expect(result).toEqual('ok'); - }); - - it('streamCan.aqua LNG-63 2', async () => { - let result = await bugLNG63_2Call(); - expect(result).toEqual(['ok', ['ok'], ['ok', 'no', 'ok']]); - }); - - it('streamCan.aqua LNG-63 3', async () => { - let result = await bugLNG63_3Call(); - expect(result).toEqual(['ok', 1, [1, 3, 2]]); - }); - - it('streamCan.aqua', async () => { - let streamCanResult = await streamCanCall(); - expect(streamCanResult).toEqual(['a', 'b', null]); - }); - - it('streamCallback.aqua', async () => { - let streamCallResult = await streamCallbackCall(); - expect(streamCallResult).toEqual([]); - }); - - it('literalCall.aqua', async () => { - let literalCallResult = await literalCall(); - expect(literalCallResult).toBe('some literal'); - }); - - it('pushToStream.aqua', async () => { - let pushToStreamResult = await pushToStreamCall(); - expect(pushToStreamResult).toEqual(['hello', 'get_string']); - }); - - it('declare.aqua', async () => { - let declareResult = await declareCall(); - expect(declareResult).toBe('small_foodeclare all barsmall_fooexport_constdeclare_constdeclare_const2'); - }); - - it('fold.aqua bug #499', async () => { - let foldCallResult = await foldBug499Call(); - expect(foldCallResult).toEqual([5]); - }); - - it('stream.aqua join', async () => { - let streamResult = await streamJoinCall(); - expect(streamResult).toEqual('444'); - }); - - it('funcs.aqua', async () => { - let result = await funcsCall(); - expect(result).toEqual([13, 6, 3, 1]); - }, 7000); - - // it('closures.aqua LNG-58 bug', async () => { - // let res = await lng58Bug() - // expect(res).toEqual("ok") - // }); - - // TODO: uncomment - // it('recursiveStreams.aqua', async () => { - // let [sucList, loopList] = await recursiveStreamsCall(); - // console.log(sucList); - // console.log(loopList); - // expect(loopList).toEqual(['yes', 'yes', 'yes', 'yes', 'no']); - // expect(sucList.length).toEqual(5); - // }); - - it('renameVars.aqua', async () => { - let renameVarsResult = await renameVarsCall(); - expect(renameVarsResult).toEqual(['ok', 'ok']); - }); - - it('callArrow.aqua', async () => { - let callArrowResult = await callArrowCall(relayPeerId1); - - expect(callArrowResult).toBe('Hello, callArrow call!'); - }, 10000); - - it('fold.aqua', async () => { - let foldCallResult = await foldCall(relayPeerId1); - expect(foldCallResult).toEqual(config.externalAddressesRelay1); - }); - - it('par.aqua', async () => { - let parCallResult = await parCall(relayPeerId1); - expect(parCallResult).toBe('hello'); - }); - - it('par.aqua testTimeout', async () => { - let testTimeoutResult = await testTimeoutCall(); - expect(testTimeoutResult).toBe('timeout'); - }); - - it('canon bug LNG-79', async () => { - let result = await bugLng79Call(selfPeerId, config.relays[0].peerId); - expect(result).toBe(2); - }); - - it('on.aqua', async () => { - let onCallResult = await onCall(relayPeerId1); - 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]); - }); - - it('object creation getObjRelay', async () => { - let result = await getObjRelayCall(); - expect(result).toEqual({ - str: 'some str', - num: 5, - inner: { - arr: ['a', 'b', 'c'], - num: 6, - }, - }); - }); - - it('collectionSugar bug LNG-59', async () => { - let result = await bugLNG59Call([config.relays[2].peerId, config.relays[3].peerId]); - expect(result).toEqual('some str'); - }); - - it('topology.aqua', async () => { - let topologyResult = await topologyCall(peer1, relay1.peerId, peer2, relay2.peerId); - expect(topologyResult).toBe('finish'); - }); - - it('topology.aqua bug 205', async () => { - let topologyResult = await topologyBug205Call(relay1.peerId, relay2.peerId); - const peerId2 = relay2.peerId; - const res: string[] = [peerId2]; - expect(topologyResult).toEqual(res); - }); - - it('topology.aqua bug 427', async () => { - let topologyResult = await topologyBug427Call(relay1.peerId, relay2.peerId); - - expect(topologyResult).toEqual(['some string', 'some string']); - }); - - it('topology.aqua bug 394', async () => { - let topologyResult = await topologyBug394Call( - peer1.getPeerId(), - relay1.peerId, - peer2.getPeerId(), - relay2.peerId, - ); - - expect(topologyResult).toEqual(selfPeerId); - }); - - it('foldJoin.aqua', async () => { - let foldJoinResult = await foldJoinCall(relayPeerId1); - expect(foldJoinResult.length).toBeGreaterThanOrEqual(3); - }, 16000); - - it('via.aqua', async () => { - let res1 = await viaArrCall(); - let res2 = await viaOptCall(relayPeerId1); - let res3 = await viaOptNullCall(relayPeerId1); - let res4 = await viaStreamCall(relayPeerId1); - expect(res1).toEqual(res2); - expect(res2).toEqual(res3); - expect(res3).toEqual(res4); - }, 180000); - - it('closureReturnRename.aqua bug LNG-193', async () => { - let result = await lng193BugCall(); - expect(result).toEqual(1 + 42 + (2 + 42) + (3 + 42) + (4 + 42)); - }, 20000); - - it('closures.aqua', async () => { - let closuresResult = await closuresCall(); - let res1 = config.externalAddressesRelay2; - let res2 = ['in', config.externalAddressesRelay2[0]]; - expect(closuresResult).toEqual(['in', res1, res1, res2]); - }, 20000); - - it('tryOtherwise.aqua', async () => { - let tryOtherwiseResult = await tryOtherwiseCall(relayPeerId1); - expect(tryOtherwiseResult).toBe('error'); - }, 20000); - - it('tryCatch.aqua', async () => { - let tryCatchResult = await tryCatchCall(relayPeerId1); - expect(tryCatchResult).toHaveLength(2); - expect(tryCatchResult[0]).toMatch(config.tryCatchError); - expect(tryCatchResult[1]).toBe(config.externalAddressesRelay1[0]); - }, 20000); - - it('coCall.aqua', async () => { - let coCallResult = await coCall(); - expect(coCallResult).toEqual(config.externalAddressesRelay1); - }, 60000); - - it('join.aqua relay', async () => { - let joinRelayCallResult = await joinIdxRelayCall(relayPeerId1); - expect(joinRelayCallResult.length).toBeGreaterThanOrEqual(2); - }, 30000); - - it('join.aqua network', async () => { - let joinCallResult = await joinIdxCall(relayPeerId1); - expect(joinCallResult.length).toBeGreaterThanOrEqual(2); - }, 10000); + }, 12000); + + afterAll(async () => { + await stop(); + }); + + it("callArrow.aqua args bug 426", async () => { + let argResult = await reproArgsBug426Call(); + + expect(argResult).toBe("privet"); + }); + + it("returnArrow.aqua", async () => { + let [result1, result2] = await returnArrowCall(); + + expect(result1).toBe("arg for closure "); + expect(result2).toBe("arg for closure arg for func literal"); + }); + + it("returnArrow.aqua chain", async () => { + let argResult = await returnArrowChainCall(); + + expect(argResult).toStrictEqual([ + "first", + "firstarg for func1 literal", + "second", + "secondarg for func2 second literal", + "third", + "thirdarg for func2 second literal", + "fourth", + "fourth from second literal", + ]); + }); + + it("streamRestrictions.aqua", async () => { + let streamResResult = await streamResCall(); + + expect(streamResResult).toEqual([[], ["a", "b", "c"]]); + }); + + it("streamScopes.aqua streamIf", async () => { + let streamIfResult = await streamIfCall(); + + expect(streamIfResult).toEqual(5); + }); + + it("streamScopes.aqua streamTry", async () => { + let streamTryResult = await streamTryCall(); + + expect(streamTryResult).toEqual(4); + }); + + it("streamScopes.aqua streamFor", async () => { + let streamTryResult = await streamForCall(); + + expect(streamTryResult).toEqual(4); + }); + + it("streamScopes.aqua streamComplex", async () => { + let streamTryResult = await streamComplexCall(); + + expect(streamTryResult).toEqual(13); + }); + + it("if.aqua", async () => { + await ifCall(); + }); + + it("if.aqua xor wrap", async () => { + let res = await ifWrapCall(relay2.peerId); + expect(res).toBe("1x"); + }); + + it("if.aqua bug LNG-69", async () => { + let res = await bugNG69Call(relay2.peerId); + expect(res).toBe(true); + }); + + it("helloWorld.aqua", async () => { + let helloWorldResult = await helloWorldCall(); + expect(helloWorldResult).toBe("Hello, NAME!"); + }); + + it("func.aqua", async () => { + let funcCallResult = await funcCall(); + expect(funcCallResult).toBe("some str"); + }); + + it("dataAlias.aqua", async () => { + let dataAliasResult = await dataAliasCall(); + expect(dataAliasResult).toBe("peer id str"); + }); + + it("constants.aqua", async () => { + let constantCallResult = await constantsCall(); + expect(constantCallResult).toEqual(["5", "default-str"]); + }); + + it("PARTICLE_TTL and PARTICLE_TIMESTAMP", async () => { + const ttl = 1234; + let result = await particleTtlAndTimestampCall(ttl); + expect(result[1]).toBeDefined(); + expect(result[0]).toEqual(ttl); + }); + + it("stream.aqua return stream from inner func", async () => { + let streamResult = await streamReturnFromInnerFunc(); + expect(streamResult).toEqual([1, 2, 3, 4]); + }); + + it("stream.aqua functor", async () => { + let streamResult = await streamFunctorCall(); + expect(streamResult).toEqual("123"); + }); + + it("stream.aqua assignment", async () => { + let streamResult = await streamAssignmentCall(); + expect(streamResult).toEqual("333"); + }); + + it("stream.aqua nil literal", async () => { + let result = await nilLiteralCall(); + expect(result).toEqual([]); + }); + + it("structuraltyping.aqua", async () => { + let result = await structuralTypingCall(); + expect(result).toEqual("some_stringsome_stringsome_stringab_string"); + }); + + it("servicesAsAbilities.aqua", async () => { + let result = await servicesAsAbilitiesCall(); + expect(result).toEqual(expectedServiceResults); + }); + + it("servicesAsAbilities.aqua capture", async () => { + let result = await servicesAsAbilitiesCaptureCall(); + expect(result).toEqual(expectedServiceCaptureResults); + }); + + it("collectionSugar array", async () => { + let result = await arraySugarCall(); + expect(result).toEqual([ + [1, 2, 3], + [4, 5, 6], + ]); + }); + + it("object creation getObj", async () => { + let result = await getObjCall(); + expect(result).toEqual({ + str: "some str", + num: 5, + inner: { + arr: ["a", "b", "c"], + num: 6, + }, + }); + }); + + it("object creation getObjAssign", async () => { + let result = await getObjAssignCall(); + expect(result).toEqual([ + { + str: "first str", + num: 5, + inner: { + arr: ["d", "e", "f"], + num: 7, + }, + }, + { + str: "some str", + num: 6, + inner: { + arr: ["a", "b", "c"], + num: 7, + }, + }, + 1, + ]); + }); + + it("collectionSugar stream", async () => { + let result = await streamSugarCall(); + expect(result).toEqual([ + [1, 2, 3], + [4, 5, 6], + ]); + }); + + it("update bug collectionSugar option", async () => { + let result = await optionSugarCall(); + expect(result).toEqual([[1], ["some"], []]); + }); + + it("math.aqua test 1", async () => { + let res = await mathTest1Call(); + + expect(res).toEqual(-10); + }); + + it("math.aqua test 2", async () => { + let res = await mathTest2Call(); + + expect(res).toEqual(3); + }); + + it("math.aqua test I16", async () => { + let res = await mathTestI16Call(relay1.peerId); + + expect(res).toEqual([-32, -64, -8, -8]); + }); + + it("math.aqua test I32", async () => { + let res = await mathTestI32Call(relay1.peerId); + + expect(res).toEqual([-16, -256, -8, 16]); + }); + + it("math.aqua test I64", async () => { + let res = await mathTestI64Call(relay1.peerId); + + expect(res).toEqual([0, -512, 0, 72]); + }); + + it("math.aqua test U64", async () => { + let res = await mathTestU64Call(relay1.peerId); + + expect(res).toEqual([96, 4096, 0, -56]); + }); + + it("multiReturn.aqua", async () => { + let multiReturnResult = await multiReturnCall(); + expect(multiReturnResult).toEqual([ + ["some-str", "random-str", "some-str"], + 5, + "some-str", + [1, 2], + null, + 10, + ]); + }); + + it("option_gen.aqua", async () => { + let optionGenResult = await genOptions(); + expect(optionGenResult).toEqual(["none", "some"]); + }); + + it("option_gen.aqua emptyString", async () => { + let optionGenResult = await genOptionsEmptyString(); + expect(optionGenResult).toEqual(null); + }); + + it("option.aqua", async () => { + registerHandlers(); + let optionResult = await useOptionalCall(); + let optionalResult = await returnOptionalCall(); + let noneResult = await returnNull(); + expect(optionResult).toBe("hello"); + expect(optionalResult).toBe("optional"); + expect(noneResult).toBe(null); + }); + + it("nestedFuncs.aqua", async () => { + let nestedFuncsResult = await nestedFuncsCall(); + expect(nestedFuncsResult).toBe("some-str"); + }); + + it("nestedData.aqua", async () => { + let nestedDataResult = await nestedDataCall(); + expect(nestedDataResult).toEqual({ + one: { + val: "hellohello", + }, + }); + }); + + it("ability.aqua", async () => { + let result = await abilityCall(); + expect(result).toStrictEqual([ + "declare_const123", + "efre123", + "declare_const123", + 12, + ]); + }); + + it("ability.aqua complex", async () => { + let result = await complexAbilityCall(); + expect(result).toStrictEqual([false, true]); + }); + + it("ability.aqua ability calls", async () => { + let result = await checkAbCallsCall(); + expect(result).toStrictEqual([true, false]); + }); + + it("functors.aqua LNG-119 bug", async () => { + let result = await bugLng119Call(); + expect(result).toEqual([1]); + }); + + it("passArgsCall.aqua", async () => { + let passArgsResult = await passArgsCall(); + expect(passArgsResult).toBe("client-utilsid"); + }); + + it("passArgsCall.aqua bugLNG60", async () => { + let result = await bugLNG60Call(relayPeerId1); + expect(result).toBe(true); + }); + + it("streamArgs.aqua", async () => { + let streamArgsResult = await streamArgsCall(); + expect(streamArgsResult).toEqual([["peer_id", "peer_id"]]); + }); + + it("streamResults.aqua", async () => { + let streamResultsResult = await streamResultsCall(); + expect(streamResultsResult).toEqual(["new_name", "new_name", "new_name"]); + }); + + it("streamReturn.aqua", async () => { + let streamReturnResult = await streamReturnCall(); + expect(streamReturnResult).toEqual(["one", "two", "three", "four"]); + }); + + it("streamCapture.aqua simple", async () => { + let streamCaptureResult = await streamCaptureSimpleCall(); + expect(streamCaptureResult).toEqual(["one", "two", "three"]); + }); + + // TODO: Unskip this after LNG-226 is fixed + it.skip("streamCapture.aqua return", async () => { + let streamCaptureResult = await streamCaptureReturnCall(); + expect(streamCaptureResult).toEqual([ + "one", + "two", + "three", + "four", + "five", + ]); + }); + + it("assignment.aqua", async () => { + let assignmentResult = await assignmentCall(); + expect(assignmentResult).toEqual(["abc", "hello"]); + }); + + it("boolAlgebra.aqua", async () => { + let boolAlgebraResult = await boolAlgebraCall(relayPeerId1); + expect(boolAlgebraResult).toEqual([ + true, + true, + true, + true, + false, + true, + true, + false, + true, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + ]); + }); + + it("boolAlgebra.aqua compareStreams", async () => { + let result = await compareStreamsCall(relayPeerId1); + expect(result).toEqual(true); + }); + + it("boolAlgebra.aqua compareStructs", async () => { + let result = await compareStructsCall(relayPeerId1, "struct"); + expect(result).toEqual(false); + }); + + it("join.aqua local", async () => { + let joinLocalCallResult = await joinIdxLocalCall(relayPeerId1); + expect(joinLocalCallResult.length).toBeGreaterThanOrEqual(2); + }); + + it("stream.aqua", async () => { + let streamResult = await streamCall(); + expect(streamResult).toEqual([ + "first updated", + "second updated", + "third updated", + "fourth updated", + ]); + // bug LNG-84 + let returnNilResult = await returnNilCall(); + expect(returnNilResult).toEqual([]); + let returnNoneResult = await returnNoneCall(); + expect(returnNoneResult).toBe(null); + }); + + it("stream.aqua nil length", async () => { + let result = await nilLengthCall(); + expect(result).toEqual(0); + }); + + it("stream.aqua int functor", async () => { + let streamResult = await streamIntFunctorCall(); + expect(streamResult).toEqual("123"); + }); + + it("streamCan.aqua LNG-63", async () => { + let result = await bugLNG63Call(); + expect(result).toEqual("ok"); + }); + + it("streamCan.aqua LNG-63 2", async () => { + let result = await bugLNG63_2Call(); + expect(result).toEqual(["ok", ["ok"], ["ok", "no", "ok"]]); + }); + + it("streamCan.aqua LNG-63 3", async () => { + let result = await bugLNG63_3Call(); + expect(result).toEqual(["ok", 1, [1, 3, 2]]); + }); + + it("streamCan.aqua", async () => { + let streamCanResult = await streamCanCall(); + expect(streamCanResult).toEqual(["a", "b", null]); + }); + + it("streamCallback.aqua", async () => { + let streamCallResult = await streamCallbackCall(); + expect(streamCallResult).toEqual([]); + }); + + it("literalCall.aqua", async () => { + let literalCallResult = await literalCall(); + expect(literalCallResult).toBe("some literal"); + }); + + it("pushToStream.aqua", async () => { + let pushToStreamResult = await pushToStreamCall(); + expect(pushToStreamResult).toEqual(["hello", "get_string"]); + }); + + it("declare.aqua", async () => { + let declareResult = await declareCall(); + expect(declareResult).toBe( + "small_foodeclare all barsmall_fooexport_constdeclare_constdeclare_const2", + ); + }); + + it("fold.aqua bug #499", async () => { + let foldCallResult = await foldBug499Call(); + expect(foldCallResult).toEqual([5]); + }); + + it("stream.aqua join", async () => { + let streamResult = await streamJoinCall(); + expect(streamResult).toEqual("444"); + }); + + it("funcs.aqua", async () => { + let result = await funcsCall(); + expect(result).toEqual([13, 6, 3, 1]); + }, 7000); + + // it('closures.aqua LNG-58 bug', async () => { + // let res = await lng58Bug() + // expect(res).toEqual("ok") + // }); + + // TODO: uncomment + // it('recursiveStreams.aqua', async () => { + // let [sucList, loopList] = await recursiveStreamsCall(); + // console.log(sucList); + // console.log(loopList); + // expect(loopList).toEqual(['yes', 'yes', 'yes', 'yes', 'no']); + // expect(sucList.length).toEqual(5); + // }); + + it("renameVars.aqua", async () => { + let renameVarsResult = await renameVarsCall(); + expect(renameVarsResult).toEqual(["ok", "ok"]); + }); + + it("callArrow.aqua", async () => { + let callArrowResult = await callArrowCall(relayPeerId1); + + expect(callArrowResult).toBe("Hello, callArrow call!"); + }, 10000); + + it("fold.aqua", async () => { + let foldCallResult = await foldCall(relayPeerId1); + expect(foldCallResult).toEqual(config.externalAddressesRelay1); + }); + + it("par.aqua", async () => { + let parCallResult = await parCall(relayPeerId1); + expect(parCallResult).toBe("hello"); + }); + + it("par.aqua testTimeout", async () => { + let testTimeoutResult = await testTimeoutCall(); + expect(testTimeoutResult).toBe("timeout"); + }); + + it("canon bug LNG-79", async () => { + let result = await bugLng79Call(selfPeerId, config.relays[0].peerId); + expect(result).toBe(2); + }); + + it("on.aqua", async () => { + let onCallResult = await onCall(relayPeerId1); + 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, + ]); + }); + + it("object creation getObjRelay", async () => { + let result = await getObjRelayCall(); + expect(result).toEqual({ + str: "some str", + num: 5, + inner: { + arr: ["a", "b", "c"], + num: 6, + }, + }); + }); + + it("collectionSugar bug LNG-59", async () => { + let result = await bugLNG59Call([ + config.relays[2].peerId, + config.relays[3].peerId, + ]); + expect(result).toEqual("some str"); + }); + + it("topology.aqua", async () => { + let topologyResult = await topologyCall( + peer1, + relay1.peerId, + peer2, + relay2.peerId, + ); + expect(topologyResult).toBe("finish"); + }); + + it("topology.aqua bug 205", async () => { + let topologyResult = await topologyBug205Call(relay1.peerId, relay2.peerId); + const peerId2 = relay2.peerId; + const res: string[] = [peerId2]; + expect(topologyResult).toEqual(res); + }); + + it("topology.aqua bug 427", async () => { + let topologyResult = await topologyBug427Call(relay1.peerId, relay2.peerId); + + expect(topologyResult).toEqual(["some string", "some string"]); + }); + + it("topology.aqua bug 394", async () => { + let topologyResult = await topologyBug394Call( + peer1.getPeerId(), + relay1.peerId, + peer2.getPeerId(), + relay2.peerId, + ); + + expect(topologyResult).toEqual(selfPeerId); + }); + + it("foldJoin.aqua", async () => { + let foldJoinResult = await foldJoinCall(relayPeerId1); + expect(foldJoinResult.length).toBeGreaterThanOrEqual(3); + }, 16000); + + it("via.aqua", async () => { + let res1 = await viaArrCall(); + let res2 = await viaOptCall(relayPeerId1); + let res3 = await viaOptNullCall(relayPeerId1); + let res4 = await viaStreamCall(relayPeerId1); + expect(res1).toEqual(res2); + expect(res2).toEqual(res3); + expect(res3).toEqual(res4); + }, 180000); + + it("closureReturnRename.aqua bug LNG-193", async () => { + let result = await lng193BugCall(); + expect(result).toEqual(1 + 42 + (2 + 42) + (3 + 42) + (4 + 42)); + }, 20000); + + it("closures.aqua", async () => { + let closuresResult = await closuresCall(); + let res1 = config.externalAddressesRelay2; + let res2 = ["in", config.externalAddressesRelay2[0]]; + expect(closuresResult).toEqual(["in", res1, res1, res2]); + }, 20000); + + it("tryOtherwise.aqua", async () => { + let tryOtherwiseResult = await tryOtherwiseCall(relayPeerId1); + expect(tryOtherwiseResult).toBe("error"); + }, 20000); + + it("tryCatch.aqua", async () => { + let tryCatchResult = await tryCatchCall(relayPeerId1); + expect(tryCatchResult).toHaveLength(2); + expect(tryCatchResult[0]).toMatch(config.tryCatchError); + expect(tryCatchResult[1]).toBe(config.externalAddressesRelay1[0]); + }, 20000); + + it("coCall.aqua", async () => { + let coCallResult = await coCall(); + expect(coCallResult).toEqual(config.externalAddressesRelay1); + }, 60000); + + it("join.aqua relay", async () => { + let joinRelayCallResult = await joinIdxRelayCall(relayPeerId1); + expect(joinRelayCallResult.length).toBeGreaterThanOrEqual(2); + }, 30000); + + it("join.aqua network", async () => { + let joinCallResult = await joinIdxCall(relayPeerId1); + expect(joinCallResult.length).toBeGreaterThanOrEqual(2); + }, 10000); }); diff --git a/integration-tests/src/examples/servicesAsAbilities.ts b/integration-tests/src/examples/servicesAsAbilities.ts new file mode 100644 index 00000000..e3974c1c --- /dev/null +++ b/integration-tests/src/examples/servicesAsAbilities.ts @@ -0,0 +1,71 @@ +import { + test, + testCapture, + registerTestService, +} from "../compiled/examples/servicesAsAbilities.js"; + +const serviceIds = { + default: "default-id", + resolved1: "resolved-id-1", + resolved2: "resolved-id-2", + iter1: "iter-id-1", + iter2: "iter-id-2", +}; + +const serviceIdsSequence = [ + serviceIds.default, + serviceIds.default, + serviceIds.resolved1, + serviceIds.resolved1, + serviceIds.default, + serviceIds.iter1, + serviceIds.iter2, + serviceIds.resolved1, + serviceIds.resolved2, +]; + +const msgs = [ + "call", + "capture call", + "accept closure call", + "accept ability call", +]; + +export const expectedServiceResults = serviceIdsSequence.flatMap((id) => + msgs.map((msg) => `${id}: ${msg}`), +); + +export async function servicesAsAbilitiesCall() { + Object.entries(serviceIds).forEach(([_key, id], _idx) => + registerTestService(id, { + concatId: (s: string) => { + return `${id}: ${s}`; + }, + getId: () => { + return id; + }, + }), + ); + + return await test(); +} + +export const expectedServiceCaptureResults = [ + "resolved-id-in-capture: in capture", + "default-id: in capture", +]; + +export async function servicesAsAbilitiesCaptureCall() { + ["resolved-id-in-capture", "default-id"].forEach((id) => + registerTestService(id, { + concatId: (s: string) => { + return `${id}: ${s}`; + }, + getId: () => { + return id; + }, + }), + ); + + return await testCapture(); +} diff --git a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala index 9f2efa29..fbb0d63a 100644 --- a/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/ArrowInliner.scala @@ -5,10 +5,11 @@ import aqua.model.* import aqua.model.inline.state.{Arrows, Exports, Mangler} import aqua.raw.ops.RawTag import aqua.raw.value.{ValueRaw, VarRaw} -import aqua.types.{AbilityType, ArrowType, BoxType, StreamType, Type} +import aqua.types.{AbilityType, ArrowType, BoxType, NamedType, StreamType, Type} import cats.data.StateT import cats.data.{Chain, IndexedStateT, State} +import cats.syntax.functor.* import cats.syntax.applicative.* import cats.syntax.bifunctor.* import cats.syntax.foldable.* @@ -123,22 +124,22 @@ object ArrowInliner extends Logging { /** * Get ability fields (vars or arrows) from exports * - * @param abilityName ability current name in state - * @param abilityNewName ability new name (for renaming) - * @param abilityType ability type + * @param name ability current name in state + * @param newName ability new name (for renaming) + * @param type ability type * @param exports exports state to resolve fields * @param fields fields selector * @return resolved ability fields (renamed if necessary) */ private def getAbilityFields[T <: Type]( - abilityName: String, - abilityNewName: Option[String], - abilityType: AbilityType, + name: String, + newName: Option[String], + `type`: NamedType, exports: Map[String, ValueModel] - )(fields: AbilityType => Map[String, T]): Map[String, ValueModel] = - fields(abilityType).flatMap { case (fName, _) => - val fullName = AbilityType.fullName(abilityName, fName) - val newFullName = AbilityType.fullName(abilityNewName.getOrElse(abilityName), fName) + )(fields: NamedType => Map[String, T]): Map[String, ValueModel] = + fields(`type`).flatMap { case (fName, _) => + val fullName = AbilityType.fullName(name, fName) + val newFullName = AbilityType.fullName(newName.getOrElse(name), fName) Exports .getLastValue(fullName, exports) @@ -179,24 +180,24 @@ object ArrowInliner extends Logging { /** * Get ability arrows from arrows * - * @param abilityName ability current name in state - * @param abilityNewName ability new name (for renaming) - * @param abilityType ability type + * @param name ability current name in state + * @param newName ability new name (for renaming) + * @param type ability type * @param exports exports state to resolve fields * @param arrows arrows state to resolve arrows * @return resolved ability arrows (renamed if necessary) */ private def getAbilityArrows( - abilityName: String, - abilityNewName: Option[String], - abilityType: AbilityType, + name: String, + newName: Option[String], + `type`: NamedType, exports: Map[String, ValueModel], arrows: Map[String, FuncArrow] ): Map[String, FuncArrow] = { val get = getAbilityFields( - abilityName, - abilityNewName, - abilityType, + name, + newName, + `type`, exports ) @@ -210,12 +211,12 @@ object ArrowInliner extends Logging { } private def getAbilityArrows[S: Arrows: Exports]( - abilityName: String, - abilityType: AbilityType + name: String, + `type`: NamedType ): State[S, Map[String, FuncArrow]] = for { exports <- Exports[S].exports arrows <- Arrows[S].arrows - } yield getAbilityArrows(abilityName, None, abilityType, exports, arrows) + } yield getAbilityArrows(name, None, `type`, exports, arrows) final case class Renamed[T]( renames: Map[String, String], @@ -276,7 +277,22 @@ object ArrowInliner extends Logging { * to avoid collisions, then resolve them in context. */ capturedValues <- findNewNames(fn.capturedValues) - capturedArrows <- findNewNames(fn.capturedArrows) + /** + * If arrow correspond to a value, + * rename in accordingly to the value + */ + capturedArrowValues = fn.capturedArrows.flatMap { case (arrowName, arrow) => + capturedValues.renames + .get(arrowName) + .orElse(fn.capturedValues.get(arrowName).as(arrowName)) + .map(_ -> arrow) + } + /** + * Rename arrows that are not values + */ + capturedArrows <- findNewNames(fn.capturedArrows.filterNot { case (arrowName, _) => + capturedArrowValues.contains(arrowName) + }) /** * Function defines variables inside its body. @@ -301,7 +317,12 @@ object ArrowInliner extends Logging { defineRenames ) - arrowsResolved = arrows ++ capturedArrows.renamed + /** + * TODO: Optimize resolve. + * It seems that resolving whole `exports` + * and `arrows` is not necessary. + */ + arrowsResolved = arrows ++ capturedArrowValues ++ capturedArrows.renamed exportsResolved = exports ++ data.renamed ++ capturedValues.renamed tree = fn.body.rename(renaming) @@ -322,12 +343,13 @@ object ArrowInliner extends Logging { exports <- Exports[S].exports streams <- getOutsideStreamNames + arrows = passArrows ++ arrowsFromAbilities inlineResult <- Exports[S].scope( Arrows[S].scope( for { // Process renamings, prepare environment - fn <- ArrowInliner.prelude(arrow, call, exports, passArrows ++ arrowsFromAbilities) + fn <- ArrowInliner.prelude(arrow, call, exports, arrows) inlineResult <- ArrowInliner.inline(fn, call, streams) } yield inlineResult ) 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 38fc8f74..36fcd9fc 100644 --- a/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/TagInliner.scala @@ -413,6 +413,49 @@ object TagInliner extends Logging { } yield TagInlined.Empty(prefix = prefix) case _ => none + case ServiceIdTag(id, serviceType, name) => + for { + idm <- valueToModel(id) + (idModel, idPrefix) = idm + + // Make `FuncArrow` wrappers for service methods + methods <- serviceType.fields.toSortedMap.toList.traverse { + case (methodName, methodType) => + for { + arrowName <- Mangler[S].findAndForbidName(s"$name-$methodName") + fn = FuncArrow.fromServiceMethod( + arrowName, + serviceType.name, + methodName, + methodType, + idModel + ) + } yield methodName -> fn + } + + // Resolve wrappers in arrows + _ <- Arrows[S].resolved( + methods.map { case (_, fn) => + fn.funcName -> fn + }.toMap + ) + + // Resolve wrappers in exports + _ <- methods.traverse { case (methodName, fn) => + Exports[S].resolveAbilityField( + name, + methodName, + VarModel(fn.funcName, fn.arrowType) + ) + } + + // Resolve service in exports + _ <- Exports[S].resolved( + name, + VarModel(name, serviceType) + ) + } yield TagInlined.Empty(prefix = idPrefix) + case _: SeqGroupTag => pure(SeqModel) case ParTag.Detach => pure(DetachModel) case _: ParGroupTag => pure(ParModel) diff --git a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala index df63c3e5..fc2594c5 100644 --- a/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala +++ b/model/inline/src/main/scala/aqua/model/inline/raw/ApplyPropertiesRawInliner.scala @@ -59,65 +59,57 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi private def unfoldAbilityProperty[S: Mangler: Exports: Arrows]( varModel: VarModel, - abilityType: AbilityType, + abilityType: NamedType, p: PropertyRaw - ): State[S, (VarModel, Inline)] = { - p match { - case IntoArrowRaw(arrowName, t, arguments) => - val arrowType = abilityType.fields - .lookup(arrowName) - .collect { case at @ ArrowType(_, _) => - at - } - .getOrElse { - logger.error(s"Inlining, cannot find arrow $arrowName in ability $varModel") - ArrowType(NilType, NilType) - } - for { - callArrow <- CallArrowRawInliner( - CallArrowRaw( - None, - AbilityType.fullName(varModel.name, arrowName), - arguments, - arrowType, - None - ) + ): State[S, (VarModel, Inline)] = p match { + case IntoArrowRaw(arrowName, t, arguments) => + val arrowType = abilityType.fields + .lookup(arrowName) + .collect { case at @ ArrowType(_, _) => + at + } + .getOrElse { + logger.error(s"Inlining, cannot find arrow $arrowName in $varModel") + ArrowType(NilType, NilType) + } + for { + callArrow <- CallArrowRawInliner( + CallArrowRaw.func( + funcName = AbilityType.fullName(varModel.name, arrowName), + baseType = arrowType, + arguments = arguments ) - result <- callArrow match { - case (vm: VarModel, inl) => - State.pure((vm, inl)) - case (lm: LiteralModel, inl) => - flatLiteralWithProperties(lm, inl, Chain.empty).flatMap { case (vm, inline) => - Exports[S].resolved(vm.name, vm).map(_ => (vm, inline)) - } - } - } yield { - result + ) + result <- callArrow match { + case (vm: VarModel, inl) => + State.pure((vm, inl)) + case (lm: LiteralModel, inl) => + flatLiteralWithProperties(lm, inl, Chain.empty).flatMap { case (vm, inline) => + Exports[S].resolved(vm.name, vm).map(_ => (vm, inline)) + } } - case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) => - (VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure - case IntoFieldRaw(fieldName, t) => - for { - abilityField <- Exports[S].getAbilityField(varModel.name, fieldName) - result <- abilityField match { - case Some(vm: VarModel) => - State.pure((vm, Inline.empty)) - case Some(lm: LiteralModel) => - flatLiteralWithProperties(lm, Inline.empty, Chain.empty) - case _ => - Exports[S].getKeys.flatMap { keys => - logger.error( - s"Inlining, cannot find field ${AbilityType - .fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys" - ) - flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty) - } + } yield result + case IntoFieldRaw(fieldName, at @ AbilityType(abName, fields)) => + (VarModel(AbilityType.fullName(varModel.name, fieldName), at), Inline.empty).pure + case IntoFieldRaw(fieldName, t) => + for { + abilityField <- Exports[S].getAbilityField(varModel.name, fieldName) + result <- abilityField match { + case Some(vm: VarModel) => + State.pure((vm, Inline.empty)) + case Some(lm: LiteralModel) => + flatLiteralWithProperties(lm, Inline.empty, Chain.empty) + case _ => + Exports[S].getKeys.flatMap { keys => + logger.error( + s"Inlining, cannot find field ${AbilityType + .fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys" + ) + flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty) + } - } - } yield { - result } - } + } yield result } private[inline] def unfoldProperty[S: Mangler: Exports: Arrows]( @@ -284,9 +276,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi case (_, _) => unfold(raw).flatMap { case (vm: VarModel, prevInline) => - unfoldProperties(prevInline, vm, properties, propertiesAllowed).map { case (v, i) => - v -> i - } + unfoldProperties(prevInline, vm, properties, propertiesAllowed) + // To coerce types + .map(identity) case (l: LiteralModel, inline) => flatLiteralWithProperties( l, diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala index d92c65b8..ce2a9491 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Arrows.scala @@ -32,12 +32,8 @@ trait Arrows[S] extends Scoped[S] { for { exps <- Exports[S].exports arrs <- arrows - // _ = println(s"Resolved arrow: ${arrow.name}") - // _ = println(s"Captured var names: ${arrow.capturedVars}") captuedVars = exps.filterKeys(arrow.capturedVars).toMap capturedArrows = arrs.filterKeys(arrow.capturedVars).toMap - // _ = println(s"Captured vars: ${captuedVars}") - // _ = println(s"Captured arrows: ${capturedArrows}") funcArrow = FuncArrow.fromRaw(arrow, capturedArrows, captuedVars, topology) _ <- save(arrow.name, funcArrow) } yield () diff --git a/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala b/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala index efd173d7..48962606 100644 --- a/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala +++ b/model/inline/src/main/scala/aqua/model/inline/state/Exports.scala @@ -2,7 +2,8 @@ package aqua.model.inline.state import aqua.model.{LiteralModel, ValueModel, VarModel} import aqua.model.ValueModel.Ability -import aqua.types.AbilityType +import aqua.types.{AbilityType, NamedType} + import cats.data.{NonEmptyList, State} /** @@ -122,7 +123,7 @@ trait Exports[S] extends Scoped[S] { } object Exports { - def apply[S](implicit exports: Exports[S]): Exports[S] = exports + def apply[S](using exports: Exports[S]): Exports[S] = exports // Get last linked VarModel def getLastValue(name: String, state: Map[String, ValueModel]): Option[ValueModel] = { @@ -143,7 +144,7 @@ object Exports { private def getAbilityPairs( oldName: String, newName: String, - at: AbilityType, + at: NamedType, state: Map[String, ValueModel] ): NonEmptyList[(String, ValueModel)] = { at.fields.toNel.flatMap { diff --git a/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala b/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala index a5de04df..329576f2 100644 --- a/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala +++ b/model/inline/src/test/scala/aqua/model/inline/RawBuilder.scala @@ -6,16 +6,16 @@ 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), + CallArrowRaw.service( + abilityName = "math", + serviceId = LiteralRaw.quote("math"), + funcName = "add", baseType = ArrowType( ProductType(List(ScalarType.i64, ScalarType.i64)), ProductType( List(l.`type` `∪` r.`type`) ) ), - serviceId = Some(LiteralRaw.quote("math")) + arguments = List(l, r) ) } diff --git a/model/raw/src/main/scala/aqua/raw/RawContext.scala b/model/raw/src/main/scala/aqua/raw/RawContext.scala index ad0cb174..1a870d2e 100644 --- a/model/raw/src/main/scala/aqua/raw/RawContext.scala +++ b/model/raw/src/main/scala/aqua/raw/RawContext.scala @@ -2,7 +2,7 @@ package aqua.raw import aqua.raw.arrow.FuncRaw import aqua.raw.value.ValueRaw -import aqua.types.{StructType, Type, AbilityType} +import aqua.types.{AbilityType, StructType, Type} import cats.Monoid import cats.Semigroup @@ -61,8 +61,9 @@ case class RawContext( all(_.services) lazy val types: Map[String, Type] = - collectPartsMap { case t: TypeRaw => - t.`type` + collectPartsMap { + case t: TypeRaw => t.`type` + case s: ServiceRaw => s.`type` } lazy val allTypes: Map[String, Type] = diff --git a/model/raw/src/main/scala/aqua/raw/ServiceRaw.scala b/model/raw/src/main/scala/aqua/raw/ServiceRaw.scala index 1db97052..68225398 100644 --- a/model/raw/src/main/scala/aqua/raw/ServiceRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/ServiceRaw.scala @@ -1,15 +1,14 @@ package aqua.raw -import aqua.types.{ArrowType, StructType} -import cats.data.NonEmptyMap +import aqua.types.ServiceType import aqua.raw.value.ValueRaw case class ServiceRaw( name: String, - arrows: NonEmptyMap[String, ArrowType], + `type`: ServiceType, defaultId: Option[ValueRaw] ) extends RawPart { - def rawPartType: StructType = StructType(name, arrows) + def rawPartType: ServiceType = `type` override def rename(s: String): RawPart = copy(name = s) diff --git a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala index e7162ac5..43d49278 100644 --- a/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala +++ b/model/raw/src/main/scala/aqua/raw/ops/RawTag.scala @@ -4,7 +4,7 @@ import aqua.raw.arrow.FuncRaw import aqua.raw.ops.RawTag.Tree import aqua.raw.value.{CallArrowRaw, ValueRaw} import aqua.tree.{TreeNode, TreeNodeCompanion} -import aqua.types.{ArrowType, DataType} +import aqua.types.{ArrowType, DataType, ServiceType} import cats.Show import cats.data.{Chain, NonEmptyList} @@ -20,7 +20,7 @@ sealed trait RawTag extends TreeNode[RawTag] { def restrictsVarNames: Set[String] = Set.empty // All variable names introduced by this tag - def definesVarNames: Set[String] = exportsVarNames ++ restrictsVarNames + final def definesVarNames: Set[String] = exportsVarNames ++ restrictsVarNames // Variable names used by this tag (not introduced by it) def usesVarNames: Set[String] = Set.empty @@ -208,6 +208,22 @@ case class CallArrowRawTag( object CallArrowRawTag { + def ability( + abilityName: String, + funcName: String, + call: Call, + arrowType: ArrowType + ): CallArrowRawTag = + CallArrowRawTag( + call.exportTo, + CallArrowRaw.ability( + abilityName, + funcName, + arrowType, + call.args + ) + ) + def service( serviceId: ValueRaw, fnName: String, @@ -231,12 +247,10 @@ object CallArrowRawTag { def func(fnName: String, call: Call): CallArrowRawTag = CallArrowRawTag( call.exportTo, - CallArrowRaw( - None, - fnName, - call.args, - call.arrowType, - None + CallArrowRaw.func( + funcName = fnName, + baseType = call.arrowType, + arguments = call.args ) ) } @@ -307,15 +321,27 @@ object EmptyTag extends NoExecTag { override def mapValues(f: ValueRaw => ValueRaw): RawTag = this } -case class AbilityIdTag( +/** + * Tag for `Service "id"` expression. + * For each such expression new ability + * is created with unique name (@p name). + * + * @param value value of service ID + * @param serviceType type of service + * @param name **rename** of service + */ +case class ServiceIdTag( value: ValueRaw, - service: String + serviceType: ServiceType, + name: String ) extends NoExecTag { override def usesVarNames: Set[String] = value.varNames + override def exportsVarNames: Set[String] = Set(name) + override def mapValues(f: ValueRaw => ValueRaw): RawTag = - AbilityIdTag(value.map(f), service) + ServiceIdTag(value.map(f), serviceType, name) } case class PushToStreamTag(operand: ValueRaw, exportTo: Call.Export) extends RawTag { 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 f5f46702..f302cb8a 100644 --- a/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala +++ b/model/raw/src/main/scala/aqua/raw/value/ValueRaw.scala @@ -250,12 +250,14 @@ case class CallArrowRaw( override def map(f: ValueRaw => ValueRaw): ValueRaw = f( copy( - arguments = arguments.map(f), - serviceId = serviceId.map(f) + arguments = arguments.map(_.map(f)), + serviceId = serviceId.map(_.map(f)) ) ) - override def varNames: Set[String] = arguments.flatMap(_.varNames).toSet + override def varNames: Set[String] = name.some + .filterNot(_ => ability.isDefined || serviceId.isDefined) + .toSet ++ arguments.flatMap(_.varNames).toSet override def renameVars(map: Map[String, String]): ValueRaw = copy( diff --git a/model/res/src/main/scala/aqua/res/ServiceRes.scala b/model/res/src/main/scala/aqua/res/ServiceRes.scala index 604f714e..e46bca5c 100644 --- a/model/res/src/main/scala/aqua/res/ServiceRes.scala +++ b/model/res/src/main/scala/aqua/res/ServiceRes.scala @@ -11,7 +11,7 @@ object ServiceRes { def fromModel(sm: ServiceModel): ServiceRes = ServiceRes( name = sm.name, - members = sm.arrows.toNel.toList, + members = sm.`type`.arrows.toList, defaultId = sm.defaultId.collect { case LiteralModel(value, t) if ScalarType.string.acceptsValueOf(t) => value diff --git a/model/src/main/scala/aqua/model/AquaContext.scala b/model/src/main/scala/aqua/model/AquaContext.scala index 4f16df4f..739a41e1 100644 --- a/model/src/main/scala/aqua/model/AquaContext.scala +++ b/model/src/main/scala/aqua/model/AquaContext.scala @@ -5,15 +5,19 @@ import aqua.raw.ops.CallArrowRawTag import aqua.raw.value.ValueRaw import aqua.raw.value.CallArrowRaw import aqua.raw.{ConstantRaw, RawContext, RawPart, ServiceRaw, TypeRaw} -import aqua.types.{StructType, Type} +import aqua.types.{AbilityType, StructType, Type} + import cats.Monoid import cats.data.NonEmptyMap import cats.data.Chain import cats.kernel.Semigroup import cats.syntax.functor.* +import cats.syntax.foldable.* +import cats.syntax.traverse.* +import cats.syntax.bifunctor.* import cats.syntax.monoid.* +import cats.syntax.option.* import scribe.Logging - import scala.collection.immutable.SortedMap case class AquaContext( @@ -26,31 +30,76 @@ case class AquaContext( services: Map[String, ServiceModel] ) { - private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) = - (prefix + pair._1, pair._2) - // TODO: it's a duplicate - private def all[T](what: AquaContext => Map[String, T], prefix: String = ""): Map[String, T] = - abilities - .foldLeft(what(this)) { case (ts, (k, v)) => - ts ++ v.all(what, k + ".") - } - .map(prefixFirst(prefix, _)) + private def all[T]( + what: AquaContext => Map[String, T], + prefix: String = "" + ): Map[String, T] = ( + what(this) ++ abilities.toList.foldMap { case (k, v) => + v.all(what, k + ".").toList + } + ).map(_.leftMap(prefix + _)).toMap lazy val allValues: Map[String, ValueModel] = - all(_.values) + all(_.values) ++ + /** + * Add values from services that have default ID + * So that they will be available in functions. + */ + services.flatMap { case (srvName, srv) => + srv.defaultId.toList.flatMap(_ => + srv.`type`.arrows.map { case (arrowName, arrowType) => + val fullName = AbilityType.fullName(srvName, arrowName) + + fullName -> VarModel( + fullName, + arrowType + ) + }.updated( + srvName, + VarModel( + srvName, + srv.`type` + ) + ) + ) + } lazy val allFuncs: Map[String, FuncArrow] = - all(_.funcs) + all(_.funcs) ++ + /** + * Add functions from services that have default ID + * So that they will be available in functions. + */ + services.flatMap { case (srvName, srv) => + srv.defaultId.toList.flatMap(id => + srv.`type`.arrows.map { case (arrowName, arrowType) => + val fullName = AbilityType.fullName(srvName, arrowName) + + fullName -> FuncArrow.fromServiceMethod( + fullName, + srvName, + arrowName, + arrowType, + id + ) + } + ) + } private def pickOne[T]( name: String, newName: String, ctx: Map[String, T], add: (AquaContext, Map[String, T]) => AquaContext - ): AquaContext = { - ctx.get(name).fold(AquaContext.blank)(t => add(AquaContext.blank, Map(newName -> t))) - } + ): AquaContext = ctx + .get(name) + .fold(AquaContext.blank)(t => + add( + AquaContext.blank, + Map(newName -> t) + ) + ) def pick(name: String, maybeRename: Option[String]): AquaContext = { val newName = maybeRename.getOrElse(name) @@ -68,7 +117,7 @@ object AquaContext extends Logging { lazy val size: Long = data.size def get(ctx: RawContext): Option[AquaContext] = - data.find(_._1 eq ctx).map(_._2) + data.collectFirst { case (rawCtx, aquaCtx) if rawCtx eq ctx => aquaCtx } def updated(ctx: RawContext, aCtx: AquaContext): Cache = copy(data :+ (ctx -> aCtx)) } @@ -91,11 +140,10 @@ object AquaContext extends Logging { x.services ++ y.services ) - // def fromService(sm: ServiceRaw, serviceId: ValueRaw): AquaContext = blank.copy( module = Some(sm.name), - funcs = sm.arrows.toSortedMap.map { case (fnName, arrowType) => + funcs = sm.`type`.arrows.map { case (fnName, arrowType) => val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType) fnName -> FuncArrow( @@ -192,13 +240,16 @@ object AquaContext extends Logging { logger.trace("Adding service " + m.name) val (pctx, pcache) = fromRawContext(partContext, ctxCache) logger.trace("Got " + m.name + " from raw") - val id = m.defaultId.map(ValueModel.fromRaw).map(_.resolveWith(pctx.allValues)) - val srv = ServiceModel(m.name, m.arrows, id) + val id = m.defaultId + .map(ValueModel.fromRaw) + .map(_.resolveWith(pctx.allValues)) + val srv = ServiceModel(m.name, m.`type`, id) val add = blank .copy( - abilities = - m.defaultId.fold(Map.empty)(id => Map(m.name -> fromService(m, id))), + abilities = m.defaultId + .map(id => Map(m.name -> fromService(m, id))) + .orEmpty, services = Map(m.name -> srv) ) diff --git a/model/src/main/scala/aqua/model/ArgsCall.scala b/model/src/main/scala/aqua/model/ArgsCall.scala index 7244f7eb..130c06d9 100644 --- a/model/src/main/scala/aqua/model/ArgsCall.scala +++ b/model/src/main/scala/aqua/model/ArgsCall.scala @@ -1,6 +1,7 @@ package aqua.model import aqua.model.{ValueModel, VarModel} +import aqua.model.ValueModel.Ability import aqua.raw.ops.Call import aqua.raw.value.{ValueRaw, VarRaw} import aqua.types.* @@ -39,11 +40,11 @@ case class ArgsCall(args: ProductType, callWith: List[ValueModel]) { }.toMap /** - * Ability arguments as mapping - * Name of argument -> (variable passed in the call, ability type) + * Ability and service arguments as mapping + * Name of argument -> (variable passed in the call, type) */ - lazy val abilityArgs: Map[String, (VarModel, AbilityType)] = - zipped.collect { case ((name, _), vr @ VarModel(_, t @ AbilityType(_, _), _)) => + lazy val abilityArgs: Map[String, (VarModel, NamedType)] = + zipped.collect { case ((name, _), vr @ Ability(_, t, _)) => name -> (vr, t) }.toMap diff --git a/model/src/main/scala/aqua/model/CallModel.scala b/model/src/main/scala/aqua/model/CallModel.scala index a0411fd8..7efb9249 100644 --- a/model/src/main/scala/aqua/model/CallModel.scala +++ b/model/src/main/scala/aqua/model/CallModel.scala @@ -1,17 +1,18 @@ package aqua.model import aqua.raw.ops.Call -import aqua.types.{ArrowType, AbilityType, Type} +import aqua.types.{ArrowType, NamedType, Type} +import aqua.model.ValueModel.{Ability, Arrow} // TODO docs case class CallModel(args: List[ValueModel], exportTo: List[CallModel.Export]) { override def toString: String = s"[${args.mkString(" ")}] ${exportTo.mkString(" ")}" - def arrowArgNames: Set[String] = args.collect { case VarModel(m, _: ArrowType, _) => + def arrowArgNames: Set[String] = args.collect { case Arrow(m, _) => m }.toSet - def abilityArgs: List[(String, AbilityType)] = args.collect { case VarModel(m, t: AbilityType, _) => + def abilityArgs: List[(String, NamedType)] = args.collect { case Ability(m, t, _) => (m, t) } @@ -21,7 +22,7 @@ case class CallModel(args: List[ValueModel], exportTo: List[CallModel.Export]) { object CallModel { case class Export(name: String, `type`: Type) { - + def asVar: VarModel = VarModel(name, `type`) override def toString: String = s"$name:${`type`}" diff --git a/model/src/main/scala/aqua/model/FuncArrow.scala b/model/src/main/scala/aqua/model/FuncArrow.scala index 16651f0b..c9d46822 100644 --- a/model/src/main/scala/aqua/model/FuncArrow.scala +++ b/model/src/main/scala/aqua/model/FuncArrow.scala @@ -2,9 +2,9 @@ package aqua.model import aqua.raw.Raw import aqua.raw.arrow.FuncRaw -import aqua.raw.ops.RawTag +import aqua.raw.ops.{Call, CallArrowRawTag, RawTag} import aqua.raw.value.{ValueRaw, VarRaw} -import aqua.types.{ArrowType, Type} +import aqua.types.{ArrowType, ServiceType, Type} case class FuncArrow( funcName: String, @@ -42,4 +42,49 @@ object FuncArrow { constants, topology ) + + /** + * Create function - wrapper around a service method + * + * @param funcName name of the function + * @param serviceName name of the service + * @param methodName name of the service method to wrap + * @param methodType type of the service method to wrap + * @param idValue resolved value of the service id + * @return `FuncArrow` wrapper for the service method + */ + def fromServiceMethod( + funcName: String, + serviceName: String, + methodName: String, + methodType: ArrowType, + idValue: ValueModel + ): FuncArrow = { + val id = VarRaw("id", idValue.`type`) + val retVar = methodType.res.map(t => VarRaw("ret", t)) + + val call = Call( + methodType.domain.toLabelledList().map(VarRaw.apply), + retVar.map(r => Call.Export(r.name, r.`type`)).toList + ) + val body = CallArrowRawTag.service( + serviceId = id, + fnName = methodName, + call = call, + name = serviceName, + arrowType = methodType + ) + + FuncArrow( + funcName = funcName, + body = body.leaf, + arrowType = methodType, + ret = retVar.toList, + capturedArrows = Map.empty, + capturedValues = Map( + id.name -> idValue + ), + capturedTopology = None + ) + } } diff --git a/model/src/main/scala/aqua/model/ServiceModel.scala b/model/src/main/scala/aqua/model/ServiceModel.scala index 390e2d0d..526256a0 100644 --- a/model/src/main/scala/aqua/model/ServiceModel.scala +++ b/model/src/main/scala/aqua/model/ServiceModel.scala @@ -1,10 +1,9 @@ package aqua.model -import aqua.types.ArrowType -import cats.data.NonEmptyMap +import aqua.types.ServiceType case class ServiceModel( name: String, - arrows: NonEmptyMap[String, ArrowType], + `type`: ServiceType, defaultId: Option[ValueModel] ) diff --git a/model/src/main/scala/aqua/model/ValueModel.scala b/model/src/main/scala/aqua/model/ValueModel.scala index 408eb742..9aa19b86 100644 --- a/model/src/main/scala/aqua/model/ValueModel.scala +++ b/model/src/main/scala/aqua/model/ValueModel.scala @@ -51,11 +51,21 @@ object ValueModel { case _ => ??? } + object Arrow { + + def unapply(vm: VarModel): Option[(String, ArrowType)] = + vm match { + case VarModel(name, t: ArrowType, _) => + (name, t).some + case _ => none + } + } + object Ability { - def unapply(vm: VarModel): Option[(String, AbilityType, Chain[PropertyModel])] = + def unapply(vm: VarModel): Option[(String, NamedType, Chain[PropertyModel])] = vm match { - case VarModel(name, t@AbilityType(_, _), properties) => + case VarModel(name, t: (AbilityType | ServiceType), properties) => (name, t, properties).some case _ => none } diff --git a/parser/src/main/scala/aqua/parser/expr/func/ArrowExpr.scala b/parser/src/main/scala/aqua/parser/expr/func/ArrowExpr.scala index b4a635d8..d918f3d6 100644 --- a/parser/src/main/scala/aqua/parser/expr/func/ArrowExpr.scala +++ b/parser/src/main/scala/aqua/parser/expr/func/ArrowExpr.scala @@ -20,7 +20,7 @@ case class ArrowExpr[F[_]](arrowTypeExpr: ArrowTypeToken[F]) object ArrowExpr extends Expr.AndIndented { val funcChildren: List[Expr.Lexem] = - AbilityIdExpr :: + ServiceIdExpr :: PushToStreamExpr :: ForExpr :: Expr.defer(OnExpr) :: diff --git a/parser/src/main/scala/aqua/parser/expr/func/AbilityIdExpr.scala b/parser/src/main/scala/aqua/parser/expr/func/ServiceIdExpr.scala similarity index 52% rename from parser/src/main/scala/aqua/parser/expr/func/AbilityIdExpr.scala rename to parser/src/main/scala/aqua/parser/expr/func/ServiceIdExpr.scala index ceab7de0..79f9a85c 100644 --- a/parser/src/main/scala/aqua/parser/expr/func/AbilityIdExpr.scala +++ b/parser/src/main/scala/aqua/parser/expr/func/ServiceIdExpr.scala @@ -1,7 +1,7 @@ package aqua.parser.expr.func import aqua.parser.Expr -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.Token.* import aqua.parser.lexer.{NamedTypeToken, ValueToken} import aqua.parser.lift.LiftParser @@ -10,19 +10,19 @@ import cats.{~>, Comonad} import aqua.parser.lift.Span import aqua.parser.lift.Span.{P0ToSpan, PToSpan} -case class AbilityIdExpr[F[_]](ability: NamedTypeToken[F], id: ValueToken[F]) - extends Expr[F](AbilityIdExpr, ability) { +case class ServiceIdExpr[F[_]](service: NamedTypeToken[F], id: ValueToken[F]) + extends Expr[F](ServiceIdExpr, service) { - def mapK[K[_]: Comonad](fk: F ~> K): AbilityIdExpr[K] = - copy(ability.copy(fk(ability.name)), id.mapK(fk)) + def mapK[K[_]: Comonad](fk: F ~> K): ServiceIdExpr[K] = + copy(service.copy(fk(service.name)), id.mapK(fk)) } -object AbilityIdExpr extends Expr.Leaf { +object ServiceIdExpr extends Expr.Leaf { - override val p: P[AbilityIdExpr[Span.S]] = + override val p: P[ServiceIdExpr[Span.S]] = ((NamedTypeToken.dotted <* ` `) ~ ValueToken.`value`).map { case (ability, id) => - AbilityIdExpr(ability, id) + ServiceIdExpr(ability, id) } } diff --git a/parser/src/test/scala/aqua/AquaSpec.scala b/parser/src/test/scala/aqua/AquaSpec.scala index e0c53262..1df39e04 100644 --- a/parser/src/test/scala/aqua/AquaSpec.scala +++ b/parser/src/test/scala/aqua/AquaSpec.scala @@ -118,8 +118,8 @@ trait AquaSpec extends EitherValues { def parseUse(str: String): UseFromExpr[Id] = UseFromExpr.p.parseAll(str).value.mapK(spanToId) - def parseAbId(str: String): AbilityIdExpr[Id] = - AbilityIdExpr.p.parseAll(str).value.mapK(spanToId) + def parseServiceId(str: String): ServiceIdExpr[Id] = + ServiceIdExpr.p.parseAll(str).value.mapK(spanToId) def parseOn(str: String): OnExpr[Id] = OnExpr.p.parseAll(str).value.mapK(spanToId) diff --git a/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala b/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala index fb94aa7f..ba989eba 100644 --- a/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/AbilityIdExprSpec.scala @@ -1,9 +1,10 @@ package aqua.parser import aqua.AquaSpec -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.LiteralToken import aqua.types.LiteralType + import cats.Id import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers @@ -12,20 +13,20 @@ class AbilityIdExprSpec extends AnyFlatSpec with Matchers with AquaSpec { import AquaSpec._ "abilities" should "be parsed" in { - parseAbId("Ab a") should be( - AbilityIdExpr[Id](toNamedType("Ab"), toVar("a")) + parseServiceId("Ab a") should be( + ServiceIdExpr[Id](toNamedType("Ab"), toVar("a")) ) - parseAbId("Ab \"a\"") should be( - AbilityIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string)) + parseServiceId("Ab \"a\"") should be( + ServiceIdExpr[Id](toNamedType("Ab"), LiteralToken[Id]("\"a\"", LiteralType.string)) ) - parseAbId("Ab 1") should be( - AbilityIdExpr[Id](toNamedType("Ab"), toNumber(1)) + parseServiceId("Ab 1") should be( + ServiceIdExpr[Id](toNamedType("Ab"), toNumber(1)) ) - parseAbId("Ab a.id") should be( - AbilityIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id"))) + parseServiceId("Ab a.id") should be( + ServiceIdExpr[Id](toNamedType("Ab"), toVarLambda("a", List("id"))) ) } diff --git a/parser/src/test/scala/aqua/parser/FuncExprSpec.scala b/parser/src/test/scala/aqua/parser/FuncExprSpec.scala index 7c1ddce6..72db95e9 100644 --- a/parser/src/test/scala/aqua/parser/FuncExprSpec.scala +++ b/parser/src/test/scala/aqua/parser/FuncExprSpec.scala @@ -2,17 +2,13 @@ package aqua.parser import aqua.AquaSpec import aqua.parser.expr.* -import aqua.parser.expr.func.{AbilityIdExpr, ArrowExpr, CallArrowExpr, IfExpr, OnExpr, ReturnExpr} -import aqua.parser.lexer.{ - ArrowTypeToken, - BasicTypeToken, - CallArrowToken, - LiteralToken, - Token, - VarToken -} +import aqua.parser.expr.func.* +import aqua.parser.lexer.* import aqua.parser.lift.LiftParser.Implicits.idLiftParser +import aqua.parser.lift.Span +import aqua.parser.lift.Span.{P0ToSpan, PToSpan} import aqua.types.ScalarType.* + import cats.Id import cats.data.{Chain, NonEmptyList} import cats.data.Chain.* @@ -27,11 +23,6 @@ import cats.Eval import scala.collection.mutable import scala.language.implicitConversions -import aqua.parser.lift.Span -import aqua.parser.lift.Span.{P0ToSpan, PToSpan} -import aqua.parser.lexer.PropertyToken -import aqua.parser.lexer.IntoArrow -import aqua.parser.expr.func.AssignmentExpr class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors with AquaSpec { import AquaSpec.{given, *} @@ -123,7 +114,7 @@ class FuncExprSpec extends AnyFlatSpec with Matchers with Inside with Inspectors ) ) ) - ifBody(1).head.mapK(spanToId) should be(AbilityIdExpr(toNamedType("Peer"), toStr("some id"))) + ifBody(1).head.mapK(spanToId) should be(ServiceIdExpr(toNamedType("Peer"), toStr("some id"))) ifBody(2).head.mapK(spanToId) should be( CallArrowExpr(Nil, CallArrowToken("call", List(toBool(true)))) ) diff --git a/parser/src/test/scala/aqua/parser/head/FromSpec.scala b/parser/src/test/scala/aqua/parser/head/FromSpec.scala index 7bd8b9c1..16e65fbb 100644 --- a/parser/src/test/scala/aqua/parser/head/FromSpec.scala +++ b/parser/src/test/scala/aqua/parser/head/FromSpec.scala @@ -1,7 +1,7 @@ package aqua.parser.head import aqua.AquaSpec -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lift.LiftParser.Implicits.* import aqua.types.LiteralType diff --git a/parser/src/test/scala/aqua/parser/head/ImportFromSpec.scala b/parser/src/test/scala/aqua/parser/head/ImportFromSpec.scala index b3399a60..359b88cd 100644 --- a/parser/src/test/scala/aqua/parser/head/ImportFromSpec.scala +++ b/parser/src/test/scala/aqua/parser/head/ImportFromSpec.scala @@ -1,7 +1,7 @@ package aqua.parser.head import aqua.AquaSpec -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lift.LiftParser.Implicits.* import aqua.types.LiteralType @@ -23,8 +23,7 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec { ) ) - HeadExpr - .ast + HeadExpr.ast .parseAll(s"""import MyModule, func as fn from "file.aqua" |""".stripMargin) .value @@ -32,7 +31,8 @@ class ImportFromSpec extends AnyFlatSpec with Matchers with AquaSpec { .value .headOption .get - .head.mapK(spanToId) should be( + .head + .mapK(spanToId) should be( ImportFromExpr( NonEmptyList.fromListUnsafe( Right(toAb("MyModule") -> None) :: Left(toName("func") -> Some(toName("fn"))) :: Nil diff --git a/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala b/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala index ea73f959..c2b7c355 100644 --- a/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala +++ b/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala @@ -1,7 +1,7 @@ package aqua.parser.head import aqua.AquaSpec -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.{LiteralToken, Token} import aqua.types.LiteralType import cats.Id @@ -22,12 +22,12 @@ class ModuleSpec extends AnyFlatSpec with Matchers with AquaSpec { ) ) - HeadExpr - .ast + HeadExpr.ast .parseAll(s"""module MyModule declares * |""".stripMargin) .value - .head.mapK(spanToId) should be( + .head + .mapK(spanToId) should be( ModuleExpr( toAb("MyModule"), Some(Token.lift[Id, Unit](())), diff --git a/parser/src/test/scala/aqua/parser/head/UseSpec.scala b/parser/src/test/scala/aqua/parser/head/UseSpec.scala index 15bdfdc4..a4321452 100644 --- a/parser/src/test/scala/aqua/parser/head/UseSpec.scala +++ b/parser/src/test/scala/aqua/parser/head/UseSpec.scala @@ -1,7 +1,7 @@ package aqua.parser.head import aqua.AquaSpec -import aqua.parser.expr.func.AbilityIdExpr +import aqua.parser.expr.func.ServiceIdExpr import aqua.parser.lexer.{LiteralToken, Token} import aqua.parser.lift.LiftParser.Implicits.* import aqua.types.LiteralType diff --git a/semantics/src/main/scala/aqua/semantics/CompilerState.scala b/semantics/src/main/scala/aqua/semantics/CompilerState.scala index 366f76b7..bc52ca49 100644 --- a/semantics/src/main/scala/aqua/semantics/CompilerState.scala +++ b/semantics/src/main/scala/aqua/semantics/CompilerState.scala @@ -8,6 +8,7 @@ import aqua.semantics.rules.definitions.DefinitionsState import aqua.semantics.rules.locations.LocationsState import aqua.semantics.rules.names.NamesState import aqua.semantics.rules.types.TypesState +import aqua.semantics.rules.mangler.ManglerState import aqua.semantics.rules.errors.ReportErrors import cats.Semigroup @@ -19,6 +20,7 @@ import monocle.macros.GenLens case class CompilerState[S[_]]( errors: Chain[SemanticError[S]] = Chain.empty[SemanticError[S]], + mangler: ManglerState = ManglerState(), names: NamesState[S] = NamesState[S](), abilities: AbilitiesState[S] = AbilitiesState[S](), types: TypesState[S] = TypesState[S](), @@ -42,6 +44,9 @@ object CompilerState { given [S[_]]: Lens[CompilerState[S], AbilitiesState[S]] = GenLens[CompilerState[S]](_.abilities) + given [S[_]]: Lens[CompilerState[S], ManglerState] = + GenLens[CompilerState[S]](_.mangler) + given [S[_]]: Lens[CompilerState[S], TypesState[S]] = GenLens[CompilerState[S]](_.types) @@ -60,7 +65,7 @@ object CompilerState { st.focus(_.errors).modify(_.append(RulesViolated(token, hints))) } - implicit def compilerStateMonoid[S[_]]: Monoid[St[S]] = new Monoid[St[S]] { + given [S[_]]: Monoid[St[S]] with { override def empty: St[S] = State.pure(Raw.Empty("compiler state monoid empty")) override def combine(x: St[S], y: St[S]): St[S] = for { @@ -69,6 +74,7 @@ object CompilerState { _ <- State.set( CompilerState[S]( a.errors ++ b.errors, + a.mangler |+| b.mangler, a.names |+| b.names, a.abilities |+| b.abilities, a.types |+| b.types, diff --git a/semantics/src/main/scala/aqua/semantics/ExprSem.scala b/semantics/src/main/scala/aqua/semantics/ExprSem.scala index 7fd390f8..7873db74 100644 --- a/semantics/src/main/scala/aqua/semantics/ExprSem.scala +++ b/semantics/src/main/scala/aqua/semantics/ExprSem.scala @@ -27,7 +27,7 @@ object ExprSem { L: LocationsAlgebra[S, G] ): Prog[G, Raw] = expr match { - case expr: AbilityIdExpr[S] => new AbilityIdSem(expr).program[G] + case expr: ServiceIdExpr[S] => new ServiceIdSem(expr).program[G] case expr: AssignmentExpr[S] => new AssignmentSem(expr).program[G] case expr: PushToStreamExpr[S] => new PushToStreamSem(expr).program[G] case expr: AliasExpr[S] => new AliasSem(expr).program[G] diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index 27ad39e0..11713e08 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -8,15 +8,11 @@ import aqua.raw.{Raw, RawContext, RawPart} import aqua.semantics.header.Picker import aqua.semantics.header.Picker.* import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState} -import aqua.semantics.rules.definitions.{ - DefinitionsAlgebra, - DefinitionsInterpreter, - DefinitionsState -} -import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra, LocationsState} -import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState} -import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState} -import aqua.semantics.rules.ValuesAlgebra +import aqua.semantics.rules.definitions.{DefinitionsAlgebra, DefinitionsInterpreter} +import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra} +import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter} +import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter} +import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter} import aqua.semantics.rules.errors.ReportErrors import aqua.semantics.rules.errors.ErrorsAlgebra import aqua.raw.ops.* @@ -306,17 +302,19 @@ object RawSemantics extends Logging { type Interpreter[S[_], A] = State[CompilerState[S], A] - def transpile[S[_]]( - ast: Ast[S] - )(implicit locations: LocationsAlgebra[S, Interpreter[S, *]]): Interpreter[S, Raw] = { + def transpile[S[_]](ast: Ast[S])(using + LocationsAlgebra[S, Interpreter[S, *]] + ): Interpreter[S, Raw] = { - implicit val typesInterpreter: TypesInterpreter[S, CompilerState[S]] = + given TypesAlgebra[S, Interpreter[S, *]] = new TypesInterpreter[S, CompilerState[S]] - implicit val abilitiesInterpreter: AbilitiesInterpreter[S, CompilerState[S]] = + given ManglerAlgebra[Interpreter[S, *]] = + new ManglerInterpreter[CompilerState[S]] + given AbilitiesAlgebra[S, Interpreter[S, *]] = new AbilitiesInterpreter[S, CompilerState[S]] - implicit val namesInterpreter: NamesInterpreter[S, CompilerState[S]] = + given NamesAlgebra[S, Interpreter[S, *]] = new NamesInterpreter[S, CompilerState[S]] - implicit val definitionsInterpreter: DefinitionsInterpreter[S, CompilerState[S]] = + given DefinitionsAlgebra[S, Interpreter[S, *]] = new DefinitionsInterpreter[S, CompilerState[S]] ast diff --git a/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala b/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala index 3991d7c7..e0798cfd 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/AbilitySem.scala @@ -1,9 +1,8 @@ package aqua.semantics.expr import aqua.parser.expr.AbilityExpr -import aqua.raw.{Raw, ServiceRaw, TypeRaw} +import aqua.raw.{Raw, TypeRaw} import aqua.parser.lexer.{Name, NamedTypeToken} -import aqua.raw.{Raw, ServiceRaw} import aqua.semantics.Prog import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra @@ -11,6 +10,7 @@ import aqua.semantics.rules.definitions.DefinitionsAlgebra import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.types.TypesAlgebra import aqua.types.{AbilityType, ArrowType, Type} + import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* diff --git a/semantics/src/main/scala/aqua/semantics/expr/ServiceSem.scala b/semantics/src/main/scala/aqua/semantics/expr/ServiceSem.scala index 0844547b..0d8162ff 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/ServiceSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/ServiceSem.scala @@ -8,49 +8,72 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.definitions.DefinitionsAlgebra import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.types.TypesAlgebra + +import cats.data.EitherT +import cats.syntax.either.* +import cats.syntax.option.* import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* +import cats.syntax.traverse.* +import cats.syntax.foldable.* import cats.syntax.applicative.* import cats.Monad class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal { - def program[Alg[_]: Monad](implicit + private def define[Alg[_]: Monad](using A: AbilitiesAlgebra[S, Alg], N: NamesAlgebra[S, Alg], T: TypesAlgebra[S, Alg], V: ValuesAlgebra[S, Alg], D: DefinitionsAlgebra[S, Alg] - ): Prog[Alg, Raw] = - Prog.after( - _ => - D.purgeArrows(expr.name).flatMap { - case Some(nel) => - val arrows = nel.map(kv => kv._1.value -> (kv._1, kv._2)).toNem - for { - defaultId <- expr.id - .map(v => V.valueToRaw(v)) - .getOrElse(None.pure[Alg]) - defineResult <- A.defineService( - expr.name, - arrows, - defaultId - ) - _ <- (expr.id zip defaultId) - .fold(().pure[Alg])(idV => - (V.ensureIsString(idV._1) >> A.setServiceId(expr.name, idV._1, idV._2)).map(_ => - () - ) - ) - } yield - if (defineResult) { - ServiceRaw(expr.name.value, arrows.map(_._2), defaultId) - } else Raw.empty("Service not created due to validation errors") - - case None => - Raw.error("Service has no arrows, fails").pure[Alg] - - } + ): EitherT[Alg, Raw, ServiceRaw] = for { + arrows <- EitherT.fromOptionF( + // TODO: Move to purgeDefs here, allow not only arrows + // from parsing, throw errors here + D.purgeArrows(expr.name), + Raw.error("Service has no arrows") ) + arrowsByName = arrows.map { case (name, arrow) => + name.value -> (name, arrow) + }.toNem + defaultId <- expr.id.traverse(id => + EitherT.fromOptionF( + V.valueToStringRaw(id), + Raw.error("Failed to resolve default service id") + ) + ) + serviceType <- EitherT.fromOptionF( + T.defineServiceType(expr.name, arrowsByName.toSortedMap.toMap), + Raw.error("Failed to define service type") + ) + arrowsDefs = arrows.map { case (name, _) => name.value -> name }.toNem + _ <- EitherT( + A.defineService( + expr.name, + arrowsDefs, + defaultId + ).map(defined => + Raw + .error("Service not created due to validation errors") + .asLeft + .whenA(!defined) + ) + ) + } yield ServiceRaw( + expr.name.value, + serviceType, + defaultId + ) + + def program[Alg[_]: Monad](using + A: AbilitiesAlgebra[S, Alg], + N: NamesAlgebra[S, Alg], + T: TypesAlgebra[S, Alg], + V: ValuesAlgebra[S, Alg], + D: DefinitionsAlgebra[S, Alg] + ): Prog[Alg, Raw] = Prog.after_( + define.value.map(_.merge) + ) } diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala deleted file mode 100644 index 3e62b52a..00000000 --- a/semantics/src/main/scala/aqua/semantics/expr/func/AbilityIdSem.scala +++ /dev/null @@ -1,30 +0,0 @@ -package aqua.semantics.expr.func - -import aqua.raw.Raw -import aqua.raw.ops.AbilityIdTag -import aqua.parser.expr.func.AbilityIdExpr -import aqua.semantics.Prog -import aqua.semantics.rules.ValuesAlgebra -import aqua.semantics.rules.abilities.AbilitiesAlgebra -import cats.Monad -import cats.syntax.applicative.* -import cats.syntax.flatMap.* -import cats.syntax.functor.* - -class AbilityIdSem[S[_]](val expr: AbilityIdExpr[S]) extends AnyVal { - - def program[Alg[_]: Monad](implicit - A: AbilitiesAlgebra[S, Alg], - V: ValuesAlgebra[S, Alg] - ): Prog[Alg, Raw] = - V.ensureIsString(expr.id) >> V.valueToRaw( - expr.id - ) >>= { - case Some(id) => - A.setServiceId(expr.ability, expr.id, id) as (AbilityIdTag( - id, - expr.ability.value - ).funcOpLeaf: Raw) - case _ => Raw.error("Cannot resolve ability ID").pure[Alg] - } -} 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 a5739e7f..a6bcc96d 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ArrowSem.scala @@ -13,11 +13,13 @@ import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.names.NamesAlgebra import aqua.semantics.rules.types.TypesAlgebra -import aqua.types.{ArrayType, ArrowType, CanonStreamType, ProductType, StreamType, Type} +import aqua.types.* import cats.Eval import cats.data.{Chain, NonEmptyList} import cats.free.{Cofree, Free} +import cats.data.OptionT +import cats.syntax.show.* import cats.syntax.applicative.* import cats.syntax.apply.* import cats.syntax.foldable.* @@ -64,16 +66,15 @@ class ArrowSem[S[_]](val expr: ArrowExpr[S]) extends AnyVal { res = bodyGen match { case FuncOp(bodyModel) => // TODO: wrap with local on...via... - val retsAndArgs = retValues zip funcArrow.codomain.toList - val argNames = funcArrow.domain.labelledData.map { case (name, _) => name } + val dataArgsNames = funcArrow.domain.labelledData.map { case (name, _) => name } val streamsThatReturnAsStreams = retsAndArgs.collect { case (VarRaw(n, StreamType(_)), StreamType(_)) => n }.toSet // Remove arguments, and values returned as streams - val localStreams = streamsInScope -- argNames -- streamsThatReturnAsStreams + val localStreams = streamsInScope -- dataArgsNames -- streamsThatReturnAsStreams // process stream that returns as not streams and all Apply*Raw val (bodyRets, retVals) = retsAndArgs.mapWithIndex { diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/OnSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/OnSem.scala index ea9c539d..56a4c4c1 100644 --- a/semantics/src/main/scala/aqua/semantics/expr/func/OnSem.scala +++ b/semantics/src/main/scala/aqua/semantics/expr/func/OnSem.scala @@ -10,7 +10,9 @@ import aqua.semantics.rules.ValuesAlgebra import aqua.semantics.rules.abilities.AbilitiesAlgebra import aqua.semantics.rules.types.TypesAlgebra import aqua.types.{BoxType, OptionType, ScalarType} + import cats.data.Chain +import cats.data.OptionT import cats.syntax.applicative.* import cats.syntax.apply.* import cats.syntax.flatMap.* @@ -49,24 +51,25 @@ class OnSem[S[_]](val expr: OnExpr[S]) extends AnyVal { object OnSem { - def beforeOn[S[_], Alg[_]: Monad](peerId: ValueToken[S], via: List[ValueToken[S]])(implicit + def beforeOn[S[_], Alg[_]: Monad]( + peerId: ValueToken[S], + via: List[ValueToken[S]] + )(using V: ValuesAlgebra[S, Alg], T: TypesAlgebra[S, Alg], A: AbilitiesAlgebra[S, Alg] ): Alg[List[ValueRaw]] = + // TODO: Remove ensureIsString, use valueToStringRaw V.ensureIsString(peerId) *> via .traverse(v => - V.valueToRaw(v).flatTap { - case Some(vm) => - vm.`type` match { - case _: BoxType => - T.ensureTypeMatches(v, OptionType(ScalarType.string), vm.`type`) - case _ => - T.ensureTypeMatches(v, ScalarType.string, vm.`type`) - } - case None => false.pure[Alg] + OptionT(V.valueToRaw(v)).filterF { vm => + val expectedType = vm.`type` match { + case _: BoxType => OptionType(ScalarType.string) + case _ => ScalarType.string + } + + T.ensureTypeMatches(v, expectedType, vm.`type`) } ) - .map(_.flatten) - + .getOrElse(List.empty) } diff --git a/semantics/src/main/scala/aqua/semantics/expr/func/ServiceIdSem.scala b/semantics/src/main/scala/aqua/semantics/expr/func/ServiceIdSem.scala new file mode 100644 index 00000000..7274c844 --- /dev/null +++ b/semantics/src/main/scala/aqua/semantics/expr/func/ServiceIdSem.scala @@ -0,0 +1,49 @@ +package aqua.semantics.expr.func + +import aqua.raw.Raw +import aqua.raw.ops.ServiceIdTag +import aqua.parser.expr.func.ServiceIdExpr +import aqua.semantics.Prog +import aqua.semantics.rules.ValuesAlgebra +import aqua.semantics.rules.abilities.AbilitiesAlgebra +import aqua.semantics.rules.types.TypesAlgebra +import aqua.semantics.rules.names.NamesAlgebra + +import cats.Monad +import cats.data.EitherT +import cats.syntax.either.* +import cats.syntax.applicative.* +import cats.syntax.flatMap.* +import cats.syntax.functor.* + +class ServiceIdSem[S[_]](val expr: ServiceIdExpr[S]) extends AnyVal { + + def program[Alg[_]: Monad](using + A: AbilitiesAlgebra[S, Alg], + V: ValuesAlgebra[S, Alg], + N: NamesAlgebra[S, Alg], + T: TypesAlgebra[S, Alg] + ): Prog[Alg, Raw] = ( + for { + id <- EitherT.fromOptionF( + V.valueToStringRaw(expr.id), + Raw.error("Can not resolve service ID") + ) + serviceType <- EitherT.fromOptionF( + T.resolveServiceType(expr.service), + Raw.error("Can not resolve service type") + ) + name <- EitherT.fromOptionF( + A.renameService(expr.service), + Raw.error("Can not set service ID") + ) + _ <- EitherT.liftF( + N.derive( + expr.service.asName.rename(name), + serviceType, + id.varNames + ) + ) + } yield ServiceIdTag(id, serviceType, name).funcOpLeaf + ).value.map(_.merge) +} diff --git a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala index c5acf483..a1410b4d 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/ValuesAlgebra.scala @@ -11,6 +11,7 @@ import aqua.semantics.rules.errors.ErrorsAlgebra import aqua.types.* import cats.Monad +import cats.data.OptionT import cats.data.Chain import cats.syntax.applicative.* import cats.syntax.apply.* @@ -33,23 +34,6 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using A: AbilitiesAlgebra[S, Alg] ) extends Logging { - def ensureIsString(v: ValueToken[S]): Alg[Boolean] = - ensureTypeMatches(v, LiteralType.string) - - def ensureTypeMatches(v: ValueToken[S], expected: Type): Alg[Boolean] = - resolveType(v).flatMap { - case Some(vt) => - T.ensureTypeMatches( - v, - expected, - vt - ) - case None => false.pure[Alg] - } - - def resolveType(v: ValueToken[S]): Alg[Option[Type]] = - valueToRaw(v).map(_.map(_.`type`)) - private def resolveSingleProperty(rootType: Type, op: PropertyOp[S]): Alg[Option[PropertyRaw]] = op match { case op: IntoField[S] => @@ -83,11 +67,20 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using LiteralRaw(l.value, t).some.pure[Alg] case VarToken(name) => - N.read(name).flatMap { + N.read(name, mustBeDefined = false).flatMap { case Some(t) => - VarRaw(name.value, t).some.pure[Alg] + VarRaw(name.value, t).some.pure case None => - None.pure[Alg] + (for { + t <- OptionT( + T.getType(name.value) + ).collect { case st: ServiceType => st } + // A hack to report name error, better to refactor + .flatTapNone(N.read(name)) + rename <- OptionT( + A.getServiceRename(name.asTypeToken) + ) + } yield VarRaw(rename, t)).value } case prop @ PropertyToken(value, properties) => @@ -107,35 +100,30 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using )(valueToRaw) case dvt @ NamedValueToken(typeName, fields) => - T.resolveType(typeName).flatMap { - case Some(resolvedType) => - for { - fieldsRawOp: NonEmptyMap[String, Option[ValueRaw]] <- fields.traverse(valueToRaw) - fieldsRaw: List[(String, ValueRaw)] = fieldsRawOp.toSortedMap.toList.collect { - case (n, Some(vr)) => n -> vr - } - rawFields = NonEmptyMap.fromMap(SortedMap.from(fieldsRaw)) - typeFromFieldsWithData = rawFields - .map(rf => - resolvedType match { - case struct @ StructType(_, _) => - ( - StructType(typeName.value, rf.map(_.`type`)), - Some(MakeStructRaw(rf, struct)) - ) - case scope @ AbilityType(_, _) => - ( - AbilityType(typeName.value, rf.map(_.`type`)), - Some(AbilityRaw(rf, scope)) - ) - } - ) - .getOrElse(BottomType -> None) - (typeFromFields, data) = typeFromFieldsWithData - isTypesCompatible <- T.ensureTypeMatches(dvt, resolvedType, typeFromFields) - } yield data.filter(_ => isTypesCompatible) - case _ => None.pure[Alg] - } + (for { + resolvedType <- OptionT(T.resolveType(typeName)) + fieldsGiven <- fields.traverse(value => OptionT(valueToRaw(value))) + fieldsGivenTypes = fieldsGiven.map(_.`type`) + generated <- OptionT.fromOption( + resolvedType match { + case struct: StructType => + ( + struct.copy(fields = fieldsGivenTypes), + MakeStructRaw(fieldsGiven, struct) + ).some + case ability: AbilityType => + ( + ability.copy(fields = fieldsGivenTypes), + AbilityRaw(fieldsGiven, ability) + ).some + case _ => none + } + ) + (genType, genData) = generated + data <- OptionT.whenM( + T.ensureTypeMatches(dvt, resolvedType, genType) + )(genData.pure) + } yield data).value case ct @ CollectionToken(_, values) => for { @@ -295,15 +283,15 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using } yield Option.when( leftChecked.isDefined && rightChecked.isDefined )( - CallArrowRaw( - ability = Some(id), - name = fn, - arguments = leftRaw :: rightRaw :: Nil, + CallArrowRaw.service( + abilityName = id, + serviceId = LiteralRaw.quote(id), + funcName = fn, baseType = ArrowType( ProductType(lType :: rType :: Nil), ProductType(resType :: Nil) ), - serviceId = Some(LiteralRaw.quote(id)) + arguments = leftRaw :: rightRaw :: Nil ) ) @@ -321,9 +309,24 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using } ) + def valueToTypedRaw(v: ValueToken[S], expectedType: Type): Alg[Option[ValueRaw]] = + OptionT(valueToRaw(v)) + .flatMap(raw => + OptionT.whenM( + T.ensureTypeMatches(v, expectedType, raw.`type`) + )(raw.pure) + ) + .value + + def valueToStringRaw(v: ValueToken[S]): Alg[Option[ValueRaw]] = + valueToTypedRaw(v, LiteralType.string) + + def ensureIsString(v: ValueToken[S]): Alg[Boolean] = + valueToStringRaw(v).map(_.isDefined) + private def callArrowFromAbility( ab: Name[S], - at: AbilityType, + at: NamedType, funcName: Name[S] ): Option[CallArrowRaw] = at.arrows .get(funcName.value) @@ -351,24 +354,23 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using ) )(ab => N.read(ab.asName, mustBeDefined = false).flatMap { - case Some(at: AbilityType) => - callArrowFromAbility(ab.asName, at, callArrow.funcName).pure + case Some(nt: (AbilityType | ServiceType)) => + callArrowFromAbility(ab.asName, nt, callArrow.funcName).pure case _ => T.getType(ab.value).flatMap { - case Some(at: AbilityType) => - callArrowFromAbility(ab.asName, at, callArrow.funcName).pure + case Some(st: ServiceType) => + OptionT(A.getServiceRename(ab)) + .subflatMap(rename => + callArrowFromAbility( + ab.asName.rename(rename), + st, + callArrow.funcName + ) + ) + .value case _ => - (A.getArrow(ab, callArrow.funcName), A.getServiceId(ab)).mapN { - case (Some(at), Right(sid)) => - CallArrowRaw - .service( - abilityName = ab.value, - serviceId = sid, - funcName = callArrow.funcName.value, - baseType = at - ) - .some - case (Some(at), Left(true)) => + A.getArrow(ab, callArrow.funcName).map { + case Some(at) => CallArrowRaw .ability( abilityName = ab.value, diff --git a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesAlgebra.scala index b48bdbde..59996224 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesAlgebra.scala @@ -2,7 +2,8 @@ package aqua.semantics.rules.abilities import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken} import aqua.raw.value.ValueRaw -import aqua.types.ArrowType +import aqua.types.{ArrowType, ServiceType} + import cats.InjectK import cats.data.{NonEmptyList, NonEmptyMap} @@ -10,15 +11,15 @@ trait AbilitiesAlgebra[S[_], Alg[_]] { def defineService( name: NamedTypeToken[S], - arrows: NonEmptyMap[String, (Name[S], ArrowType)], + arrowDefs: NonEmptyMap[String, Name[S]], defaultId: Option[ValueRaw] ): Alg[Boolean] def getArrow(name: NamedTypeToken[S], arrow: Name[S]): Alg[Option[ArrowType]] - def setServiceId(name: NamedTypeToken[S], id: ValueToken[S], vm: ValueRaw): Alg[Boolean] + def renameService(name: NamedTypeToken[S]): Alg[Option[String]] - def getServiceId(name: NamedTypeToken[S]): Alg[Either[Boolean, ValueRaw]] + def getServiceRename(name: NamedTypeToken[S]): Alg[Option[String]] def beginScope(token: Token[S]): Alg[Unit] diff --git a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesInterpreter.scala b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesInterpreter.scala index 23a8b5e0..69275e8f 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesInterpreter.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesInterpreter.scala @@ -5,27 +5,31 @@ import aqua.raw.value.ValueRaw import aqua.raw.{RawContext, ServiceRaw} import aqua.semantics.Levenshtein import aqua.semantics.rules.errors.ReportErrors +import aqua.semantics.rules.mangler.ManglerAlgebra import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.{abilities, StackInterpreter} -import aqua.types.ArrowType +import aqua.types.{ArrowType, ServiceType} import cats.data.{NonEmptyMap, State} import cats.syntax.functor.* +import cats.syntax.apply.* import cats.syntax.foldable.* import cats.syntax.traverse.* import cats.syntax.applicative.* +import cats.syntax.option.* import monocle.Lens import monocle.macros.GenLens -class AbilitiesInterpreter[S[_], X](implicit +class AbilitiesInterpreter[S[_], X](using lens: Lens[X, AbilitiesState[S]], error: ReportErrors[S, X], + mangler: ManglerAlgebra[State[X, *]], locations: LocationsAlgebra[S, State[X, *]] ) extends AbilitiesAlgebra[S, State[X, *]] { type SX[A] = State[X, A] - val stackInt = new StackInterpreter[S, X, AbilitiesState[S], AbilitiesState.Frame[S]]( + private val stackInt = new StackInterpreter[S, X, AbilitiesState[S], AbilitiesState.Frame[S]]( GenLens[AbilitiesState[S]](_.stack) ) @@ -33,11 +37,11 @@ class AbilitiesInterpreter[S[_], X](implicit override def defineService( name: NamedTypeToken[S], - arrows: NonEmptyMap[String, (Name[S], ArrowType)], + arrowDefs: NonEmptyMap[String, Name[S]], defaultId: Option[ValueRaw] ): SX[Boolean] = - getService(name.value).flatMap { - case Some(_) => + serviceExists(name.value).flatMap { + case true => getState .map(_.definitions.get(name.value).exists(_ == name)) .flatMap(exists => @@ -47,23 +51,14 @@ class AbilitiesInterpreter[S[_], X](implicit ).whenA(!exists) ) .as(false) - case None => + case false => for { - _ <- arrows.toNel.traverse_ { case (_, (n, arr)) => - report(n, "Service functions cannot have multiple results") - .whenA(arr.codomain.length > 1) - } - _ <- modify(s => - s.copy( - services = s.services - .updated(name.value, ServiceRaw(name.value, arrows.map(_._2), defaultId)), - definitions = s.definitions.updated(name.value, name) - ) - ) + _ <- modify(_.defineService(name, defaultId)) + // TODO: Is it used? _ <- locations.addTokenWithFields( name.value, name, - arrows.toNel.toList.map(t => t._1 -> t._2._1) + arrowDefs.toNel.toList ) } yield true } @@ -74,83 +69,50 @@ class AbilitiesInterpreter[S[_], X](implicit } override def getArrow(name: NamedTypeToken[S], arrow: Name[S]): SX[Option[ArrowType]] = - getService(name.value).map(_.map(_.arrows)).flatMap { - case Some(arrows) => - arrows(arrow.value) + getAbility(name.value).flatMap { + case Some(abCtx) => + abCtx.funcs + .get(arrow.value) .fold( report( arrow, Levenshtein.genMessage( - s"Service is found, but arrow '${arrow.value}' isn't found in scope", + s"Ability is found, but arrow '${arrow.value}' isn't found in scope", arrow.value, - arrows.value.keys.toNonEmptyList.toList + abCtx.funcs.keys.toList ) - ).as(Option.empty[ArrowType]) - )(a => addServiceArrowLocation(name, arrow).as(Some(a))) - case None => - getAbility(name.value).flatMap { - case Some(abCtx) => - abCtx.funcs - .get(arrow.value) - .fold( - report( - arrow, - Levenshtein.genMessage( - s"Ability is found, but arrow '${arrow.value}' isn't found in scope", - arrow.value, - abCtx.funcs.keys.toList - ) - ).as(Option.empty[ArrowType]) - ) { fn => - // TODO: add name and arrow separately - // TODO: find tokens somewhere - addServiceArrowLocation(name, arrow).as(Some(fn.arrow.`type`)) - } - case None => - report(name, "Ability with this name is undefined").as(Option.empty[ArrowType]) - } - } - - override def setServiceId(name: NamedTypeToken[S], id: ValueToken[S], vm: ValueRaw): SX[Boolean] = - getService(name.value).flatMap { - case Some(_) => - mapStackHeadM( - modify(st => st.copy(rootServiceIds = st.rootServiceIds.updated(name.value, id -> vm))) - .as(true) - )(h => (h.copy(serviceIds = h.serviceIds.updated(name.value, id -> vm)) -> true).pure) - case None => - report(name, "Service with this name is not registered, can't set its ID").as(false) - } - - override def getServiceId(name: NamedTypeToken[S]): SX[Either[Boolean, ValueRaw]] = - getService(name.value).flatMap { - case Some(_) => - getState.flatMap(st => - st.stack - .flatMap(_.serviceIds.get(name.value).map(_._2)) - .headOption orElse st.rootServiceIds - .get( - name.value - ) - .map(_._2) orElse st.services.get(name.value).flatMap(_.defaultId) match { - case None => - report( - name, - s"Service ID unresolved, use `${name.value} id` expression to set it" - ) - .as(Left[Boolean, ValueRaw](false)) - - case Some(v) => State.pure(Right(v)) + ).as(none) + ) { fn => + // TODO: add name and arrow separately + // TODO: find tokens somewhere + addServiceArrowLocation(name, arrow).as(fn.arrow.`type`.some) } - ) case None => - getAbility(name.value).flatMap { - case Some(_) => State.pure(Left[Boolean, ValueRaw](true)) - case None => - report(name, "Ability with this name is undefined").as( - Left[Boolean, ValueRaw](false) - ) - } + report(name, "Ability with this name is undefined").as(none) + } + + override def renameService(name: NamedTypeToken[S]): SX[Option[String]] = + serviceExists(name.value).flatMap { + case true => + mapStackHeadM( + name.value.pure + )(h => + mangler + .rename(name.value) + .map(newName => h.setServiceRename(name.value, newName) -> newName) + ).map(_.some) + case false => + report(name, "Service with this name is not registered").as(none) + } + + override def getServiceRename(name: NamedTypeToken[S]): State[X, Option[String]] = + ( + serviceExists(name.value), + getState.map(_.getServiceRename(name.value)) + ).flatMapN { + case (true, Some(rename)) => rename.some.pure + case (false, _) => report(name, "Service with this name is undefined").as(none) + case (_, None) => report(name, "Service ID is undefined").as(none) } override def beginScope(token: Token[S]): SX[Unit] = @@ -158,8 +120,8 @@ class AbilitiesInterpreter[S[_], X](implicit override def endScope(): SX[Unit] = stackInt.endScope - private def getService(name: String): SX[Option[ServiceRaw]] = - getState.map(_.services.get(name)) + private def serviceExists(name: String): SX[Boolean] = + getState.map(_.services(name)) private def getAbility(name: String): SX[Option[RawContext]] = getState.map(_.abilities.get(name)) diff --git a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesState.scala b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesState.scala index aa3e5cb9..37d04654 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesState.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/abilities/AbilitiesState.scala @@ -4,54 +4,81 @@ import aqua.raw.{RawContext, ServiceRaw} import aqua.raw.value.ValueRaw import aqua.parser.lexer.{Name, NamedTypeToken, Token, ValueToken} import aqua.types.ArrowType + import cats.Monoid +import cats.syntax.foldable.* +import cats.syntax.functor.* import cats.data.NonEmptyList +import aqua.parser.lexer.Token.name case class AbilitiesState[S[_]]( stack: List[AbilitiesState.Frame[S]] = Nil, - services: Map[String, ServiceRaw] = Map.empty, + services: Set[String] = Set.empty, abilities: Map[String, RawContext] = Map.empty, - rootServiceIds: Map[String, (ValueToken[S], ValueRaw)] = - Map.empty[String, (ValueToken[S], ValueRaw)], - definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]] + rootServiceIds: Map[String, ValueRaw] = Map(), + definitions: Map[String, NamedTypeToken[S]] = Map() ) { - def purgeArrows: Option[(NonEmptyList[(Name[S], ArrowType)], AbilitiesState[S])] = - stack match { - case sc :: tail => - NonEmptyList - .fromList(sc.arrows.values.toList) - .map(_ -> copy[S](sc.copy(arrows = Map.empty) :: tail)) - case _ => None - } + def defineService(name: NamedTypeToken[S], defaultId: Option[ValueRaw]): AbilitiesState[S] = + copy( + services = services + name.value, + definitions = definitions.updated(name.value, name), + rootServiceIds = rootServiceIds ++ defaultId.map(name.value -> _) + ) + + def getServiceRename(name: String): Option[String] = + stack.collectFirstSome(_.getServiceRename(name)) orElse + // Suppose that services without id + // resolved in scope are not renamed + rootServiceIds.get(name).as(name) + } object AbilitiesState { case class Frame[S[_]]( token: Token[S], - arrows: Map[String, (Name[S], ArrowType)] = Map.empty[String, (Name[S], ArrowType)], - serviceIds: Map[String, (ValueToken[S], ValueRaw)] = - Map.empty[String, (ValueToken[S], ValueRaw)] - ) + services: Map[String, Frame.ServiceState] = Map() + ) { - implicit def abilitiesStateMonoid[S[_]]: Monoid[AbilitiesState[S]] = - new Monoid[AbilitiesState[S]] { - override def empty: AbilitiesState[S] = AbilitiesState() - - override def combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] = - AbilitiesState( - Nil, - x.services ++ y.services, - x.abilities ++ y.abilities, - x.rootServiceIds ++ y.rootServiceIds, - x.definitions ++ y.definitions + def setServiceRename(name: String, rename: String): Frame[S] = + copy(services = + services.updated( + name, + Frame.ServiceState(rename) ) - } + ) + + def getServiceRename(name: String): Option[String] = + services.get(name).map(_.rename) + } + + object Frame { + + final case class ServiceState( + rename: String + ) + } + + given [S[_]]: Monoid[AbilitiesState[S]] with { + override def empty: AbilitiesState[S] = AbilitiesState() + + override def combine(x: AbilitiesState[S], y: AbilitiesState[S]): AbilitiesState[S] = + AbilitiesState( + Nil, + x.services ++ y.services, + x.abilities ++ y.abilities, + x.rootServiceIds ++ y.rootServiceIds, + x.definitions ++ y.definitions + ) + } def init[S[_]](context: RawContext): AbilitiesState[S] = AbilitiesState( - services = context.allServices, + services = context.allServices.keySet, + rootServiceIds = context.allServices.flatMap { case (name, service) => + service.defaultId.map(name -> _) + }, abilities = context.abilities // TODO is it the right way to collect abilities? Why? ) } diff --git a/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerAlgebra.scala new file mode 100644 index 00000000..4a620c75 --- /dev/null +++ b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerAlgebra.scala @@ -0,0 +1,5 @@ +package aqua.semantics.rules.mangler + +trait ManglerAlgebra[Alg[_]] { + def rename(name: String): Alg[String] +} diff --git a/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerInterpreter.scala b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerInterpreter.scala new file mode 100644 index 00000000..0eff5d80 --- /dev/null +++ b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerInterpreter.scala @@ -0,0 +1,27 @@ +package aqua.semantics.rules.mangler + +import cats.data.State +import monocle.Lens +import monocle.macros.GenLens + +class ManglerInterpreter[X](using + lens: Lens[X, ManglerState] +) extends ManglerAlgebra[State[X, *]] { + + override def rename(name: String): State[X, String] = + for { + s <- get + newName = LazyList + .from(0) + .map(i => s"$name-$i") + .dropWhile(s.isForbidden) + .head + _ <- modify(_.forbid(newName)) + } yield newName + + private lazy val get: State[X, ManglerState] = + State.get[X].map(lens.get) + + private def modify(f: ManglerState => ManglerState): State[X, Unit] = + State.modify[X](lens.modify(f)) +} diff --git a/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerState.scala b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerState.scala new file mode 100644 index 00000000..7d7f5dca --- /dev/null +++ b/semantics/src/main/scala/aqua/semantics/rules/mangler/ManglerState.scala @@ -0,0 +1,24 @@ +package aqua.semantics.rules.mangler + +import cats.kernel.Monoid + +final case class ManglerState( + forbidden: Set[String] = Set.empty +) { + + def isForbidden(name: String): Boolean = + forbidden.contains(name) + + def forbid(name: String): ManglerState = + copy(forbidden = forbidden + name) +} + +object ManglerState { + + given Monoid[ManglerState] with { + override val empty: ManglerState = ManglerState() + + override def combine(x: ManglerState, y: ManglerState): ManglerState = + ManglerState(forbidden = x.forbidden ++ y.forbidden) + } +} diff --git a/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala b/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala index d459cdd7..725db4af 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/types/TypesAlgebra.scala @@ -2,7 +2,7 @@ package aqua.semantics.rules.types import aqua.parser.lexer.* import aqua.raw.value.{PropertyRaw, ValueRaw} -import aqua.types.{AbilityType, ArrowType, NamedType, StructType, Type} +import aqua.types.* import cats.data.NonEmptyMap import cats.data.NonEmptyList @@ -15,11 +15,18 @@ trait TypesAlgebra[S[_], Alg[_]] { def resolveArrowDef(arrowDef: ArrowTypeToken[S]): Alg[Option[ArrowType]] + def resolveServiceType(name: NamedTypeToken[S]): Alg[Option[ServiceType]] + def defineAbilityType( name: NamedTypeToken[S], fields: Map[String, (Name[S], Type)] ): Alg[Option[AbilityType]] + def defineServiceType( + name: NamedTypeToken[S], + fields: Map[String, (Name[S], Type)] + ): Alg[Option[ServiceType]] + def defineStructType( name: NamedTypeToken[S], fields: Map[String, (Name[S], Type)] diff --git a/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala b/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala index d84c4934..14b32ca2 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/types/TypesInterpreter.scala @@ -13,16 +13,17 @@ import aqua.raw.value.{ import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.StackInterpreter import aqua.semantics.rules.errors.ReportErrors +import aqua.semantics.rules.types.TypesStateHelper.{TypeResolution, TypeResolutionError} import aqua.types.* import cats.data.Validated.{Invalid, Valid} -import cats.data.{Chain, NonEmptyList, NonEmptyMap, State} -import cats.instances.list.* +import cats.data.{Chain, NonEmptyList, NonEmptyMap, OptionT, State} import cats.syntax.applicative.* import cats.syntax.apply.* import cats.syntax.flatMap.* import cats.syntax.functor.* import cats.syntax.traverse.* +import cats.syntax.foldable.* import cats.{~>, Applicative} import cats.syntax.option.* import monocle.Lens @@ -44,18 +45,12 @@ class TypesInterpreter[S[_], X](implicit type ST[A] = State[X, A] - val resolver: (TypesState[S], NamedTypeToken[S]) => Option[ - (Type, List[(Token[S], NamedTypeToken[S])]) - ] = { (state, ctt) => - state.strict.get(ctt.value).map(t => (t, state.definitions.get(ctt.value).toList.map(ctt -> _))) - } - override def getType(name: String): State[X, Option[Type]] = getState.map(st => st.strict.get(name)) override def resolveType(token: TypeToken[S]): State[X, Option[Type]] = - getState.map(st => TypesStateHelper.resolveTypeToken(token, st, resolver)).flatMap { - case Some((typ, tokens)) => + getState.map(TypesStateHelper.resolveTypeToken(token)).flatMap { + case Some(TypeResolution(typ, tokens)) => val tokensLocs = tokens.map { case (t, n) => n.value -> t } locations.pointLocations(tokensLocs).as(typ.some) case None => @@ -64,88 +59,111 @@ class TypesInterpreter[S[_], X](implicit } override def resolveArrowDef(arrowDef: ArrowTypeToken[S]): State[X, Option[ArrowType]] = - getState.map(st => TypesStateHelper.resolveArrowDef(arrowDef, st, resolver)).flatMap { - case Valid(t) => - val (tt, tokens) = t - val tokensLocs = tokens.map { case (t, n) => - n.value -> t - } - locations.pointLocations(tokensLocs).map(_ => Some(tt)) + getState.map(TypesStateHelper.resolveArrowDef(arrowDef)).flatMap { + case Valid(TypeResolution(tt, tokens)) => + val tokensLocs = tokens.map { case (t, n) => n.value -> t } + locations.pointLocations(tokensLocs).as(tt.some) case Invalid(errs) => - errs - .foldLeft[ST[Option[ArrowType]]](State.pure(None)) { case (n, (tkn, hint)) => - report(tkn, hint) >> n - } + errs.traverse_ { case TypeResolutionError(token, hint) => + report(token, hint) + }.as(none) + } + + override def resolveServiceType(name: NamedTypeToken[S]): State[X, Option[ServiceType]] = + resolveType(name).flatMap { + case Some(serviceType: ServiceType) => + serviceType.some.pure + case Some(t) => + report(name, s"Type `$t` is not a service").as(none) + case None => + report(name, s"Type `${name.value}` is not defined").as(none) } override def defineAbilityType( name: NamedTypeToken[S], fields: Map[String, (Name[S], Type)] ): State[X, Option[AbilityType]] = - getState.map(_.definitions.get(name.value)).flatMap { - case Some(_) => report(name, s"Ability `${name.value}` was already defined").as(none) - case None => - val types = fields.view.mapValues { case (_, t) => t }.toMap - NonEmptyMap - .fromMap(SortedMap.from(types)) - .fold(report(name, s"Ability `${name.value}` has no fields").as(none))(nonEmptyFields => - val `type` = AbilityType(name.value, nonEmptyFields) - modify { st => - st.copy( - strict = st.strict.updated(name.value, `type`), - definitions = st.definitions.updated(name.value, name) - ) - }.as(`type`.some) - ) + ensureNameNotDefined(name.value, name, ifDefined = none) { + val types = fields.view.mapValues { case (_, t) => t }.toMap + NonEmptyMap + .fromMap(SortedMap.from(types)) + .fold(report(name, s"Ability `${name.value}` has no fields").as(none))(nonEmptyFields => + val `type` = AbilityType(name.value, nonEmptyFields) + + modify(_.defineType(name, `type`)).as(`type`.some) + ) } + override def defineServiceType( + name: NamedTypeToken[S], + fields: Map[String, (Name[S], Type)] + ): State[X, Option[ServiceType]] = + ensureNameNotDefined(name.value, name, ifDefined = none)( + fields.toList.traverse { + case (field, (fieldName, t: ArrowType)) => + OptionT + .when(t.codomain.length <= 1)(field -> t) + .flatTapNone( + report(fieldName, "Service functions cannot have multiple results") + ) + case (field, (fieldName, t)) => + OptionT( + report( + fieldName, + s"Field '$field' has unacceptable for service field type '$t'" + ).as(none) + ) + }.flatMapF(arrows => + NonEmptyMap + .fromMap(SortedMap.from(arrows)) + .fold( + report(name, s"Service `${name.value}` has no fields").as(none) + )(_.some.pure) + ).semiflatMap(nonEmptyArrows => + val `type` = ServiceType(name.value, nonEmptyArrows) + + modify(_.defineType(name, `type`)).as(`type`) + ).value + ) + override def defineStructType( name: NamedTypeToken[S], fields: Map[String, (Name[S], Type)] ): State[X, Option[StructType]] = - getState.map(_.definitions.get(name.value)).flatMap { - case Some(_) => report(name, s"Data `${name.value}` was already defined").as(none) - case None => - fields.toList.traverse { - case (field, (fieldName, t: DataType)) => - t match { - case _: StreamType => report(fieldName, s"Field '$field' has stream type").as(none) - case _ => (field -> t).some.pure[ST] - } - case (field, (fieldName, t)) => - report( - fieldName, - s"Field '$field' has unacceptable for struct field type '$t'" - ).as(none) - }.map(_.sequence.map(_.toMap)) - .flatMap( - _.map(SortedMap.from) - .flatMap(NonEmptyMap.fromMap) - .fold( - report(name, s"Struct `${name.value}` has no fields").as(none) - )(nonEmptyFields => - val `type` = StructType(name.value, nonEmptyFields) - modify { st => - st.copy( - strict = st.strict.updated(name.value, `type`), - definitions = st.definitions.updated(name.value, name) - ) - }.as(`type`.some) - ) - ) - } + ensureNameNotDefined(name.value, name, ifDefined = none)( + fields.toList.traverse { + case (field, (fieldName, t: DataType)) => + t match { + case _: StreamType => report(fieldName, s"Field '$field' has stream type").as(none) + case _ => (field -> t).some.pure[ST] + } + case (field, (fieldName, t)) => + report( + fieldName, + s"Field '$field' has unacceptable for struct field type '$t'" + ).as(none) + }.map(_.sequence.map(_.toMap)) + .flatMap( + _.map(SortedMap.from) + .flatMap(NonEmptyMap.fromMap) + .fold( + report(name, s"Struct `${name.value}` has no fields").as(none) + )(nonEmptyFields => + val `type` = StructType(name.value, nonEmptyFields) + + modify(_.defineType(name, `type`)).as(`type`.some) + ) + ) + ) override def defineAlias(name: NamedTypeToken[S], target: Type): State[X, Boolean] = getState.map(_.definitions.get(name.value)).flatMap { case Some(n) if n == name => State.pure(false) case Some(_) => report(name, s"Type `${name.value}` was already defined").as(false) case None => - modify(st => - st.copy( - strict = st.strict.updated(name.value, target), - definitions = st.definitions.updated(name.value, name) - ) - ).flatMap(_ => locations.addToken(name.value, name)).as(true) + modify(_.defineType(name, target)) + .productL(locations.addToken(name.value, name)) + .as(true) } override def resolveField(rootT: Type, op: IntoField[S]): State[X, Option[PropertyRaw]] = { @@ -465,4 +483,22 @@ class TypesInterpreter[S[_], X](implicit ).as(frame -> Nil) else (frame -> frame.retVals.getOrElse(Nil)).pure ) <* stack.endScope + + private def ensureNameNotDefined[A]( + name: String, + token: Token[S], + ifDefined: => A + )( + ifNotDefined: => State[X, A] + ): State[X, A] = getState + .map(_.definitions.get(name)) + .flatMap { + case Some(_) => + // TODO: Point to both locations here + report( + token, + s"Name `${name}` was already defined here" + ).as(ifDefined) + case None => ifNotDefined + } } diff --git a/semantics/src/main/scala/aqua/semantics/rules/types/TypesState.scala b/semantics/src/main/scala/aqua/semantics/rules/types/TypesState.scala index 6e6f0442..731d1cbd 100644 --- a/semantics/src/main/scala/aqua/semantics/rules/types/TypesState.scala +++ b/semantics/src/main/scala/aqua/semantics/rules/types/TypesState.scala @@ -9,129 +9,139 @@ import cats.data.Validated.{Invalid, Valid} import cats.data.{Chain, NonEmptyChain, ValidatedNec} import cats.kernel.Monoid import cats.syntax.option.* +import cats.syntax.traverse.* +import cats.syntax.validated.* +import cats.syntax.apply.* +import cats.syntax.bifunctor.* +import cats.syntax.functor.* +import cats.syntax.apply.* case class TypesState[S[_]]( - fields: Map[String, (Name[S], Type)] = Map.empty[String, (Name[S], Type)], - strict: Map[String, Type] = Map.empty[String, Type], - definitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]], + fields: Map[String, (Name[S], Type)] = Map(), + strict: Map[String, Type] = Map.empty, + definitions: Map[String, NamedTypeToken[S]] = Map(), stack: List[TypesState.Frame[S]] = Nil ) { def isDefined(t: String): Boolean = strict.contains(t) + + def defineType(name: NamedTypeToken[S], `type`: Type): TypesState[S] = + copy( + strict = strict.updated(name.value, `type`), + definitions = definitions.updated(name.value, name) + ) + + def getType(name: String): Option[Type] = + strict.get(name) + + def getTypeDefinition(name: String): Option[NamedTypeToken[S]] = + definitions.get(name) } object TypesStateHelper { - // TODO: an ugly return type, refactoring - // Returns type and a token with its definition - def resolveTypeToken[S[_]]( - tt: TypeToken[S], - state: TypesState[S], - resolver: ( - TypesState[S], - NamedTypeToken[S] - ) => Option[(Type, List[(Token[S], NamedTypeToken[S])])] - ): Option[(Type, List[(Token[S], NamedTypeToken[S])])] = + final case class TypeResolution[S[_], +T]( + `type`: T, + definitions: List[(Token[S], NamedTypeToken[S])] + ) + + final case class TypeResolutionError[S[_]]( + token: Token[S], + hint: String + ) + + def resolveTypeToken[S[_]](tt: TypeToken[S])( + state: TypesState[S] + ): Option[TypeResolution[S, Type]] = tt match { case TopBottomToken(_, isTop) => - (if (isTop) TopType else BottomType, Nil).some + val `type` = if (isTop) TopType else BottomType + + TypeResolution(`type`, Nil).some case ArrayTypeToken(_, dtt) => - resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => - (ArrayType(it), t) + resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) => + TypeResolution(ArrayType(it), t) } case StreamTypeToken(_, dtt) => - resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => - (StreamType(it), t) + resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) => + TypeResolution(StreamType(it), t) } case OptionTypeToken(_, dtt) => - resolveTypeToken(dtt, state, resolver).collect { case (it: DataType, t) => - (OptionType(it), t) - } - case ctt: NamedTypeToken[S] => - resolver(state, ctt) - case btt: BasicTypeToken[S] => (btt.value, Nil).some - case ArrowTypeToken(_, args, res) => - val strictArgs = - args.map(_._2).map(resolveTypeToken(_, state, resolver)).collect { - case Some((dt: DataType, t)) => - (dt, t) - } - val strictRes = - res.map(resolveTypeToken(_, state, resolver)).collect { case Some((dt: DataType, t)) => - (dt, t) - } - Option.when(strictRes.length == res.length && strictArgs.length == args.length) { - val (sArgs, argTokens) = strictArgs.unzip - val (sRes, resTokens) = strictRes.unzip - (ArrowType(ProductType(sArgs), ProductType(sRes)), argTokens.flatten ++ resTokens.flatten) + resolveTypeToken(dtt)(state).collect { case TypeResolution(it: DataType, t) => + TypeResolution(OptionType(it), t) } + case ntt: NamedTypeToken[S] => + val defs = state + .getTypeDefinition(ntt.value) + .toList + .map(ntt -> _) + + state + .getType(ntt.value) + .map(typ => TypeResolution(typ, defs)) + case btt: BasicTypeToken[S] => + TypeResolution(btt.value, Nil).some + case att: ArrowTypeToken[S] => + resolveArrowDef(att)(state).toOption } - def resolveArrowDef[S[_]]( - arrowTypeToken: ArrowTypeToken[S], - state: TypesState[S], - resolver: ( - TypesState[S], - NamedTypeToken[S] - ) => Option[(Type, List[(Token[S], NamedTypeToken[S])])] - ): ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])] = { - val resType = arrowTypeToken.res.map(resolveTypeToken(_, state, resolver)) + def resolveArrowDef[S[_]](arrowTypeToken: ArrowTypeToken[S])( + state: TypesState[S] + ): ValidatedNec[TypeResolutionError[S], TypeResolution[S, ArrowType]] = { + val res = arrowTypeToken.res.traverse(typeToken => + resolveTypeToken(typeToken)(state) + .toValidNec( + TypeResolutionError( + typeToken, + "Can not resolve the result type" + ) + ) + ) + val args = arrowTypeToken.args.traverse { case (argName, typeToken) => + resolveTypeToken(typeToken)(state) + .toValidNec( + TypeResolutionError( + typeToken, + "Can not resolve the argument type" + ) + ) + .map(argName.map(_.value) -> _) + } - NonEmptyChain - .fromChain(Chain.fromSeq(arrowTypeToken.res.zip(resType).collect { case (dt, None) => - dt -> "Cannot resolve the result type" - })) - .fold[ValidatedNec[(Token[S], String), (ArrowType, List[(Token[S], NamedTypeToken[S])])]] { - val (errs, argTypes) = arrowTypeToken.args.map { (argName, tt) => - resolveTypeToken(tt, state, resolver) - .toRight(tt -> s"Type unresolved") - .map(argName.map(_.value) -> _) - } - .foldLeft[ - ( - Chain[(Token[S], String)], - Chain[(Option[String], (Type, List[(Token[S], NamedTypeToken[S])]))] - ) - ]( - ( - Chain.empty, - Chain.empty[(Option[String], (Type, List[(Token[S], NamedTypeToken[S])]))] - ) - ) { - case ((errs, argTypes), Right(at)) => (errs, argTypes.append(at)) - case ((errs, argTypes), Left(e)) => (errs.append(e), argTypes) - } + (args, res).mapN { (args, res) => + val (argsLabeledTypes, argsTokens) = + args.map { case lbl -> TypeResolution(typ, tkn) => + (lbl, typ) -> tkn + }.unzip.map(_.flatten) + val (resTypes, resTokens) = + res.map { case TypeResolution(typ, tkn) => + typ -> tkn + }.unzip.map(_.flatten) - NonEmptyChain - .fromChain(errs) - .fold[ValidatedNec[ - (Token[S], String), - (ArrowType, List[(Token[S], NamedTypeToken[S])]) - ]]( - Valid { - val (labels, types) = argTypes.toList.unzip - val (resTypes, resTokens) = resType.flatten.unzip - ( - ArrowType( - ProductType.maybeLabelled(labels.zip(types.map(_._1))), - ProductType(resTypes) - ), - types.flatMap(_._2) ++ resTokens.flatten - ) - } - )(Invalid(_)) - }(Invalid(_)) + val typ = ArrowType( + ProductType.maybeLabelled(argsLabeledTypes), + ProductType(resTypes) + ) + val defs = (argsTokens ++ resTokens) + + TypeResolution(typ, defs) + } } } object TypesState { + final case class TypeDefinition[S[_]]( + token: NamedTypeToken[S], + `type`: Type + ) + case class Frame[S[_]]( token: ArrowTypeToken[S], arrowType: ArrowType, retVals: Option[List[ValueRaw]] ) - implicit def typesStateMonoid[S[_]]: Monoid[TypesState[S]] = new Monoid[TypesState[S]] { + given [S[_]]: Monoid[TypesState[S]] with { override def empty: TypesState[S] = TypesState() override def combine(x: TypesState[S], y: TypesState[S]): TypesState[S] = diff --git a/semantics/src/test/scala/aqua/semantics/ArrowSemSpec.scala b/semantics/src/test/scala/aqua/semantics/ArrowSemSpec.scala index 35d203cc..403545da 100644 --- a/semantics/src/test/scala/aqua/semantics/ArrowSemSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/ArrowSemSpec.scala @@ -15,7 +15,8 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers class ArrowSemSpec extends AnyFlatSpec with Matchers with EitherValues { - import Utils.* + + import Utils.{given, *} def program(arrowStr: String): Prog[State[CompilerState[cats.Id], *], Raw] = { import CompilerState.* diff --git a/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala b/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala index f1026bbe..252b6b59 100644 --- a/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/ClosureSemSpec.scala @@ -29,7 +29,7 @@ import org.scalatest.matchers.should.Matchers class ClosureSemSpec extends AnyFlatSpec with Matchers { - import Utils.* + import Utils.{given, *} val program: Prog[State[CompilerState[cats.Id], *], Raw] = { import CompilerState.* diff --git a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala index 49ddcd5d..453e2299 100644 --- a/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/SemanticsSpec.scala @@ -87,11 +87,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { def testServiceCallStr(str: String) = CallArrowRawTag - .service( - serviceId = LiteralRaw.quote("test"), - fnName = "testCallStr", + .ability( + abilityName = "Test", + funcName = "testCallStr", call = Call(LiteralRaw.quote(str) :: Nil, Nil), - name = "Test", arrowType = ArrowType( ProductType.labelled(("s" -> ScalarType.string) :: Nil), ProductType(ScalarType.string :: Nil) @@ -137,11 +136,10 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside { insideBody(script) { body => val arrowType = ArrowType(NilType, ConsType.cons(ScalarType.string, NilType)) val serviceCall = CallArrowRawTag - .service( - serviceId = LiteralRaw.quote("srv1"), - fnName = "fn1", + .ability( + abilityName = "A", + funcName = "fn1", call = emptyCall, - name = "A", arrowType = arrowType ) .leaf diff --git a/semantics/src/test/scala/aqua/semantics/Utils.scala b/semantics/src/test/scala/aqua/semantics/Utils.scala index 2117f546..2605c8d6 100644 --- a/semantics/src/test/scala/aqua/semantics/Utils.scala +++ b/semantics/src/test/scala/aqua/semantics/Utils.scala @@ -6,45 +6,35 @@ import aqua.parser.lift.Span import aqua.raw.{Raw, RawContext} import aqua.semantics.expr.func.ClosureSem import aqua.semantics.rules.errors.ReportErrors -import aqua.semantics.rules.abilities.{AbilitiesInterpreter, AbilitiesState} -import aqua.semantics.rules.locations.DummyLocationsInterpreter -import aqua.semantics.rules.names.{NamesInterpreter, NamesState} -import aqua.semantics.rules.types.{TypesInterpreter, TypesState} +import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState} +import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra} +import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState} +import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState} import aqua.types.* + import cats.data.State import cats.{~>, Id} import monocle.Lens import monocle.macros.GenLens import monocle.syntax.all.* +import aqua.semantics.rules.mangler.ManglerAlgebra +import aqua.semantics.rules.mangler.ManglerInterpreter object Utils { - implicit val re: ReportErrors[Id, CompilerState[Id]] = new ReportErrors[Id, CompilerState[Id]] { + given ManglerAlgebra[State[CompilerState[Id], *]] = + new ManglerInterpreter[CompilerState[Id]] - override def apply( - st: CompilerState[Id], - token: Token[Id], - hints: List[String] - ): CompilerState[Id] = - st.focus(_.errors).modify(_.append(RulesViolated(token, hints))) - } - - implicit val locationsInterpreter: DummyLocationsInterpreter[Id, CompilerState[Id]] = + given LocationsAlgebra[Id, State[CompilerState[Id], *]] = new DummyLocationsInterpreter[Id, CompilerState[Id]]() - implicit val ns: Lens[CompilerState[Id], NamesState[Id]] = GenLens[CompilerState[Id]](_.names) - - implicit val as: Lens[CompilerState[Id], AbilitiesState[Id]] = - GenLens[CompilerState[Id]](_.abilities) - implicit val ts: Lens[CompilerState[Id], TypesState[Id]] = GenLens[CompilerState[Id]](_.types) - - implicit val alg: NamesInterpreter[Id, CompilerState[Id]] = + given NamesAlgebra[Id, State[CompilerState[Id], *]] = new NamesInterpreter[Id, CompilerState[Id]] - implicit val typesInterpreter: TypesInterpreter[Id, CompilerState[Id]] = + given TypesAlgebra[Id, State[CompilerState[Id], *]] = new TypesInterpreter[Id, CompilerState[Id]] - implicit val abilitiesInterpreter: AbilitiesInterpreter[Id, CompilerState[Id]] = + given AbilitiesAlgebra[Id, State[CompilerState[Id], *]] = new AbilitiesInterpreter[Id, CompilerState[Id]] def spanToId: Span.S ~> Id = new (Span.S ~> Id) { diff --git a/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala index 684ca648..d0312c69 100644 --- a/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala +++ b/semantics/src/test/scala/aqua/semantics/ValuesAlgebraSpec.scala @@ -1,32 +1,17 @@ package aqua.semantics import aqua.semantics.rules.ValuesAlgebra -import aqua.semantics.rules.names.NamesState -import aqua.semantics.rules.abilities.AbilitiesState -import aqua.semantics.rules.types.TypesState -import aqua.semantics.rules.types.TypesAlgebra -import aqua.semantics.rules.abilities.AbilitiesInterpreter -import aqua.semantics.rules.names.NamesAlgebra -import aqua.semantics.rules.definitions.DefinitionsAlgebra -import aqua.semantics.rules.abilities.AbilitiesAlgebra -import aqua.semantics.rules.names.NamesInterpreter -import aqua.semantics.rules.definitions.DefinitionsInterpreter -import aqua.semantics.rules.types.TypesInterpreter -import aqua.semantics.rules.locations.LocationsAlgebra -import aqua.semantics.rules.locations.DummyLocationsInterpreter +import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState} +import aqua.semantics.rules.names.{NamesAlgebra, NamesInterpreter, NamesState} +import aqua.semantics.rules.definitions.{DefinitionsAlgebra, DefinitionsInterpreter} +import aqua.semantics.rules.types.{TypesAlgebra, TypesInterpreter, TypesState} +import aqua.semantics.rules.locations.{DummyLocationsInterpreter, LocationsAlgebra} +import aqua.semantics.rules.mangler.{ManglerAlgebra, ManglerInterpreter} import aqua.raw.value.{ApplyBinaryOpRaw, LiteralRaw} import aqua.raw.RawContext import aqua.types.* -import aqua.parser.lexer.{ - CollectionToken, - InfixToken, - LiteralToken, - Name, - PrefixToken, - ValueToken, - VarToken -} -import aqua.raw.value.ApplyUnaryOpRaw +import aqua.parser.lexer.* +import aqua.raw.value.* import aqua.parser.lexer.ValueToken.string import org.scalatest.flatspec.AnyFlatSpec @@ -50,6 +35,8 @@ class ValuesAlgebraSpec extends AnyFlatSpec with Matchers with Inside { given LocationsAlgebra[Id, Interpreter] = new DummyLocationsInterpreter[Id, CompilerState[Id]] + given ManglerAlgebra[Interpreter] = + new ManglerInterpreter[CompilerState[Id]] given TypesAlgebra[Id, Interpreter] = new TypesInterpreter[Id, CompilerState[Id]] given AbilitiesAlgebra[Id, Interpreter] = diff --git a/types/src/main/scala/aqua/types/Type.scala b/types/src/main/scala/aqua/types/Type.scala index 4a3befbc..1d816e13 100644 --- a/types/src/main/scala/aqua/types/Type.scala +++ b/types/src/main/scala/aqua/types/Type.scala @@ -243,31 +243,19 @@ case class OptionType(element: Type) extends BoxType { sealed trait NamedType extends Type { def name: String def fields: NonEmptyMap[String, Type] -} - -// Struct is an unordered collection of labelled types -// TODO: Make fields type `DataType` -case class StructType(name: String, fields: NonEmptyMap[String, Type]) - extends DataType with NamedType { - - override def toString: String = - s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" -} - -// Ability is an unordered collection of labelled types and arrows -case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType { /** - * Get all arrows defined in this ability and its sub-abilities. - * Paths to arrows are returned **without** ability name + * Get all arrows defined in this type and its sub-abilities. + * Paths to arrows are returned **without** type name * to allow renaming on call site. */ lazy val arrows: Map[String, ArrowType] = { - def getArrowsEval(path: Option[String], ability: AbilityType): Eval[List[(String, ArrowType)]] = - ability.fields.toNel.toList.flatTraverse { - case (abName, abType: AbilityType) => - val newPath = path.fold(abName)(AbilityType.fullName(_, abName)) - getArrowsEval(newPath.some, abType) + def getArrowsEval(path: Option[String], nt: NamedType): Eval[List[(String, ArrowType)]] = + nt.fields.toNel.toList.flatTraverse { + // sub-arrows could be in abilities or services + case (innerName, innerType: (ServiceType | AbilityType)) => + val newPath = path.fold(innerName)(AbilityType.fullName(_, innerName)) + getArrowsEval(newPath.some, innerType) case (aName, aType: ArrowType) => val newPath = path.fold(aName)(AbilityType.fullName(_, aName)) List(newPath -> aType).pure @@ -278,16 +266,17 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends } /** - * Get all abilities defined in this ability and its sub-abilities. - * Paths to abilities are returned **without** ability name + * Get all abilities defined in this type and its sub-abilities. + * Paths to abilities are returned **without** type name * to allow renaming on call site. */ lazy val abilities: Map[String, AbilityType] = { def getAbilitiesEval( path: Option[String], - ability: AbilityType + nt: NamedType ): Eval[List[(String, AbilityType)]] = - ability.fields.toNel.toList.flatTraverse { + nt.fields.toNel.toList.flatTraverse { + // sub-abilities could be only in abilities case (abName, abType: AbilityType) => val fullName = path.fold(abName)(AbilityType.fullName(_, abName)) getAbilitiesEval(fullName.some, abType).map( @@ -300,16 +289,17 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends } /** - * Get all variables defined in this ability and its sub-abilities. - * Paths to variables are returned **without** ability name + * Get all variables defined in this type and its sub-abilities. + * Paths to variables are returned **without** type name * to allow renaming on call site. */ lazy val variables: Map[String, DataType] = { def getVariablesEval( path: Option[String], - ability: AbilityType + nt: NamedType ): Eval[List[(String, DataType)]] = - ability.fields.toNel.toList.flatTraverse { + nt.fields.toNel.toList.flatTraverse { + // sub-variables could be only in abilities case (abName, abType: AbilityType) => val newPath = path.fold(abName)(AbilityType.fullName(_, abName)) getVariablesEval(newPath.some, abType) @@ -321,6 +311,25 @@ case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends getVariablesEval(None, this).value.toMap } +} + +// Struct is an unordered collection of labelled types +// TODO: Make fields type `DataType` +case class StructType(name: String, fields: NonEmptyMap[String, Type]) + extends DataType with NamedType { + + override def toString: String = + s"$name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" +} + +case class ServiceType(name: String, fields: NonEmptyMap[String, ArrowType]) extends NamedType { + + override def toString: String = + s"service $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" +} + +// Ability is an unordered collection of labelled types and arrows +case class AbilityType(name: String, fields: NonEmptyMap[String, Type]) extends NamedType { override def toString: String = s"ability $name{${fields.map(_.toString).toNel.toList.map(kv => kv._1 + ": " + kv._2).mkString(", ")}}" diff --git a/types/src/test/scala/aqua/types/TypeSpec.scala b/types/src/test/scala/aqua/types/TypeSpec.scala index 8c0dddbc..73616300 100644 --- a/types/src/test/scala/aqua/types/TypeSpec.scala +++ b/types/src/test/scala/aqua/types/TypeSpec.scala @@ -170,7 +170,7 @@ class TypeSpec extends AnyFlatSpec with Matchers { accepts(four, one) should be(false) } - "labeled types" should "create correc labels" in { + "labeled types" should "create correct labels" in { val cons = LabeledConsType( "arg1", ArrowType(