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:
InversionSpaces 2024-01-09 12:48:02 +01:00 committed by GitHub
parent 9aec470d38
commit ae32f80277
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 456 additions and 128 deletions

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View 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

View File

@ -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

View 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

View File

@ -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"]);

View File

@ -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);
}

View File

@ -0,0 +1,5 @@
import { nested } from "../../compiled/examples/recursiveStreams/nested.js";
export async function nestedCall(n: number): Promise<number[]> {
return await nested(n);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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(),
);
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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)

View File

@ -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),

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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(

View File

@ -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
}

View File

@ -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,

View File

@ -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 {

View File

@ -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("!=")

View File

@ -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)

View File

@ -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))
}
}

View File

@ -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],

View File

@ -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]
}

View File

@ -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),

View File

@ -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 =
"""