package controllogic import ( "math" "reflect" "testing" ) func TestEvalExpr(t *testing.T) { resolve := func(ds, name string) Value { switch { case ds == "stub" && name == "x": return 10 case ds == "local" && name == "y": return 3 case ds == "sys" && name == "dt": return 0.5 } return math.NaN() } cases := []struct { expr string want float64 }{ {"1 + 2 * 3", 7}, {"(1 + 2) * 3", 9}, {"10 % 3", 1}, {"{stub:x} + y", 13}, {"{stub:x} > 5 ? 1 : 0", 1}, {"min(4, 2, 8)", 2}, {"max(4, 2, 8)", 8}, {"abs(-5)", 5}, {"sqrt(16)", 4}, {"2 < 3 && 3 < 4", 1}, {"!0", 1}, {"-{stub:x}", -10}, {"{sys:dt} * 2", 1}, {"true + false", 1}, } for _, c := range cases { got := EvalExpr(c.expr, resolve) if math.Abs(got-c.want) > 1e-9 { t.Errorf("EvalExpr(%q) = %v, want %v", c.expr, got, c.want) } } } func TestEvalExprErrors(t *testing.T) { r := func(ds, name string) Value { return 0 } for _, bad := range []string{"1 +", "(1", "1 2", "{unterminated"} { if v := EvalExpr(bad, r); !math.IsNaN(v) { t.Errorf("EvalExpr(%q) = %v, want NaN", bad, v) } } } func TestCollectRefs(t *testing.T) { refs := CollectRefs("{stub:x} + y + {epics:MY:PV} * z") got := map[string]bool{} for _, r := range refs { got[r.DS+":"+r.Name] = true } for _, want := range []string{"stub:x", "local:y", "epics:MY:PV", "local:z"} { if !got[want] { t.Errorf("CollectRefs missing %q (got %v)", want, got) } } } func TestCheckExpr(t *testing.T) { if msg := CheckExpr("1 + 2"); msg != "" { t.Errorf("CheckExpr valid returned %q", msg) } if msg := CheckExpr("1 +"); msg == "" { t.Errorf("CheckExpr invalid returned empty") } if msg := CheckExpr(""); msg != "" { t.Errorf("CheckExpr empty returned %q", msg) } } func TestEpicsRefSplitFirstColon(t *testing.T) { refs := CollectRefs("{epics:SR:BPM:01:X}") if len(refs) != 1 || refs[0].DS != "epics" || refs[0].Name != "SR:BPM:01:X" { t.Errorf("got %+v, want epics / SR:BPM:01:X", refs) } } // ── New tests for value-polymorphic evaluator ───────────────────────────────── func numResolver(vals map[string]Value) Resolver { return func(ds, name string) Value { if v, ok := vals[ds+":"+name]; ok { return v } return math.NaN() } } func TestEvalValueScalar(t *testing.T) { R := numResolver(nil) if got := EvalExpr("2 + 3 * 4", R); got != 14 { t.Fatalf("scalar = %v", got) } if !EvalBool("1 < 2 && 3 >= 3", R) { t.Fatal("bool expr should be true") } } func TestEvalValueArrayLiteralAndIndex(t *testing.T) { R := numResolver(nil) got := EvalValue("[1, 2, 3]", R) if !reflect.DeepEqual(got, []Value{1.0, 2.0, 3.0}) { t.Fatalf("array literal = %#v", got) } if v := EvalExpr("[10,20,30][-1]", R); v != 30 { t.Fatalf("index -1 = %v", v) } } func TestEvalArrayFuncs(t *testing.T) { R := numResolver(map[string]Value{"local:buf": []Value{3.0, 1.0, 2.0}}) if v := EvalExpr("len(buf)", R); v != 3 { t.Fatalf("len = %v", v) } if v := EvalExpr("sum(buf)", R); v != 6 { t.Fatalf("sum = %v", v) } if v := EvalExpr("max(buf)", R); v != 3 { t.Fatalf("max(array) = %v", v) } if v := EvalExpr("max(1, 9, 4)", R); v != 9 { t.Fatalf("max(scalars) = %v", v) } got := EvalValue("push(buf, 7)", R) if !reflect.DeepEqual(got, []Value{3.0, 1.0, 2.0, 7.0}) { t.Fatalf("push = %#v", got) } } func TestEvalExprArrayYieldsNaN(t *testing.T) { if v := EvalExpr("[1,2]", numResolver(nil)); !math.IsNaN(v) { t.Fatalf("array via EvalExpr should be NaN, got %v", v) } } func TestCollectRefsArray(t *testing.T) { refs := CollectRefs("[{ds:a}, b[0]] ") keys := map[string]bool{} for _, r := range refs { keys[r.DS+":"+r.Name] = true } if !keys["ds:a"] || !keys["local:b"] { t.Fatalf("refs = %#v", refs) } }