Added list:uniq and the !nil builtin
This commit is contained in:
parent
41b4fdb003
commit
e71699d5a7
|
@ -148,7 +148,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
|||
{"NL", `[;\n][; \n\t]*`, nil},
|
||||
{"PIPE", `\|`, nil},
|
||||
{"EQ", `=`, nil},
|
||||
{"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil},
|
||||
{"Ident", `[-!?]*[a-zA-Z_!?-][\w-!?]*`, nil},
|
||||
},
|
||||
"String": {
|
||||
{"Escaped", `\\.`, nil},
|
||||
|
|
|
@ -188,7 +188,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
l := args.args[0]
|
||||
r := args.args[1]
|
||||
|
||||
return BoolObject(objectsEqual(l, r)), nil
|
||||
return BoolObject(ObjectsEqual(l, r)), nil
|
||||
}
|
||||
|
||||
func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
|
@ -199,7 +199,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
l := args.args[0]
|
||||
r := args.args[1]
|
||||
|
||||
return BoolObject(!objectsEqual(l, r)), nil
|
||||
return BoolObject(!ObjectsEqual(l, r)), nil
|
||||
}
|
||||
|
||||
func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
|
@ -223,7 +223,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BoolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil
|
||||
return BoolObject(isLess || ObjectsEqual(args.args[0], args.args[1])), nil
|
||||
}
|
||||
|
||||
func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
|
@ -247,7 +247,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BoolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
|
||||
return BoolObject(isGreater || ObjectsEqual(args.args[0], args.args[1])), nil
|
||||
}
|
||||
|
||||
func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
|
@ -286,7 +286,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
|
||||
var errObjectsNotEqual = errors.New("objects not equal")
|
||||
|
||||
func objectsEqual(l, r Object) bool {
|
||||
func ObjectsEqual(l, r Object) bool {
|
||||
if l == nil || r == nil {
|
||||
return l == nil && r == nil
|
||||
}
|
||||
|
@ -314,7 +314,7 @@ func objectsEqual(l, r Object) bool {
|
|||
return false
|
||||
}
|
||||
for i := 0; i < lv.Len(); i++ {
|
||||
if !objectsEqual(lv.Index(i), rv.Index(i)) {
|
||||
if !ObjectsEqual(lv.Index(i), rv.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ func objectsEqual(l, r Object) bool {
|
|||
rkv := rv.Value(k)
|
||||
if rkv == nil {
|
||||
return errObjectsNotEqual
|
||||
} else if !objectsEqual(lkv, rkv) {
|
||||
} else if !ObjectsEqual(lkv, rkv) {
|
||||
return errObjectsNotEqual
|
||||
}
|
||||
return nil
|
||||
|
@ -370,6 +370,18 @@ func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
return StringObject(args.args[0].String()), nil
|
||||
}
|
||||
|
||||
func notNilBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if args.args[0] == nil {
|
||||
return BoolObject(false), nil
|
||||
}
|
||||
|
||||
return BoolObject(true), nil
|
||||
}
|
||||
|
||||
func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -11,6 +11,7 @@ func Lists() ucl.Module {
|
|||
Name: "lists",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"first": listFirst,
|
||||
"uniq": listUniq,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -58,3 +59,69 @@ func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|||
|
||||
return newList, nil
|
||||
}
|
||||
|
||||
func eachListOrIterItem(ctx context.Context, o ucl.Object, f func(int, ucl.Object) error) error {
|
||||
switch t := o.(type) {
|
||||
case ucl.Listable:
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
if err := f(i, t.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case ucl.Iterable:
|
||||
idx := 0
|
||||
for t.HasNext() {
|
||||
v, err := t.Next(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f(idx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return errors.New("expected listable")
|
||||
}
|
||||
|
||||
type uniqKey struct {
|
||||
sVal string
|
||||
iVal int
|
||||
}
|
||||
|
||||
func listUniq(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var (
|
||||
what ucl.Object
|
||||
)
|
||||
|
||||
if err := args.Bind(&what); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seen := make(map[uniqKey]bool)
|
||||
found := ucl.NewListObject()
|
||||
|
||||
if err := eachListOrIterItem(ctx, what, func(idx int, v ucl.Object) error {
|
||||
var key uniqKey
|
||||
switch v := v.(type) {
|
||||
case ucl.StringObject:
|
||||
key = uniqKey{sVal: string(v)}
|
||||
case ucl.IntObject:
|
||||
key = uniqKey{iVal: int(v)}
|
||||
default:
|
||||
return errors.New("expected string or int")
|
||||
}
|
||||
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
found.Append(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
|
|
@ -45,6 +45,37 @@ func TestLists_First(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLists_Uniq(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
eval string
|
||||
want any
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "uniq 1", eval: `lists:uniq [a a a a b b b c c c]`, want: []any{"a", "b", "c"}},
|
||||
{desc: "uniq 2", eval: `lists:uniq [1 2 1 3 2 4 2 5 3]`, want: []any{1, 2, 3, 4, 5}},
|
||||
{desc: "uniq 3", eval: `lists:uniq [1 a 2 b 3 b 2 a 1]5`, want: []any{1, "a", 2, "b", 3}},
|
||||
|
||||
{desc: "uniq err 1", eval: `lists:uniq [[1 2 3] [a:2] ()]`, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.Itrs()),
|
||||
ucl.WithModule(builtins.Lists()),
|
||||
)
|
||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func uclListOf(args ...any) *ucl.ListObject {
|
||||
newList := ucl.NewListObject()
|
||||
for _, arg := range args {
|
||||
|
|
|
@ -103,7 +103,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|||
if i > 0 {
|
||||
sb.WriteString(tok)
|
||||
}
|
||||
sb.WriteString(t.Index(i).String())
|
||||
sb.WriteString(ucl.ObjectToString(t.Index(i)))
|
||||
}
|
||||
return sb.String(), nil
|
||||
case ucl.Iterable:
|
||||
|
@ -120,7 +120,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|||
} else {
|
||||
first = false
|
||||
}
|
||||
sb.WriteString(v.String())
|
||||
sb.WriteString(ucl.ObjectToString(v))
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
|
|
|
@ -183,6 +183,7 @@ func TestStrs_Join(t *testing.T) {
|
|||
{desc: "join 3", eval: `strs:join [a b c] ""`, want: "abc"},
|
||||
{desc: "join 4", eval: `strs:join [a b c]`, want: "abc"},
|
||||
{desc: "join 5", eval: `strs:join (itrs:from [a b c]) ","`, want: "a,b,c"},
|
||||
{desc: "join 6", eval: `strs:join [a () c () e]`, want: "ace"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -81,6 +81,8 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("str", invokableFunc(strBuiltin))
|
||||
rootEC.addCmd("int", invokableFunc(intBuiltin))
|
||||
|
||||
rootEC.addCmd("!nil", invokableFunc(notNilBuiltin))
|
||||
|
||||
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
||||
rootEC.addCmd("sub", invokableFunc(subBuiltin))
|
||||
rootEC.addCmd("mup", invokableFunc(mupBuiltin))
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestInst_Eval(t *testing.T) {
|
|||
{desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
|
||||
{desc: "simple int 1", expr: `firstarg 123`, want: 123},
|
||||
{desc: "simple int 2", expr: `firstarg -234`, want: -234},
|
||||
{desc: "simple ident", expr: `firstarg a-test`, want: "a-test"},
|
||||
{desc: "simple ident 1", expr: `firstarg a-test`, want: "a-test"},
|
||||
|
||||
// String interpolation
|
||||
{desc: "interpolate string 1", expr: `$what = "world" ; firstarg "hello $what"`, want: "hello world"},
|
||||
|
|
|
@ -680,3 +680,10 @@ func isBreakErr(err error) bool {
|
|||
|
||||
return errors.As(err, &errBreak{}) || errors.As(err, &errReturn{}) || errors.Is(err, ErrHalt)
|
||||
}
|
||||
|
||||
func ObjectToString(obj Object) string {
|
||||
if obj == nil {
|
||||
return ""
|
||||
}
|
||||
return obj.String()
|
||||
}
|
||||
|
|
|
@ -1749,6 +1749,44 @@ func TestBuiltins_Cat(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuiltins_NotNil(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want any
|
||||
}{
|
||||
{desc: "not nil 1", expr: `!nil "hello"`, want: true},
|
||||
{desc: "not nil 2", expr: `!nil ""`, want: true},
|
||||
{desc: "not nil 3", expr: `!nil 4`, want: true},
|
||||
{desc: "not nil 4", expr: `!nil 0`, want: true},
|
||||
{desc: "not nil 5", expr: `!nil $true`, want: true},
|
||||
{desc: "not nil 6", expr: `!nil $false`, want: true},
|
||||
{desc: "not nil 7", expr: `!nil [1 2 3]`, want: true},
|
||||
{desc: "not nil 8", expr: `!nil []`, want: true},
|
||||
{desc: "not nil 9", expr: `!nil [a:1 b:21]`, want: true},
|
||||
{desc: "not nil 10", expr: `!nil [:]`, want: true},
|
||||
|
||||
{desc: "not nil 11", expr: `!nil ()`, want: false},
|
||||
|
||||
{desc: "not nil 12", expr: `[1 () 2 () 3] | filter !nil | reduce "" { |x a| "$a $x" }`, want: " 1 2 3"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
inst.SetVar("true", true)
|
||||
inst.SetVar("false", false)
|
||||
|
||||
eqRes, err := inst.EvalString(ctx, tt.expr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, eqRes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
||||
res, err := inst.eval(ctx, strings.NewReader(expr), evalOptions{})
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue