mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 14:40:17 +00:00
feat(compiler): for ... rec
[LNG-307] (#1026)
* Add parser * Add semantics * Add inlining * Add range test * Rewrite to for ... rec * Rewrite tests * Fix import * Add nested test * Remove only * Add yes|no test * Add multi rec test * Add pipeline test * Unignore tests * Change timeouts * Add remote rec test * Fix integration tests * Add parser test * Add semantics test * Add inlining test * Add comment
This commit is contained in:
parent
9aec470d38
commit
ae32f80277
@ -1,13 +0,0 @@
|
||||
|
||||
service YesNoService("yesno"):
|
||||
get() -> string
|
||||
|
||||
func recursiveStream() -> []string, []string:
|
||||
result: *string
|
||||
loop: *string
|
||||
loop <<- "yes"
|
||||
for l <- loop:
|
||||
if l == "yes":
|
||||
loop <- YesNoService.get()
|
||||
result <<- "success"
|
||||
<- result, loop
|
@ -0,0 +1,22 @@
|
||||
aqua MultiRec
|
||||
|
||||
export TestService, multiRecStream
|
||||
|
||||
service TestService("test-srv"):
|
||||
handle(i: i32) -> []i32
|
||||
|
||||
func multiRecStream(init: i32, target: i32) -> []i32:
|
||||
result: *string
|
||||
loop: *i32
|
||||
|
||||
loop <<- init
|
||||
for l <- loop rec:
|
||||
news <- TestService.handle(l)
|
||||
for n <- news:
|
||||
loop <<- n
|
||||
if l == target:
|
||||
result <<- "done"
|
||||
|
||||
join result!
|
||||
|
||||
<- loop
|
19
integration-tests/aqua/examples/recursiveStreams/nested.aqua
Normal file
19
integration-tests/aqua/examples/recursiveStreams/nested.aqua
Normal file
@ -0,0 +1,19 @@
|
||||
aqua Nested
|
||||
|
||||
export nested
|
||||
|
||||
func nested(n: u32) -> []u32:
|
||||
result: *u32
|
||||
iterator: *u32
|
||||
|
||||
iterator <<- 0
|
||||
for i <- iterator rec:
|
||||
if i < n:
|
||||
for j <- iterator rec:
|
||||
result <<- j
|
||||
iterator <<- i + 1
|
||||
|
||||
if n > 0:
|
||||
join result[n * (n + 1) / 2 - 1]
|
||||
|
||||
<- result
|
@ -0,0 +1,29 @@
|
||||
aqua Pipeline
|
||||
|
||||
export pipelineStream
|
||||
|
||||
func pipelineStream(init: i32, target: i32) -> []i32:
|
||||
result: *string
|
||||
|
||||
loop1: *i32
|
||||
loop2: *i32
|
||||
loop3: *i32
|
||||
|
||||
loop1 <<- init
|
||||
for l <- loop1 rec:
|
||||
if l < target:
|
||||
loop1 <<- l + 1
|
||||
loop2 <<- l * 3
|
||||
|
||||
for l <- loop2 rec:
|
||||
loop3 <<- l
|
||||
loop3 <<- l + 1
|
||||
loop3 <<- l + 2
|
||||
|
||||
for l <- loop3 rec:
|
||||
if l == target:
|
||||
result <<- "success"
|
||||
|
||||
join result!
|
||||
|
||||
<- loop3
|
18
integration-tests/aqua/examples/recursiveStreams/range.aqua
Normal file
18
integration-tests/aqua/examples/recursiveStreams/range.aqua
Normal file
@ -0,0 +1,18 @@
|
||||
aqua Range
|
||||
|
||||
export range
|
||||
|
||||
func range(a: i32, b: i32) -> []i32:
|
||||
result: *i32
|
||||
iterator: *i32
|
||||
|
||||
iterator <<- a
|
||||
for i <- iterator rec:
|
||||
if i < b:
|
||||
result <<- i
|
||||
iterator <<- i + 1
|
||||
|
||||
if b > a:
|
||||
join result[b - a - 1]
|
||||
|
||||
<- result
|
@ -0,0 +1,19 @@
|
||||
aqua RemoteRec
|
||||
|
||||
export RemoteSrv, remoteRecStream
|
||||
|
||||
service RemoteSrv("remote-srv"):
|
||||
handle(i: i32) -> i32
|
||||
|
||||
func remoteRecStream(init: i32, target: i32, friend: string, friendRelay: string) -> []i32:
|
||||
loop: *i32
|
||||
|
||||
loop <<- init
|
||||
for l <- loop rec:
|
||||
on friend via friendRelay:
|
||||
if l < target:
|
||||
loop <- RemoteSrv.handle(l)
|
||||
|
||||
join loop[target - init]
|
||||
|
||||
<- loop
|
21
integration-tests/aqua/examples/recursiveStreams/yesNo.aqua
Normal file
21
integration-tests/aqua/examples/recursiveStreams/yesNo.aqua
Normal file
@ -0,0 +1,21 @@
|
||||
aqua YesNo
|
||||
|
||||
export YesNoService, yesNoStream
|
||||
|
||||
service YesNoService("yesno"):
|
||||
get() -> string
|
||||
|
||||
func yesNoStream() -> []string:
|
||||
result: *string
|
||||
loop: *string
|
||||
|
||||
loop <<- "yes"
|
||||
for l <- loop rec:
|
||||
if l == "yes":
|
||||
loop <- YesNoService.get()
|
||||
else:
|
||||
result <<- "success"
|
||||
|
||||
join result!
|
||||
|
||||
<- loop
|
@ -90,7 +90,7 @@ import {
|
||||
streamArgsCall,
|
||||
modifyStreamCall,
|
||||
returnDerivedStreamCall,
|
||||
lng280BugWithForEmptyStreamFuncCall
|
||||
lng280BugWithForEmptyStreamFuncCall,
|
||||
} from "../examples/streamArgsCall.js";
|
||||
import { streamResultsCall } from "../examples/streamResultsCall.js";
|
||||
import { structuralTypingCall } from "../examples/structuralTypingCall.js";
|
||||
@ -135,7 +135,6 @@ import {
|
||||
joinIdxLocalCall,
|
||||
joinIdxRelayCall,
|
||||
} from "../examples/joinCall.js";
|
||||
import { recursiveStreamsCall } from "../examples/recursiveStreamsCall.js";
|
||||
import { renameVarsCall } from "../examples/renameVars.js";
|
||||
import {
|
||||
arraySugarCall,
|
||||
@ -161,6 +160,12 @@ import {
|
||||
returnArrowCall,
|
||||
returnArrowChainCall,
|
||||
} from "../examples/returnArrowCall.js";
|
||||
import { rangeCall } from "../examples/recursiveStreams/rangeCall.js";
|
||||
import { nestedCall } from "../examples/recursiveStreams/nestedCall.js";
|
||||
import { yesNoStreamCall } from "../examples/recursiveStreams/yesNoStreamCall.js";
|
||||
import { multiRecStreamCall } from "../examples/recursiveStreams/multiRecStreamCall.js";
|
||||
import { pipelineStreamCall } from "../examples/recursiveStreams/pipelineCall.js";
|
||||
import { remoteRecStreamCall } from "../examples/recursiveStreams/remoteRecCall.js";
|
||||
|
||||
var selfPeerId: string;
|
||||
var peer1: IFluenceClient;
|
||||
@ -217,6 +222,77 @@ describe("Testing examples", () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
describe("for ... rec", () => {
|
||||
const range = (start: number, end: number) =>
|
||||
Array.from({ length: end - start }, (v, k) => k + start);
|
||||
|
||||
it("range", async () => {
|
||||
for (const i of range(-5, 5)) {
|
||||
for (const j of range(-5, 5)) {
|
||||
const result = await rangeCall(i, j);
|
||||
if (i < j) {
|
||||
expect(result).toEqual(range(i, j));
|
||||
} else {
|
||||
expect(result).toEqual([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
/**
|
||||
* This test does not work due to Aqua VM
|
||||
*/
|
||||
it.skip("nested", async () => {
|
||||
for (const i of range(0, 10)) {
|
||||
const result = await nestedCall(i);
|
||||
expect(result).toEqual(range(0, i).flatMap((x) => range(0, x + 1)));
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
it("yes|no stream", async () => {
|
||||
for (const i of range(1, 10)) {
|
||||
const yesNo = await yesNoStreamCall(i);
|
||||
expect(yesNo).toEqual(
|
||||
range(0, i)
|
||||
.map((_) => "yes")
|
||||
.concat(["no"]),
|
||||
);
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
it("multi rec stream", async () => {
|
||||
const handle = (i: number) => {
|
||||
if (i % 3 === 0) return [i + 1];
|
||||
if (i % 3 === 1) return [i + 1, i + 2];
|
||||
return [];
|
||||
};
|
||||
for (const i of range(1, 10)) {
|
||||
const loop = await multiRecStreamCall(0, i, handle);
|
||||
range(0, i + 1).forEach((j) => {
|
||||
expect(loop).toContain(j);
|
||||
});
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
it("pipeline", async () => {
|
||||
for (const i of range(1, 10)) {
|
||||
const result = await pipelineStreamCall(0, i);
|
||||
expect(result.sort()).toEqual(range(0, i + 1));
|
||||
}
|
||||
}, 15000);
|
||||
|
||||
/**
|
||||
* This test does not work due to `for ... rec`
|
||||
* not taking topology into account
|
||||
*/
|
||||
it.skip("remote rec", async () => {
|
||||
for (const i of range(0, 10)) {
|
||||
const result = await remoteRecStreamCall(0, i, peer2);
|
||||
expect(result).toEqual(range(0, i + 1));
|
||||
}
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
it("callArrow.aqua args bug 426", async () => {
|
||||
let argResult = await reproArgsBug426Call();
|
||||
|
||||
@ -630,29 +706,41 @@ describe("Testing examples", () => {
|
||||
it.skip("streamArgs.aqua LNG-280 with for", async () => {
|
||||
let result = await lng280BugWithForCall();
|
||||
expect(result).toEqual([
|
||||
"valueUseStream",
|
||||
"valueReturnStream",
|
||||
"valueUseStream",
|
||||
"valueReturnStream",
|
||||
"valueUseStream",
|
||||
"valueReturnStream"
|
||||
"valueUseStream",
|
||||
"valueReturnStream",
|
||||
"valueUseStream",
|
||||
"valueReturnStream",
|
||||
"valueUseStream",
|
||||
"valueReturnStream",
|
||||
]);
|
||||
});
|
||||
|
||||
it("streamArgs.aqua LNG-280 with for and anonymous stream", async () => {
|
||||
let result = await lng280BugWithForAnonStreamCall();
|
||||
expect(result).toEqual([[1, 1], [1, 2], [1, 3], [1, 4], [1, 5]]);
|
||||
expect(result).toEqual([
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[1, 4],
|
||||
[1, 5],
|
||||
]);
|
||||
});
|
||||
|
||||
it("streamArgs.aqua LNG-280 with for and anonymous stream from function", async () => {
|
||||
let result = await lng280BugWithForEmptyStreamFuncCall();
|
||||
expect(result).toEqual([[1, 1], [1, 2], [1, 3], [1, 4], [1, 5]]);
|
||||
});
|
||||
let result = await lng280BugWithForEmptyStreamFuncCall();
|
||||
expect(result).toEqual([
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[1, 4],
|
||||
[1, 5],
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip("streamArgs.aqua return derived stream", async () => {
|
||||
let result = await returnDerivedStreamCall();
|
||||
expect(result).toEqual([1]);
|
||||
});
|
||||
let result = await returnDerivedStreamCall();
|
||||
expect(result).toEqual([1]);
|
||||
});
|
||||
|
||||
it("streamResults.aqua", async () => {
|
||||
let streamResultsResult = await streamResultsCall();
|
||||
@ -822,15 +910,6 @@ describe("Testing examples", () => {
|
||||
// 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"]);
|
||||
|
@ -0,0 +1,14 @@
|
||||
import {
|
||||
multiRecStream,
|
||||
registerTestService,
|
||||
} from "../../compiled/examples/recursiveStreams/multiRec.js";
|
||||
|
||||
export async function multiRecStreamCall(
|
||||
init: number,
|
||||
target: number,
|
||||
handle: (i: number) => number[],
|
||||
): Promise<number[]> {
|
||||
registerTestService({ handle });
|
||||
|
||||
return await multiRecStream(init, target);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { nested } from "../../compiled/examples/recursiveStreams/nested.js";
|
||||
|
||||
export async function nestedCall(n: number): Promise<number[]> {
|
||||
return await nested(n);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { pipelineStream } from "../../compiled/examples/recursiveStreams/pipeline.js";
|
||||
|
||||
export async function pipelineStreamCall(
|
||||
init: number,
|
||||
target: number,
|
||||
): Promise<number[]> {
|
||||
return await pipelineStream(init, target);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { range } from "../../compiled/examples/recursiveStreams/range.js";
|
||||
|
||||
export async function rangeCall(a: number, b: number): Promise<number[]> {
|
||||
return await range(a, b);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { IFluenceClient } from "@fluencelabs/js-client";
|
||||
import { remoteRecStream } from "../../compiled/examples/recursiveStreams/remoteRec.js";
|
||||
|
||||
export async function remoteRecStreamCall(
|
||||
init: number,
|
||||
target: number,
|
||||
peer: IFluenceClient,
|
||||
): Promise<number[]> {
|
||||
return await remoteRecStream(
|
||||
init,
|
||||
target,
|
||||
peer.getPeerId(),
|
||||
peer.getRelayPeerId(),
|
||||
);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import {
|
||||
yesNoStream,
|
||||
registerYesNoService,
|
||||
} from "../../compiled/examples/recursiveStreams/yesNo.js";
|
||||
|
||||
export async function yesNoStreamCall(limit: number): Promise<string[]> {
|
||||
let i = 1;
|
||||
registerYesNoService({
|
||||
get: () => {
|
||||
i += 1;
|
||||
return i > limit ? "no" : "yes";
|
||||
},
|
||||
});
|
||||
|
||||
return await yesNoStream();
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import {
|
||||
recursiveStream,
|
||||
registerYesNoService,
|
||||
} from "../compiled/examples/recursiveStreams.js";
|
||||
|
||||
export async function recursiveStreamsCall(): Promise<[string[], string[]]> {
|
||||
let i = 0;
|
||||
registerYesNoService({
|
||||
get: () => {
|
||||
i++;
|
||||
if (i > 3) {
|
||||
console.log("return no");
|
||||
return "no";
|
||||
} else {
|
||||
console.log("return yes");
|
||||
return "yes";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return await recursiveStream();
|
||||
}
|
@ -115,8 +115,8 @@ object RawValueInliner extends Logging {
|
||||
): State[S, (CallModel, Option[OpModel.Tree])] = {
|
||||
valueListToModel(call.args).flatMap { args =>
|
||||
if (flatStreamArguments)
|
||||
args.map{ arg =>
|
||||
TagInliner.flat(arg._1, arg._2, true)
|
||||
args.map { arg =>
|
||||
TagInliner.flat(arg._1, arg._2)
|
||||
}.sequence
|
||||
else
|
||||
State.pure(args)
|
||||
|
@ -149,11 +149,10 @@ object TagInliner extends Logging {
|
||||
|
||||
def flat[S: Mangler](
|
||||
vm: ValueModel,
|
||||
op: Option[OpModel.Tree],
|
||||
flatStream: Boolean
|
||||
op: Option[OpModel.Tree]
|
||||
): State[S, (ValueModel, Option[OpModel.Tree])] = {
|
||||
vm match {
|
||||
case v @ VarModel(n, StreamType(t), l) if flatStream =>
|
||||
case ValueModel.Stream(v @ VarModel(n, _, l), StreamType(t)) =>
|
||||
val canonName = n + "_canon"
|
||||
for {
|
||||
canonN <- Mangler[S].findAndForbidName(canonName)
|
||||
@ -203,7 +202,7 @@ object TagInliner extends Logging {
|
||||
peerIdDe <- valueToModel(peerId)
|
||||
viaDe <- valueListToModel(via.toList)
|
||||
viaDeFlattened <- viaDe.traverse { case (vm, tree) =>
|
||||
flat(vm, tree, true)
|
||||
flat(vm, tree)
|
||||
}
|
||||
(pid, pif) = peerIdDe
|
||||
(viaD, viaF) = viaDeFlattened.unzip
|
||||
@ -238,7 +237,10 @@ object TagInliner extends Logging {
|
||||
case ForTag(item, iterable, mode) =>
|
||||
for {
|
||||
vp <- valueToModel(iterable)
|
||||
flattened <- flat(vp._1, vp._2, true)
|
||||
flattened <- mode match {
|
||||
case ForTag.Mode.RecMode => State.pure(vp)
|
||||
case _ => flat(vp._1, vp._2)
|
||||
}
|
||||
(v, p) = flattened
|
||||
n <- Mangler[S].findAndForbidName(item)
|
||||
elementType = iterable.`type` match {
|
||||
@ -250,8 +252,8 @@ object TagInliner extends Logging {
|
||||
}
|
||||
_ <- Exports[S].resolved(item, VarModel(n, elementType))
|
||||
modeModel = mode match {
|
||||
case ForTag.Mode.Blocking => ForModel.Mode.Never
|
||||
case ForTag.Mode.NonBlocking => ForModel.Mode.Null
|
||||
case ForTag.Mode.SeqMode | ForTag.Mode.TryMode => ForModel.Mode.Null
|
||||
case ForTag.Mode.ParMode | ForTag.Mode.RecMode => ForModel.Mode.Never
|
||||
}
|
||||
} yield TagInlined.Single(
|
||||
model = ForModel(n, v, modeModel),
|
||||
|
@ -2245,7 +2245,7 @@ class ArrowInlinerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
)
|
||||
|
||||
val foldOp = ForTag
|
||||
.blocking(iVar.name, array)
|
||||
.par(iVar.name, array)
|
||||
.wrap(
|
||||
inFold,
|
||||
NextTag(iVar.name).leaf
|
||||
|
@ -1,15 +1,20 @@
|
||||
package aqua.model.inline
|
||||
|
||||
import aqua.model.{LiteralModel, OpModel, SeqModel}
|
||||
import aqua.model.ForModel
|
||||
import aqua.model.ValueModel
|
||||
import aqua.model.inline.TagInliner.TagInlined
|
||||
import aqua.model.inline.state.InliningState
|
||||
import aqua.model.{LiteralModel, OpModel, SeqModel}
|
||||
import aqua.raw.ops.ForTag
|
||||
import aqua.raw.ops.{Call, CanonicalizeTag, FlattenTag}
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.raw.value.VarRaw
|
||||
import aqua.types.{ScalarType, StreamType}
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import cats.syntax.show.*
|
||||
import org.scalatest.Inside
|
||||
import aqua.model.inline.TagInliner.TagInlined
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|
||||
@ -47,9 +52,31 @@ class TagInlinerSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
ValueRaw.Nil.value,
|
||||
ValueRaw.Nil.baseType
|
||||
)
|
||||
|
||||
|
||||
inside(inlined) { case TagInlined.Empty(prefix) =>
|
||||
prefix shouldBe None
|
||||
}
|
||||
}
|
||||
|
||||
"ForTag" should "not canonicalize iterable in RecMode" in {
|
||||
val iterableRaw = VarRaw("iterable", StreamType(ScalarType.string))
|
||||
val iterableModel = ValueModel.fromRaw(iterableRaw)
|
||||
|
||||
val tag = ForTag("i", iterableRaw, ForTag.Mode.RecMode)
|
||||
|
||||
val (state, inlined) = TagInliner
|
||||
.tagToModel[InliningState](tag)
|
||||
.run(
|
||||
InliningState(
|
||||
resolvedExports = Map(
|
||||
iterableRaw.name -> iterableModel
|
||||
)
|
||||
)
|
||||
)
|
||||
.value
|
||||
|
||||
inside(inlined) { case TagInlined.Single(ForModel(_, iter, ForModel.Mode.Never), _) =>
|
||||
iter shouldBe iterableModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,16 +183,24 @@ case class ForTag(item: String, iterable: ValueRaw, mode: ForTag.Mode) extends S
|
||||
|
||||
object ForTag {
|
||||
|
||||
/**
|
||||
* | Syntax | mode | fold last | canon | inner tag | par null wrap |
|
||||
* |-------------|:----:|:---------:|:-----:|:---------:|:-------------:|
|
||||
* | for ... | seq | null | + | seq | - |
|
||||
* | for ... par | par | never | + | par | + |
|
||||
* | for ... try | try | null | + | try | - |
|
||||
* | for ... rec | rec | never | - | par | + |
|
||||
* | parseq ... | par | never | + | par | - |
|
||||
*/
|
||||
enum Mode {
|
||||
case Blocking
|
||||
case NonBlocking
|
||||
case ParMode, SeqMode, TryMode, RecMode
|
||||
}
|
||||
|
||||
def blocking(item: String, iterable: ValueRaw): ForTag =
|
||||
ForTag(item, iterable, Mode.Blocking)
|
||||
def par(item: String, iterable: ValueRaw): ForTag =
|
||||
ForTag(item, iterable, Mode.ParMode)
|
||||
|
||||
def nonBlocking(item: String, iterable: ValueRaw): ForTag =
|
||||
ForTag(item, iterable, Mode.NonBlocking)
|
||||
def seq(item: String, iterable: ValueRaw): ForTag =
|
||||
ForTag(item, iterable, Mode.SeqMode)
|
||||
}
|
||||
|
||||
case class CallArrowRawTag(
|
||||
|
@ -78,7 +78,7 @@ object ValueModel {
|
||||
|
||||
def unapply(vm: VarModel): Option[(VarModel, StreamType)] =
|
||||
vm match {
|
||||
case vm@VarModel(_, t: StreamType, _) =>
|
||||
case vm @ VarModel(_, t: StreamType, _) =>
|
||||
(vm, t).some
|
||||
case _ => none
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ case class ArgsFromService(dataServiceId: ValueRaw) extends ArgsProvider {
|
||||
)
|
||||
.leaf,
|
||||
ForTag
|
||||
.nonBlocking(item, VarRaw(iter, ArrayType(t.element)))
|
||||
.seq(item, VarRaw(iter, ArrayType(t.element)))
|
||||
.wrap(
|
||||
SeqTag.wrap(
|
||||
PushToStreamTag(VarRaw(item, t.element), Call.Export(varName, t)).leaf,
|
||||
|
@ -6,12 +6,13 @@ import aqua.parser.lexer.Token.*
|
||||
import aqua.parser.lexer.{Name, ValueToken}
|
||||
import aqua.parser.lift.LiftParser
|
||||
import aqua.parser.lift.LiftParser.*
|
||||
import cats.parse.Parser as P
|
||||
import cats.syntax.comonad.*
|
||||
import cats.{~>, Comonad}
|
||||
import aqua.parser.lift.Span
|
||||
import aqua.parser.lift.Span.{P0ToSpan, PToSpan}
|
||||
|
||||
import cats.parse.Parser as P
|
||||
import cats.syntax.comonad.*
|
||||
import cats.{Comonad, ~>}
|
||||
|
||||
case class ForExpr[F[_]](
|
||||
item: Name[F],
|
||||
iterable: ValueToken[F],
|
||||
@ -23,12 +24,16 @@ case class ForExpr[F[_]](
|
||||
}
|
||||
|
||||
object ForExpr extends Expr.AndIndented {
|
||||
enum Mode { case ParMode, TryMode }
|
||||
enum Mode { case ParMode, TryMode, RecMode }
|
||||
|
||||
override def validChildren: List[Expr.Lexem] = ArrowExpr.funcChildren
|
||||
|
||||
private lazy val modeP: P[Mode] =
|
||||
(` ` *> (`par`.as(Mode.ParMode) | `try`.as(Mode.TryMode)).lift).map(_.extract)
|
||||
(` ` *> (
|
||||
`par`.as(Mode.ParMode) |
|
||||
`try`.as(Mode.TryMode) |
|
||||
`rec`.as(Mode.RecMode)
|
||||
).lift).map(_.extract)
|
||||
|
||||
override def p: P[ForExpr[Span.S]] =
|
||||
((`for` *> ` ` *> Name.p <* ` <- `) ~ ValueToken.`value` ~ modeP.?).map {
|
||||
|
@ -56,6 +56,7 @@ object Token {
|
||||
val `via`: P[Unit] = P.string("via")
|
||||
val `%init_peer_id%` : P[Unit] = P.string("%init_peer_id%")
|
||||
val `for`: P[Unit] = P.string("for")
|
||||
val `rec`: P[Unit] = P.string("rec")
|
||||
val `if`: P[Unit] = P.string("if")
|
||||
val `eqs`: P[Unit] = P.string("==")
|
||||
val `neq`: P[Unit] = P.string("!=")
|
||||
|
@ -67,6 +67,9 @@ object AquaSpec {
|
||||
def toBool(n: Boolean): LiteralToken[Id] = LiteralToken[Id](n.toString, bool)
|
||||
def toStr(n: String): LiteralToken[Id] = LiteralToken[Id]("\"" + n + "\"", string)
|
||||
|
||||
def toArr(arr: List[ValueToken[Id]]): CollectionToken[Id] =
|
||||
CollectionToken[Id](CollectionToken.Mode.ArrayMode, arr)
|
||||
|
||||
def toNamedType(str: String): NamedTypeToken[Id] = NamedTypeToken[Id](str)
|
||||
def toArrayType(str: String): ArrayTypeToken[Id] = ArrayTypeToken[Id]((), str)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package aqua.parser
|
||||
|
||||
import aqua.AquaSpec
|
||||
import aqua.parser.expr.func.ForExpr
|
||||
|
||||
import cats.Id
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
@ -9,30 +10,48 @@ import org.scalatest.matchers.should.Matchers
|
||||
class ForExprSpec extends AnyFlatSpec with Matchers with AquaSpec {
|
||||
import AquaSpec.{given, *}
|
||||
|
||||
def forTestSuite(
|
||||
modeStr: String,
|
||||
mode: Option[ForExpr.Mode]
|
||||
): Unit = {
|
||||
parseFor(s"for some <- 1$modeStr") should be(
|
||||
ForExpr[Id]("some", toNumber(1), mode)
|
||||
)
|
||||
|
||||
parseFor(s"for some <- false$modeStr") should be(
|
||||
ForExpr[Id]("some", toBool(false), mode)
|
||||
)
|
||||
|
||||
parseFor(s"for some <- \"a\"$modeStr") should be(
|
||||
ForExpr[Id]("some", toStr("a"), mode)
|
||||
)
|
||||
|
||||
parseFor(s"for i <- []$modeStr") should be(
|
||||
ForExpr[Id]("i", toArr(Nil), mode)
|
||||
)
|
||||
|
||||
parseFor(s"for i <- [1, 2, 3]$modeStr") should be(
|
||||
ForExpr[Id]("i", toArr(List(toNumber(1), toNumber(2), toNumber(3))), mode)
|
||||
)
|
||||
|
||||
parseFor(s"for i <- stream$modeStr") should be(
|
||||
ForExpr[Id]("i", toVar("stream"), mode)
|
||||
)
|
||||
}
|
||||
|
||||
"for expression" should "be parsed" in {
|
||||
parseFor("for some <- \"a\"") should be(
|
||||
ForExpr[Id]("some", toStr("a"), None)
|
||||
)
|
||||
forTestSuite("", None)
|
||||
}
|
||||
|
||||
parseFor("for some <- \"a\"") should be(
|
||||
ForExpr[Id]("some", toStr("a"), None)
|
||||
)
|
||||
"for par expression" should "be parsed" in {
|
||||
forTestSuite(" par", Some(ForExpr.Mode.ParMode))
|
||||
}
|
||||
|
||||
parseFor("for some <- 1") should be(
|
||||
ForExpr[Id]("some", toNumber(1), None)
|
||||
)
|
||||
|
||||
parseFor("for some <- false") should be(
|
||||
ForExpr[Id]("some", toBool(false), None)
|
||||
)
|
||||
|
||||
parseFor("for some <- false par") should be(
|
||||
ForExpr[Id]("some", toBool(false), Some(ForExpr.Mode.ParMode))
|
||||
)
|
||||
|
||||
parseFor("for some <- false try") should be(
|
||||
ForExpr[Id]("some", toBool(false), Some(ForExpr.Mode.TryMode))
|
||||
)
|
||||
"for try expression" should "be parsed" in {
|
||||
forTestSuite(" try", Some(ForExpr.Mode.TryMode))
|
||||
}
|
||||
|
||||
"for rec expression" should "be parsed" in {
|
||||
forTestSuite(" rec", Some(ForExpr.Mode.RecMode))
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package aqua.semantics
|
||||
|
||||
import aqua.raw.Raw
|
||||
import aqua.parser.Expr
|
||||
import aqua.parser.expr.*
|
||||
import aqua.parser.expr.func.*
|
||||
import aqua.raw.Raw
|
||||
import aqua.semantics.expr.*
|
||||
import aqua.semantics.expr.func.*
|
||||
import aqua.semantics.rules.ValuesAlgebra
|
||||
@ -20,7 +20,7 @@ object ExprSem {
|
||||
|
||||
def getProg[S[_], G[_]: Monad](
|
||||
expr: Expr[S]
|
||||
)(implicit
|
||||
)(using
|
||||
A: AbilitiesAlgebra[S, G],
|
||||
N: NamesAlgebra[S, G],
|
||||
T: TypesAlgebra[S, G],
|
||||
|
@ -1,11 +1,10 @@
|
||||
package aqua.semantics.expr.func
|
||||
|
||||
import aqua.parser.expr.func.ForExpr
|
||||
import aqua.parser.expr.func.ForExpr.Mode
|
||||
import aqua.parser.lexer.{Name, ValueToken}
|
||||
import aqua.raw.Raw
|
||||
import aqua.raw.ops.*
|
||||
import aqua.raw.ops.ForTag
|
||||
import aqua.raw.ops.ForTag.Mode
|
||||
import aqua.raw.value.ValueRaw
|
||||
import aqua.semantics.Prog
|
||||
import aqua.semantics.expr.func.FuncOpSem
|
||||
@ -40,18 +39,17 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal {
|
||||
(iterable, ops) match {
|
||||
case (Some(vm), FuncOp(op)) =>
|
||||
FuncOpSem.restrictStreamsInScope(op).map { restricted =>
|
||||
val innerTag = expr.mode.fold(SeqTag) {
|
||||
case ForExpr.Mode.ParMode => ParTag
|
||||
case ForExpr.Mode.TryMode => TryTag
|
||||
val mode = expr.mode.fold(ForTag.Mode.SeqMode) {
|
||||
case ForExpr.Mode.ParMode => ForTag.Mode.ParMode
|
||||
case ForExpr.Mode.TryMode => ForTag.Mode.TryMode
|
||||
case ForExpr.Mode.RecMode => ForTag.Mode.RecMode
|
||||
}
|
||||
|
||||
/**
|
||||
* `for ... par` => blocking (`never` as `last` in `fold`)
|
||||
* `for` and `for ... try` => non blocking (`null` as `last` in `fold`)
|
||||
*/
|
||||
val mode = expr.mode.fold(ForTag.Mode.NonBlocking) {
|
||||
case ForExpr.Mode.ParMode => ForTag.Mode.Blocking
|
||||
case Mode.TryMode => ForTag.Mode.NonBlocking
|
||||
val innerTag = mode match {
|
||||
case ForTag.Mode.SeqMode => SeqTag
|
||||
case ForTag.Mode.ParMode => ParTag
|
||||
case ForTag.Mode.TryMode => TryTag
|
||||
case ForTag.Mode.RecMode => ParTag
|
||||
}
|
||||
|
||||
val forTag = ForTag(expr.item.value, vm, mode).wrap(
|
||||
@ -61,9 +59,15 @@ class ForSem[S[_]](val expr: ForExpr[S]) extends AnyVal {
|
||||
)
|
||||
)
|
||||
|
||||
// Fix: continue execution after fold par immediately, without finding a path out from par branches
|
||||
if (innerTag == ParTag) ParTag.Detach.wrap(forTag).toFuncOp
|
||||
else forTag.toFuncOp
|
||||
// Fix: continue execution after fold par immediately,
|
||||
// without finding a path out from par branches
|
||||
val result = mode match {
|
||||
case ForTag.Mode.ParMode | ForTag.Mode.RecMode =>
|
||||
ParTag.Detach.wrap(forTag)
|
||||
case _ => forTag
|
||||
}
|
||||
|
||||
result.toFuncOp
|
||||
}
|
||||
case _ => Raw.error("Wrong body of the `for` expression").pure[F]
|
||||
}
|
||||
|
@ -64,11 +64,11 @@ class ParSeqSem[S[_]](val expr: ParSeqExpr[S]) extends AnyVal {
|
||||
strategy = OnTag.ReturnStrategy.Relay.some
|
||||
)
|
||||
/**
|
||||
* `parseq` => blocking (`never` as `last` in `fold`)
|
||||
* `parseq` => par (`never` as `last` in `fold`)
|
||||
* So that peer initiating `parseq` would not continue execution past it
|
||||
*/
|
||||
tag = ForTag
|
||||
.blocking(expr.item.value, vm)
|
||||
.par(expr.item.value, vm)
|
||||
.wrap(
|
||||
ParTag.wrap(
|
||||
onTag.wrap(restricted),
|
||||
|
@ -607,7 +607,7 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
|""".stripMargin
|
||||
|
||||
insideBody(script) { body =>
|
||||
matchSubtree(body) { case (ForTag("p", _, ForTag.Mode.Blocking), forTag) =>
|
||||
matchSubtree(body) { case (ForTag("p", _, ForTag.Mode.ParMode), forTag) =>
|
||||
matchChildren(forTag) { case (ParTag, parTag) =>
|
||||
matchChildren(parTag)(
|
||||
{ case (OnTag(_, _, strat), _) =>
|
||||
@ -620,6 +620,30 @@ class SemanticsSpec extends AnyFlatSpec with Matchers with Inside {
|
||||
}
|
||||
}
|
||||
|
||||
it should "generate right model for `for ... rec`" in {
|
||||
val script = """
|
||||
|func test():
|
||||
| stream: *i32
|
||||
| for i <- stream rec:
|
||||
| stream <<- i
|
||||
|""".stripMargin
|
||||
|
||||
insideBody(script) { body =>
|
||||
matchSubtree(body) { case (ForTag("i", stream, ForTag.Mode.RecMode), forTag) =>
|
||||
stream.`type` shouldBe StreamType(ScalarType.i32)
|
||||
matchChildren(forTag) { case (ParTag, parTag) =>
|
||||
matchChildren(parTag)(
|
||||
{ case (PushToStreamTag(VarRaw(varName, _), Call.Export(streamName, _)), _) =>
|
||||
varName shouldBe "i"
|
||||
streamName shouldBe "stream"
|
||||
},
|
||||
{ case (NextTag("i"), _) => }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "forbid abilities or streams in struct fields" in {
|
||||
val scriptAbility =
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user