From 17c78c454a22461783eb10a5feaecc98eaa8ff01 Mon Sep 17 00:00:00 2001
From: Emiliano Bonilla <56323762+emilbon99@users.noreply.github.com>
Date: Wed, 4 Feb 2026 18:36:15 -0700
Subject: [PATCH 1/3] [arc/go] - fixed stateful variable expression analysis
---
arc/go/analyzer/expression/expression_test.go | 14 +-
arc/go/analyzer/statement/statement.go | 5 +
arc/go/runtime/wasm/wasm_test.go | 143 ++++++++++++++
arc/go/text/analyze.go | 2 +-
arc/go/text/text_test.go | 183 ++++++++++++------
5 files changed, 283 insertions(+), 64 deletions(-)
diff --git a/arc/go/analyzer/expression/expression_test.go b/arc/go/analyzer/expression/expression_test.go
index 8ac41cc215..37352986b9 100644
--- a/arc/go/analyzer/expression/expression_test.go
+++ b/arc/go/analyzer/expression/expression_test.go
@@ -934,11 +934,13 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "ox_pt_1",
Type: types.Chan(types.I32()),
+ ID: 20001,
},
"ox_pt_2": symbol.Symbol{
Kind: symbol.KindChannel,
Name: "ox_pt_2",
Type: types.Chan(types.I32()),
+ ID: 20002,
},
}
ctx := context.CreateRoot(bCtx, ast, resolver)
@@ -957,11 +959,13 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "ox_pt_1",
Type: types.Chan(types.I32()),
+ ID: 20003,
},
"ox_pt_2": symbol.Symbol{
Kind: symbol.KindChannel,
Name: "ox_pt_2",
Type: types.Chan(types.F32()),
+ ID: 20004,
},
}
ctx := context.CreateRoot(bCtx, ast, resolver)
@@ -982,6 +986,7 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "ox_pt_1",
Type: types.Chan(types.I32()),
+ ID: 20005,
},
}
ctx := context.CreateRoot(bCtx, ast, resolver)
@@ -1002,6 +1007,7 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "sensor",
Type: types.Chan(types.F32()),
+ ID: 20006,
},
}),
Entry("comparison", `
@@ -1013,6 +1019,7 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "sensor",
Type: types.Chan(types.F32()),
+ ID: 20007,
},
}),
Entry("multiple channels in expression", `
@@ -1020,9 +1027,9 @@ var _ = Describe("Expressions", func() {
return (temp1 + temp2 + temp3) / 3
}
`, symbol.MapResolver{
- "temp1": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp1", Type: types.Chan(types.F64())},
- "temp2": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp2", Type: types.Chan(types.F64())},
- "temp3": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp3", Type: types.Chan(types.F64())},
+ "temp1": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp1", Type: types.Chan(types.F64()), ID: 20008},
+ "temp2": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp2", Type: types.Chan(types.F64()), ID: 20009},
+ "temp3": symbol.Symbol{Kind: symbol.KindChannel, Name: "temp3", Type: types.Chan(types.F64()), ID: 20010},
}),
)
@@ -1032,6 +1039,7 @@ var _ = Describe("Expressions", func() {
Kind: symbol.KindChannel,
Name: "sensor",
Type: types.Chan(types.F32()),
+ ID: 20011,
},
}
expectFailure(`
diff --git a/arc/go/analyzer/statement/statement.go b/arc/go/analyzer/statement/statement.go
index d65575ead4..9296a0d5d9 100644
--- a/arc/go/analyzer/statement/statement.go
+++ b/arc/go/analyzer/statement/statement.go
@@ -240,6 +240,11 @@ func analyzeStatefulVariable(ctx context.Context[parser.IStatefulVariableContext
})
return
}
+ // Stateful variables store VALUES, not channel references.
+ // If initialized from a channel, unwrap to get the value type.
+ if varType.Kind == types.KindChan {
+ varType = varType.Unwrap()
+ }
_, err := ctx.Scope.Add(ctx, symbol.Symbol{
Name: name,
Kind: symbol.KindStatefulVariable,
diff --git a/arc/go/runtime/wasm/wasm_test.go b/arc/go/runtime/wasm/wasm_test.go
index ba9e6e61c1..90c56a0f3f 100644
--- a/arc/go/runtime/wasm/wasm_test.go
+++ b/arc/go/runtime/wasm/wasm_test.go
@@ -2542,5 +2542,148 @@ input_ch -> checker{} -> output_ch
result = h.Output("checker_0", 0)
Expect(telem.UnmarshalSeries[uint8](result)[0]).To(Equal(uint8(0)))
})
+
+ It("Should write to separate channels when function with channel config is used multiple times", func() {
+ resolver := symbol.MapResolver{
+ "input_1": {
+ Name: "input_1",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 101,
+ },
+ "input_2": {
+ Name: "input_2",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 102,
+ },
+ "counter_1": {
+ Name: "counter_1",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.F32()),
+ ID: 201,
+ },
+ "counter_2": {
+ Name: "counter_2",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.F32()),
+ ID: 202,
+ },
+ "sink_1": {
+ Name: "sink_1",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 301,
+ },
+ "sink_2": {
+ Name: "sink_2",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 302,
+ },
+ }
+
+ source := `
+func increment{
+ counter chan f32
+} (trigger u8) u8 {
+ if trigger != 0 {
+ counter = counter + 1.0
+ }
+ return 0
+}
+
+input_1 -> increment{counter=counter_1} -> sink_1
+input_2 -> increment{counter=counter_2} -> sink_2
+`
+ h := newTextHarness(ctx, source, resolver, []state.ChannelDigest{
+ {Key: 101, DataType: telem.Uint8T},
+ {Key: 102, DataType: telem.Uint8T},
+ {Key: 201, DataType: telem.Float32T},
+ {Key: 202, DataType: telem.Float32T},
+ {Key: 301, DataType: telem.Uint8T},
+ {Key: 302, DataType: telem.Uint8T},
+ })
+ defer h.Close()
+
+ fr := telem.Frame[uint32]{}
+ fr = fr.Append(201, telem.NewSeriesV[float32](0.0))
+ fr = fr.Append(202, telem.NewSeriesV[float32](0.0))
+ h.state.Ingest(fr)
+
+ h.SetInput("on_input_1_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(1))
+ h.SetInput("on_input_2_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(1))
+ h.Execute(ctx, "increment_0")
+ h.Execute(ctx, "increment_1")
+
+ outFr, changed := h.state.Flush(telem.Frame[uint32]{})
+ Expect(changed).To(BeTrue())
+ Expect(outFr.Get(201).Series).To(HaveLen(1), "counter_1 should have been written")
+ Expect(telem.UnmarshalSeries[float32](outFr.Get(201).Series[0])[0]).To(Equal(float32(1.0)))
+ Expect(outFr.Get(202).Series).To(HaveLen(1), "counter_2 should have been written")
+ Expect(telem.UnmarshalSeries[float32](outFr.Get(202).Series[0])[0]).To(Equal(float32(1.0)))
+ })
+
+ It("Should not write to channel when stateful variable initialized from channel is modified", func() {
+ resolver := symbol.MapResolver{
+ "input_ch": {
+ Name: "input_ch",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 100,
+ },
+ "counter_ch": {
+ Name: "counter_ch",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.F32()),
+ ID: 200,
+ },
+ "sink_ch": {
+ Name: "sink_ch",
+ Kind: symbol.KindChannel,
+ Type: types.Chan(types.U8()),
+ ID: 300,
+ },
+ }
+
+ source := `
+func count_local (trigger u8) u8 {
+ counter $= counter_ch
+ prev $= trigger
+ if trigger != 0 and prev == 0 {
+ counter = counter + 1.0
+ }
+ prev = trigger
+ return 0
+}
+
+input_ch -> count_local{} -> sink_ch
+`
+ h := newTextHarness(ctx, source, resolver, []state.ChannelDigest{
+ {Key: 100, DataType: telem.Uint8T},
+ {Key: 200, DataType: telem.Float32T},
+ {Key: 300, DataType: telem.Uint8T},
+ })
+ defer h.Close()
+
+ fr := telem.Frame[uint32]{}
+ fr = fr.Append(200, telem.NewSeriesV[float32](5.0))
+ h.state.Ingest(fr)
+
+ h.SetInput("on_input_ch_0", 0, telem.NewSeriesV[uint8](0), telem.NewSeriesSecondsTSV(1))
+ h.Execute(ctx, "count_local_0")
+
+ outFr, _ := h.state.Flush(telem.Frame[uint32]{})
+ Expect(outFr.Get(200).Series).To(BeEmpty())
+
+ fr = telem.Frame[uint32]{}
+ fr = fr.Append(200, telem.NewSeriesV[float32](5.0))
+ h.state.Ingest(fr)
+ h.SetInput("on_input_ch_0", 0, telem.NewSeriesV[uint8](1), telem.NewSeriesSecondsTSV(2))
+ h.Execute(ctx, "count_local_0")
+
+ outFr, _ = h.state.Flush(telem.Frame[uint32]{})
+ Expect(outFr.Get(200).Series).To(BeEmpty())
+ })
})
})
diff --git a/arc/go/text/analyze.go b/arc/go/text/analyze.go
index e82428d73e..fc3f96b9d7 100644
--- a/arc/go/text/analyze.go
+++ b/arc/go/text/analyze.go
@@ -236,7 +236,7 @@ func analyzeFunctionNode(
n := ir.Node{
Key: key,
Type: name,
- Channels: sym.Channels,
+ Channels: sym.Channels.Copy(),
Config: slices.Clone(sym.Type.Config),
Outputs: slices.Clone(sym.Type.Outputs),
Inputs: slices.Clone(sym.Type.Inputs),
diff --git a/arc/go/text/text_test.go b/arc/go/text/text_test.go
index d69332939f..f6bd2ff218 100644
--- a/arc/go/text/text_test.go
+++ b/arc/go/text/text_test.go
@@ -143,7 +143,7 @@ var _ = Describe("Text", func() {
Context("Channel Flow Analysis", func() {
It("Should analyze flow with channel identifier", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 42},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10042},
}
source := `
func print{} () {
@@ -163,7 +163,7 @@ var _ = Describe("Text", func() {
Expect(channelNode.Config).To(HaveLen(1))
Expect(channelNode.Config[0].Name).To(Equal("channel"))
Expect(channelNode.Config[0].Type).To(Equal(types.Chan(types.I32())))
- Expect(channelNode.Channels.Read.Contains(42)).To(BeTrue())
+ Expect(channelNode.Channels.Read.Contains(10042)).To(BeTrue())
printNode := findNodeByKey(inter.Nodes, "print_0")
Expect(printNode.Type).To(Equal("print"))
@@ -236,17 +236,17 @@ var _ = Describe("Text", func() {
},
Entry("integer literal",
`1 -> output`,
- symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1}},
+ symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10001}},
true, types.F32(),
),
Entry("float literal",
`3.14 -> output`,
- symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1}},
+ symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10002}},
true, types.F64(),
),
Entry("complex expression (should not generate constant)",
`1 + 2 -> output`,
- symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1}},
+ symbol.MapResolver{"output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10003}},
false, types.Type{}, // Type ignored when expectConstant is false
),
)
@@ -317,7 +317,7 @@ var _ = Describe("Text", func() {
It("Should resolve channel name to channel ID in config parameter", func() {
resolver := symbol.MapResolver{
- "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 42},
+ "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10042},
}
source := `
func reader{
@@ -339,13 +339,13 @@ var _ = Describe("Text", func() {
Expect(readerNode.Config).To(HaveLen(1))
Expect(readerNode.Config[0].Name).To(Equal("channel"))
Expect(readerNode.Config[0].Type).To(Equal(types.Chan(types.F64())))
- Expect(readerNode.Config[0].Value).To(Equal(uint32(42)))
- Expect(readerNode.Channels.Read.Contains(uint32(42))).To(BeTrue())
+ Expect(readerNode.Config[0].Value).To(Equal(uint32(10042)))
+ Expect(readerNode.Channels.Read.Contains(uint32(10042))).To(BeTrue())
})
It("Should produce diagnostic error when channel config type mismatches", func() {
resolver := symbol.MapResolver{
- "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 42},
+ "temp_sensor": {Name: "temp_sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10043},
}
source := `
func reader{
@@ -393,7 +393,7 @@ var _ = Describe("Text", func() {
It("Should resolve channel name for write operations and add to Channels.Write", func() {
resolver := symbol.MapResolver{
- "output_channel": {Name: "output_channel", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 55},
+ "output_channel": {Name: "output_channel", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10055},
}
source := `
func writer{
@@ -416,16 +416,81 @@ var _ = Describe("Text", func() {
Expect(writerNode.Config).To(HaveLen(1))
Expect(writerNode.Config[0].Name).To(Equal("channel"))
Expect(writerNode.Config[0].Type).To(Equal(types.Chan(types.F64())))
- Expect(writerNode.Config[0].Value).To(Equal(uint32(55)))
- Expect(writerNode.Channels.Write.Contains(uint32(55))).To(BeTrue())
- Expect(writerNode.Channels.Read.Contains(uint32(55))).To(BeFalse())
+ Expect(writerNode.Config[0].Value).To(Equal(uint32(10055)))
+ Expect(writerNode.Channels.Write.Contains(uint32(10055))).To(BeTrue())
+ Expect(writerNode.Channels.Read.Contains(uint32(10055))).To(BeFalse())
+ })
+
+ It("Should register separate write channels when function with channel config is used multiple times", func() {
+ resolver := symbol.MapResolver{
+ "toggle_1": {Name: "toggle_1", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10011},
+ "toggle_2": {Name: "toggle_2", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10012},
+ "counter_1": {Name: "counter_1", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10013},
+ "counter_2": {Name: "counter_2", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10014},
+ }
+ source := `
+ func count_rising{counter chan f32}(input u8) {
+ prev $= input
+ if input != 0 and prev == 0 {
+ counter = counter + 1.0
+ }
+ prev = input
+ }
+
+ toggle_1 -> count_rising{counter=counter_1}
+ toggle_2 -> count_rising{counter=counter_2}
+ `
+ parsedText := MustSucceed(text.Parse(text.Text{Raw: source}))
+ inter, diagnostics := text.Analyze(ctx, parsedText, resolver)
+ Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String())
+
+ // Find the two count_rising nodes
+ node1 := findNodeByKey(inter.Nodes, "count_rising_0")
+ node2 := findNodeByKey(inter.Nodes, "count_rising_1")
+
+ // Each node should have its own write channel
+ Expect(node1.Channels.Write.Contains(uint32(10013))).To(BeTrue(), "first node should write to counter_1")
+ Expect(node2.Channels.Write.Contains(uint32(10014))).To(BeTrue(), "second node should write to counter_2")
+
+ Expect(node1.Channels.Read.Contains(uint32(10013))).To(BeTrue(), "first node should read from counter_1")
+ Expect(node2.Channels.Read.Contains(uint32(10014))).To(BeTrue(), "second node should read from counter_2")
+ })
+
+ It("Should not add stateful variable to write channels when initialized from global channel", func() {
+ resolver := symbol.MapResolver{
+ "toggle_1": {Name: "toggle_1", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10101},
+ "counter_1": {Name: "counter_1", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10102},
+ }
+ source := `
+ func count_rising(input u8) {
+ counter $= counter_1
+ prev $= input
+ if input != 0 and prev == 0 {
+ counter = counter + 1.0
+ }
+ prev = input
+ }
+
+ toggle_1 -> count_rising{}
+ `
+ parsedText := MustSucceed(text.Parse(text.Text{Raw: source}))
+ inter, diagnostics := text.Analyze(ctx, parsedText, resolver)
+ Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String())
+
+ // Find the count_rising node
+ node := findNodeByKey(inter.Nodes, "count_rising_0")
+
+ // counter_1 should be in Read (stateful var is initialized from channel value)
+ Expect(node.Channels.Read.Contains(uint32(10102))).To(BeTrue(), "should read from counter_1")
+ // Write channels should be empty - we write to a stateful variable, not a channel
+ Expect(node.Channels.Write).To(BeEmpty(), "should not have any write channels")
})
})
Context("Edge Parameter Validation", func() {
It("Should create edges with parameters that exist in node definitions", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10001},
}
source := `
func filter{} (data i64) i64 {
@@ -502,7 +567,7 @@ var _ = Describe("Text", func() {
It("Should verify channel node outputs are defined", func() {
resolver := symbol.MapResolver{
- "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 42},
+ "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10044},
}
source := `
func display{} (value f64) {
@@ -664,7 +729,7 @@ var _ = Describe("Text", func() {
Context("Stratification", func() {
It("Should calculate strata for simple flow chain", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10001},
}
source := `
func filter{} (data i64) i64 {
@@ -727,8 +792,8 @@ var _ = Describe("Text", func() {
Context("Channel Sink Detection", func() {
It("Should create write node for channel at end of flow", func() {
resolver := symbol.MapResolver{
- "input_chan": {Name: "input_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
- "output_chan": {Name: "output_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2},
+ "input_chan": {Name: "input_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10021},
+ "output_chan": {Name: "output_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10022},
}
source := `
func double{} (x f32) f32 {
@@ -745,12 +810,12 @@ var _ = Describe("Text", func() {
inputNode := inter.Nodes[0]
Expect(inputNode.Type).To(Equal("on"))
- Expect(inputNode.Channels.Read.Contains(uint32(1))).To(BeTrue())
+ Expect(inputNode.Channels.Read.Contains(uint32(10021))).To(BeTrue())
Expect(inputNode.Outputs).To(HaveLen(1))
outputNode := inter.Nodes[2]
Expect(outputNode.Type).To(Equal("write"))
- Expect(outputNode.Channels.Write.Contains(uint32(2))).To(BeTrue())
+ Expect(outputNode.Channels.Write.Contains(uint32(10022))).To(BeTrue())
Expect(outputNode.Inputs).To(HaveLen(1))
Expect(outputNode.Inputs[0].Name).To(Equal("input"))
Expect(outputNode.Outputs).To(BeEmpty())
@@ -758,8 +823,8 @@ var _ = Describe("Text", func() {
It("Should handle channel-to-channel flow", func() {
resolver := symbol.MapResolver{
- "chan1": {Name: "chan1", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 1},
- "chan2": {Name: "chan2", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 2},
+ "chan1": {Name: "chan1", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10031},
+ "chan2": {Name: "chan2", Kind: symbol.KindChannel, Type: types.Chan(types.I32()), ID: 10032},
}
source := `chan1 -> chan2`
parsedText := MustSucceed(text.Parse(text.Text{Raw: source}))
@@ -768,15 +833,15 @@ var _ = Describe("Text", func() {
Expect(inter.Nodes).To(HaveLen(2))
Expect(inter.Nodes[0].Type).To(Equal("on"))
- Expect(inter.Nodes[0].Channels.Read.Contains(uint32(1))).To(BeTrue())
+ Expect(inter.Nodes[0].Channels.Read.Contains(uint32(10031))).To(BeTrue())
Expect(inter.Nodes[1].Type).To(Equal("write"))
- Expect(inter.Nodes[1].Channels.Write.Contains(uint32(2))).To(BeTrue())
+ Expect(inter.Nodes[1].Channels.Write.Contains(uint32(10032))).To(BeTrue())
})
It("Should handle channel sinks in routing tables", func() {
resolver := symbol.MapResolver{
- "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1},
- "low_chan": {Name: "low_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 2},
+ "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10041},
+ "low_chan": {Name: "low_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10045},
}
source := `
func demux{threshold f64} (value f64) (high f64, low f64) {
@@ -829,7 +894,7 @@ var _ = Describe("Text", func() {
Context("Sequence Targeting", func() {
It("Should connect one-shot edge to sequence's first stage entry node", func() {
resolver := symbol.MapResolver{
- "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1},
+ "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051},
}
source := `
sequence main {
@@ -869,7 +934,7 @@ var _ = Describe("Text", func() {
It("Should handle continuous flow to sequence", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10061},
}
source := `
sequence main {
@@ -892,7 +957,7 @@ var _ = Describe("Text", func() {
It("Should handle sequence with multiple stages - connects to first stage", func() {
resolver := symbol.MapResolver{
- "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1},
+ "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051},
}
source := `
sequence main {
@@ -918,7 +983,7 @@ var _ = Describe("Text", func() {
It("Should error when targeting empty sequence (no stages)", func() {
resolver := symbol.MapResolver{
- "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 1},
+ "trigger": {Name: "trigger", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10051},
}
source := `
sequence empty {
@@ -934,7 +999,7 @@ var _ = Describe("Text", func() {
It("Should handle sequence in routing table as sink", func() {
resolver := symbol.MapResolver{
- "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 1},
+ "high_chan": {Name: "high_chan", Kind: symbol.KindChannel, Type: types.Chan(types.F64()), ID: 10071},
}
source := `
sequence alarm {
@@ -965,7 +1030,7 @@ var _ = Describe("Text", func() {
Expect(entryNode.Type).To(Equal("stage_entry"))
writeNode := findNodeByType(inter.Nodes, "write")
- Expect(writeNode.Channels.Write.Contains(uint32(1))).To(BeTrue())
+ Expect(writeNode.Channels.Write.Contains(uint32(10071))).To(BeTrue())
Expect(inter.Edges).To(HaveLen(2))
@@ -978,7 +1043,7 @@ var _ = Describe("Text", func() {
Context("Direct Stage Targeting", func() {
It("Should allow targeting a stage by name within a sequence", func() {
resolver := symbol.MapResolver{
- "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
+ "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081},
}
source := `
sequence main {
@@ -1012,8 +1077,8 @@ var _ = Describe("Text", func() {
}
}`
resolver := symbol.MapResolver{
- "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
- "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 2},
+ "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10091},
+ "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.U8()), ID: 10092},
}
parsedText := MustSucceed(text.Parse(text.Text{Raw: source}))
inter, diag := text.Analyze(ctx, parsedText, resolver)
@@ -1040,14 +1105,14 @@ var _ = Describe("Text", func() {
}
}`,
symbol.MapResolver{
- "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
+ "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081},
},
"no next stage",
),
Entry("next outside sequence",
`input > 10 => next`,
symbol.MapResolver{
- "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
+ "input": {Name: "input", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10081},
},
"outside of a sequence",
),
@@ -1057,7 +1122,7 @@ var _ = Describe("Text", func() {
Context("Implicit Expression Triggers", func() {
It("Should inject implicit trigger for expression as first flow node", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142},
}
source := `
func alarm{} (value u8) {
@@ -1073,11 +1138,11 @@ var _ = Describe("Text", func() {
triggerNode := findNodeByKey(inter.Nodes, "on_sensor_0")
Expect(triggerNode.Type).To(Equal("on"))
- Expect(triggerNode.Channels.Read.Contains(uint32(42))).To(BeTrue())
+ Expect(triggerNode.Channels.Read.Contains(uint32(10142))).To(BeTrue())
exprNode := inter.Nodes[1]
Expect(exprNode.Type).To(HavePrefix("expression_"))
- Expect(exprNode.Channels.Read.Contains(uint32(42))).To(BeTrue())
+ Expect(exprNode.Channels.Read.Contains(uint32(10142))).To(BeTrue())
Expect(inter.Edges).To(HaveLen(2))
@@ -1095,8 +1160,8 @@ var _ = Describe("Text", func() {
It("Should inject multiple triggers for multi-channel expression", func() {
resolver := symbol.MapResolver{
- "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 1},
- "pressure": {Name: "pressure", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 2},
+ "temp": {Name: "temp", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10151},
+ "pressure": {Name: "pressure", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10152},
}
source := `
func alarm{} (value u8) {
@@ -1121,8 +1186,8 @@ var _ = Describe("Text", func() {
}
}
Expect(exprNode.Channels.Read).To(HaveLen(2))
- Expect(exprNode.Channels.Read.Contains(uint32(1))).To(BeTrue())
- Expect(exprNode.Channels.Read.Contains(uint32(2))).To(BeTrue())
+ Expect(exprNode.Channels.Read.Contains(uint32(10151))).To(BeTrue())
+ Expect(exprNode.Channels.Read.Contains(uint32(10152))).To(BeTrue())
Expect(inter.Edges).To(HaveLen(3))
@@ -1138,7 +1203,7 @@ var _ = Describe("Text", func() {
It("Should not inject trigger for constant expressions", func() {
resolver := symbol.MapResolver{
- "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 1},
+ "output": {Name: "output", Kind: symbol.KindChannel, Type: types.Chan(types.I64()), ID: 10161},
}
source := `1 + 2 -> output`
parsedText := MustSucceed(text.Parse(text.Text{Raw: source}))
@@ -1153,7 +1218,7 @@ var _ = Describe("Text", func() {
It("Should not inject trigger when expression is not first node", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142},
}
source := `
func alarm{} (value u8) {
@@ -1175,7 +1240,7 @@ var _ = Describe("Text", func() {
It("Should inject trigger for expression in sequence stage", func() {
resolver := symbol.MapResolver{
- "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 42},
+ "sensor": {Name: "sensor", Kind: symbol.KindChannel, Type: types.Chan(types.F32()), ID: 10142},
}
source := `
sequence main {
@@ -1194,7 +1259,7 @@ var _ = Describe("Text", func() {
Expect(triggerCount).To(Equal(1))
triggerNode := findNodeByType(inter.Nodes, "on")
- Expect(triggerNode.Channels.Read.Contains(uint32(42))).To(BeTrue())
+ Expect(triggerNode.Channels.Read.Contains(uint32(10142))).To(BeTrue())
})
})
@@ -1389,7 +1454,7 @@ var _ = Describe("Text", func() {
Name: "virt_1",
Kind: symbol.KindChannel,
Type: types.Chan(types.F32()),
- ID: 25,
+ ID: 10025,
},
}
source := `
@@ -1423,7 +1488,7 @@ var _ = Describe("Text", func() {
Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String())
Expect(ir.Nodes).To(HaveLen(3))
Expect(ir.Nodes[1].Channels.Read).To(HaveLen(1))
- Expect(ir.Nodes[1].Channels.Read.Contains(25)).To(BeTrue())
+ Expect(ir.Nodes[1].Channels.Read.Contains(10025)).To(BeTrue())
module := MustSucceed(text.Compile(ctx, ir))
Expect(module.Output.WASM).ToNot(BeEmpty())
@@ -1439,19 +1504,19 @@ var _ = Describe("Text", func() {
Name: "input_ch",
Kind: symbol.KindChannel,
Type: types.Chan(types.F32()),
- ID: 100,
+ ID: 10100,
},
"write_target": {
Name: "write_target",
Kind: symbol.KindChannel,
Type: types.Chan(types.F32()),
- ID: 200,
+ ID: 10200,
},
"sink_ch": {
Name: "sink_ch",
Kind: symbol.KindChannel,
Type: types.Chan(types.U8()),
- ID: 300,
+ ID: 10300,
},
}
source := `
@@ -1475,7 +1540,7 @@ var _ = Describe("Text", func() {
writerNode := ir.Nodes[1]
Expect(writerNode.Type).To(Equal("writer"))
Expect(writerNode.Channels.Write).To(HaveLen(1))
- Expect(writerNode.Channels.Write.Contains(200)).To(BeTrue())
+ Expect(writerNode.Channels.Write.Contains(10200)).To(BeTrue())
module := MustSucceed(text.Compile(ctx, ir))
Expect(module.Output.WASM).ToNot(BeEmpty())
@@ -1490,19 +1555,19 @@ var _ = Describe("Text", func() {
Name: "input_ch",
Kind: symbol.KindChannel,
Type: types.Chan(types.F32()),
- ID: 100,
+ ID: 10110,
},
"output_ch": {
Name: "output_ch",
Kind: symbol.KindChannel,
Type: types.Chan(types.F32()),
- ID: 200,
+ ID: 10210,
},
"sink_ch": {
Name: "sink_ch",
Kind: symbol.KindChannel,
Type: types.Chan(types.U8()),
- ID: 300,
+ ID: 10310,
},
}
source := `
@@ -1519,12 +1584,10 @@ var _ = Describe("Text", func() {
Expect(diagnostics.Ok()).To(BeTrue(), diagnostics.String())
Expect(ir.Nodes).To(HaveLen(3))
- // The writer function node should have output_ch (200) in Channels.Write
- // NOT the intermediate variable's ID
writerNode := ir.Nodes[1]
Expect(writerNode.Type).To(Equal("writer"))
Expect(writerNode.Channels.Write).To(HaveLen(1))
- Expect(writerNode.Channels.Write.Contains(200)).To(BeTrue())
+ Expect(writerNode.Channels.Write.Contains(10210)).To(BeTrue())
module := MustSucceed(text.Compile(ctx, ir))
Expect(module.Output.WASM).ToNot(BeEmpty())
From 25f36c4bbfcbe52b210d14694e263795a34efaf7 Mon Sep 17 00:00:00 2001
From: Emiliano Bonilla <56323762+emilbon99@users.noreply.github.com>
Date: Wed, 4 Feb 2026 21:33:02 -0700
Subject: [PATCH 2/3] [console] - improved task controls styles
---
console/src/arc/editor/Controls.css | 12 ++++++++++-
.../common/task/controls/Controls.css | 13 ++++++++++++
.../hardware/common/task/controls/Status.tsx | 20 ++++++++++++-------
core/pkg/service/arc/runtime/task.go | 1 +
4 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/console/src/arc/editor/Controls.css b/console/src/arc/editor/Controls.css
index 38342385d1..729417700b 100644
--- a/console/src/arc/editor/Controls.css
+++ b/console/src/arc/editor/Controls.css
@@ -10,6 +10,8 @@
*/
.console-arc-editor {
+ container-type: inline-size;
+
.console-editor {
padding-bottom: 11rem;
@@ -32,7 +34,15 @@
box-shadow: var(--pluto-shadow-v2);
& .console-rack-select {
- min-width: 225px;
+ flex-grow: 1;
+ }
+ }
+}
+
+@container (max-width: 400px) {
+ .console-arc-editor {
+ .console-rack-select {
+ display: none;
}
}
}
diff --git a/console/src/hardware/common/task/controls/Controls.css b/console/src/hardware/common/task/controls/Controls.css
index 1be404f249..ed1b5e0b42 100644
--- a/console/src/hardware/common/task/controls/Controls.css
+++ b/console/src/hardware/common/task/controls/Controls.css
@@ -79,6 +79,19 @@
@container (max-width: 600px) {
.console-task-controls {
flex-direction: column !important;
+ overflow: hidden;
+
+ .console-task-controls__actions {
+ width: 100%;
+ }
+
+ .console-task-status__copy-button {
+ font-size: 0;
+ gap: 0;
+ & svg {
+ font-size: 2rem;
+ }
+ }
.console-task-status {
min-height: 10rem;
diff --git a/console/src/hardware/common/task/controls/Status.tsx b/console/src/hardware/common/task/controls/Status.tsx
index bf840c8260..be1a65e8f3 100644
--- a/console/src/hardware/common/task/controls/Status.tsx
+++ b/console/src/hardware/common/task/controls/Status.tsx
@@ -66,12 +66,18 @@ export const Status = ({
{message}
-
-
- {stat.time}
-
- {expanded && (
+ {expanded && (
+
+
+ {stat.time}
+
Copy diagnostics
- )}
-
+
+ )}
{expanded && hasDescription && (
diff --git a/core/pkg/service/arc/runtime/task.go b/core/pkg/service/arc/runtime/task.go
index dd64650b77..a1303245eb 100644
--- a/core/pkg/service/arc/runtime/task.go
+++ b/core/pkg/service/arc/runtime/task.go
@@ -281,6 +281,7 @@ func (d *dataRuntime) next(
d.scheduler.Next(ctx, telem.Since(d.startTime), reason)
d.state.ClearReads()
if fr, changed := d.state.Flush(telem.Frame[uint32]{}); changed && d.Out != nil {
+ fmt.Println(fr)
req := framer.WriterRequest{
Frame: frame.NewFromStorage(fr),
Command: writer.CommandWrite,
From 9d1ae984b0dc32c4d90b9e1178a0589d9c06ab42 Mon Sep 17 00:00:00 2001
From: Emiliano Bonilla <56323762+emilbon99@users.noreply.github.com>
Date: Wed, 4 Feb 2026 21:34:46 -0700
Subject: [PATCH 3/3] [console] - updated css tyles
---
console/src/hardware/common/task/controls/Controls.css | 4 ++++
console/src/hardware/common/task/controls/Status.tsx | 1 +
2 files changed, 5 insertions(+)
diff --git a/console/src/hardware/common/task/controls/Controls.css b/console/src/hardware/common/task/controls/Controls.css
index ed1b5e0b42..c8cdf5101c 100644
--- a/console/src/hardware/common/task/controls/Controls.css
+++ b/console/src/hardware/common/task/controls/Controls.css
@@ -81,6 +81,10 @@
flex-direction: column !important;
overflow: hidden;
+ .console-task-status__time-stamp {
+ display: none;
+ }
+
.console-task-controls__actions {
width: 100%;
}
diff --git a/console/src/hardware/common/task/controls/Status.tsx b/console/src/hardware/common/task/controls/Status.tsx
index be1a65e8f3..6d54286675 100644
--- a/console/src/hardware/common/task/controls/Status.tsx
+++ b/console/src/hardware/common/task/controls/Status.tsx
@@ -69,6 +69,7 @@ export const Status = ({
{expanded && (