Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/select-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
The `select` operation chooses between two values based on a simple
conditions. It enables basic control flow within Gendo pipelines by selecting
either a `trueValue` or a `falseValue` depending on whether the `condition`
string exactly matches `"true"` (case-insensitive).

The syntax of the `select` operation is as follows:

select [destination] value trueValue falseValue

The `destination` identifier is optional. If omitted, the result is bound to
the special slot `_`. The `condition` is required and must be a
string—typically the result of a previous model call or logic step. If
`condition` equals `"true"` (case-insensitive), the `trueValue` is chosen.
Otherwise, the `falseValue` is chosen. The selected value is then stored in
`destination` or in `_` if no destination is provided.

An example with an explicit destination:

select outcome "true" "Proceed" "Abort"

In this example, the string `"Proceed"` is assigned to `outcome` because the
condition matches `"true"`.

Another example using a prior model result as the condition:

prompt isValid "Is the previous input acceptable? Reply 'true' or 'false'."
select validationMessage isValid "Input is acceptable." "Input is not acceptable."

Here, the pipeline sends a prompt to the model. The response, assumed to be
`"true"` or `"false"`, is stored in `isValid`. The `select` instruction then
chooses an appropriate message and binds it to `validationMessage`.

The `select` operation requires all arguments to be present and in order. It
performs no transformation beyond the selection logic, and it produces exactly
one value. All bindings follow the single-assignment rule: once a name is used
as a destination, it cannot be reassigned later in the same pipeline.
34 changes: 34 additions & 0 deletions pkg/primitives/select.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package primitives

import (
"fmt"

"github.com/hyperifyio/gnd/pkg/primitive_services"
)

// Select represents the select primitive
type Select struct{}

func (s *Select) Name() string {
return "/gnd/select"
}

func (s *Select) Execute(args []interface{}) (interface{}, error) {
if len(args) != 3 {
return nil, fmt.Errorf("select expects exactly 3 arguments (condition, trueValue, falseValue), got %d", len(args))
}

condition := fmt.Sprintf("%v", args[0])
trueValue := args[1]
falseValue := args[2]

// Case-sensitive comparison with "true"
if condition == "true" {
return trueValue, nil
}
return falseValue, nil
}

func init() {
primitive_services.RegisterPrimitive(&Select{})
}
86 changes: 86 additions & 0 deletions pkg/primitives/select_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package primitives

import (
"testing"
)

func TestSelect(t *testing.T) {
tests := []struct {
name string
args []interface{}
expected interface{}
wantErr bool
}{
{
name: "true condition",
args: []interface{}{"true", "Proceed", "Abort"},
expected: "Proceed",
wantErr: false,
},
{
name: "false condition",
args: []interface{}{"false", "Proceed", "Abort"},
expected: "Abort",
wantErr: false,
},
{
name: "uppercase true",
args: []interface{}{"TRUE", "Proceed", "Abort"},
expected: "Abort",
wantErr: false,
},
{
name: "uppercase false",
args: []interface{}{"FALSE", "Proceed", "Abort"},
expected: "Abort",
wantErr: false,
},
{
name: "mixed case true",
args: []interface{}{"True", "Proceed", "Abort"},
expected: "Abort",
wantErr: false,
},
{
name: "mixed case false",
args: []interface{}{"False", "Proceed", "Abort"},
expected: "Abort",
wantErr: false,
},
{
name: "too few args",
args: []interface{}{"true", "Proceed"},
expected: nil,
wantErr: true,
},
{
name: "too many args",
args: []interface{}{"true", "Proceed", "Abort", "Extra"},
expected: nil,
wantErr: true,
},
}

selectPrim := &Select{}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := selectPrim.Execute(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("Select.Execute() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.expected {
t.Errorf("Select.Execute() = %v, want %v", got, tt.expected)
}
})
}
}

func TestSelectName(t *testing.T) {
selectPrim := &Select{}
expected := "/gnd/select"
if got := selectPrim.Name(); got != expected {
t.Errorf("Select.Name() = %v, want %v", got, expected)
}
}