Skip to content

Commit cff7942

Browse files
author
Jaakko Heusala
committed
Implemented initial select primitive
1 parent f5a4ddd commit cff7942

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

docs/select-syntax.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
The `select` operation chooses between two values based on a simple
2+
conditions. It enables basic control flow within Gendo pipelines by selecting
3+
either a `trueValue` or a `falseValue` depending on whether the `condition`
4+
string exactly matches `"true"` (case-insensitive).
5+
6+
The syntax of the `select` operation is as follows:
7+
8+
select [destination] value trueValue falseValue
9+
10+
The `destination` identifier is optional. If omitted, the result is bound to
11+
the special slot `_`. The `condition` is required and must be a
12+
string—typically the result of a previous model call or logic step. If
13+
`condition` equals `"true"` (case-insensitive), the `trueValue` is chosen.
14+
Otherwise, the `falseValue` is chosen. The selected value is then stored in
15+
`destination` or in `_` if no destination is provided.
16+
17+
An example with an explicit destination:
18+
19+
select outcome "true" "Proceed" "Abort"
20+
21+
In this example, the string `"Proceed"` is assigned to `outcome` because the
22+
condition matches `"true"`.
23+
24+
Another example using a prior model result as the condition:
25+
26+
prompt isValid "Is the previous input acceptable? Reply 'true' or 'false'."
27+
select validationMessage isValid "Input is acceptable." "Input is not acceptable."
28+
29+
Here, the pipeline sends a prompt to the model. The response, assumed to be
30+
`"true"` or `"false"`, is stored in `isValid`. The `select` instruction then
31+
chooses an appropriate message and binds it to `validationMessage`.
32+
33+
The `select` operation requires all arguments to be present and in order. It
34+
performs no transformation beyond the selection logic, and it produces exactly
35+
one value. All bindings follow the single-assignment rule: once a name is used
36+
as a destination, it cannot be reassigned later in the same pipeline.

pkg/primitives/select.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package primitives
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hyperifyio/gnd/pkg/primitive_services"
7+
)
8+
9+
// Select represents the select primitive
10+
type Select struct{}
11+
12+
func (s *Select) Name() string {
13+
return "/gnd/select"
14+
}
15+
16+
func (s *Select) Execute(args []interface{}) (interface{}, error) {
17+
if len(args) != 3 {
18+
return nil, fmt.Errorf("select expects exactly 3 arguments (condition, trueValue, falseValue), got %d", len(args))
19+
}
20+
21+
condition := fmt.Sprintf("%v", args[0])
22+
trueValue := args[1]
23+
falseValue := args[2]
24+
25+
// Case-sensitive comparison with "true"
26+
if condition == "true" {
27+
return trueValue, nil
28+
}
29+
return falseValue, nil
30+
}
31+
32+
func init() {
33+
primitive_services.RegisterPrimitive(&Select{})
34+
}

pkg/primitives/select_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package primitives
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestSelect(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
args []interface{}
11+
expected interface{}
12+
wantErr bool
13+
}{
14+
{
15+
name: "true condition",
16+
args: []interface{}{"true", "Proceed", "Abort"},
17+
expected: "Proceed",
18+
wantErr: false,
19+
},
20+
{
21+
name: "false condition",
22+
args: []interface{}{"false", "Proceed", "Abort"},
23+
expected: "Abort",
24+
wantErr: false,
25+
},
26+
{
27+
name: "uppercase true",
28+
args: []interface{}{"TRUE", "Proceed", "Abort"},
29+
expected: "Abort",
30+
wantErr: false,
31+
},
32+
{
33+
name: "uppercase false",
34+
args: []interface{}{"FALSE", "Proceed", "Abort"},
35+
expected: "Abort",
36+
wantErr: false,
37+
},
38+
{
39+
name: "mixed case true",
40+
args: []interface{}{"True", "Proceed", "Abort"},
41+
expected: "Abort",
42+
wantErr: false,
43+
},
44+
{
45+
name: "mixed case false",
46+
args: []interface{}{"False", "Proceed", "Abort"},
47+
expected: "Abort",
48+
wantErr: false,
49+
},
50+
{
51+
name: "too few args",
52+
args: []interface{}{"true", "Proceed"},
53+
expected: nil,
54+
wantErr: true,
55+
},
56+
{
57+
name: "too many args",
58+
args: []interface{}{"true", "Proceed", "Abort", "Extra"},
59+
expected: nil,
60+
wantErr: true,
61+
},
62+
}
63+
64+
selectPrim := &Select{}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
got, err := selectPrim.Execute(tt.args)
69+
if (err != nil) != tt.wantErr {
70+
t.Errorf("Select.Execute() error = %v, wantErr %v", err, tt.wantErr)
71+
return
72+
}
73+
if !tt.wantErr && got != tt.expected {
74+
t.Errorf("Select.Execute() = %v, want %v", got, tt.expected)
75+
}
76+
})
77+
}
78+
}
79+
80+
func TestSelectName(t *testing.T) {
81+
selectPrim := &Select{}
82+
expected := "/gnd/select"
83+
if got := selectPrim.Name(); got != expected {
84+
t.Errorf("Select.Name() = %v, want %v", got, expected)
85+
}
86+
}

0 commit comments

Comments
 (0)