Compare commits
	
		
			No commits in common. "main" and "feature/assign" have entirely different histories.
		
	
	
		
			main
			...
			feature/as
		
	
		
|  | @ -14,7 +14,7 @@ jobs: | ||||||
|       - name: Setup Go |       - name: Setup Go | ||||||
|         uses: actions/setup-go@v2 |         uses: actions/setup-go@v2 | ||||||
|         with: |         with: | ||||||
|           go-version: 1.25 |           go-version: 1.24 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 21.1 |           node-version: 21.1 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ jobs: | ||||||
|       - name: Setup Go |       - name: Setup Go | ||||||
|         uses: actions/setup-go@v2 |         uses: actions/setup-go@v2 | ||||||
|         with: |         with: | ||||||
|           go-version: 1.25 |           go-version: 1.22.4 | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: | |         run: | | ||||||
|           make test |           make test | ||||||
|  | @ -26,7 +26,6 @@ func main() { | ||||||
| 		ucl.WithModule(builtins.Strs()), | 		ucl.WithModule(builtins.Strs()), | ||||||
| 		ucl.WithModule(builtins.Lists()), | 		ucl.WithModule(builtins.Lists()), | ||||||
| 		ucl.WithModule(builtins.Time()), | 		ucl.WithModule(builtins.Time()), | ||||||
| 		ucl.WithModule(builtins.Fns()), |  | ||||||
| 	) | 	) | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
									
									
									
									
								
							|  | @ -1,20 +1,20 @@ | ||||||
| module ucl.lmika.dev | module ucl.lmika.dev | ||||||
| 
 | 
 | ||||||
| go 1.25 | go 1.24 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/alecthomas/participle/v2 v2.1.1 | 	github.com/alecthomas/participle/v2 v2.1.1 | ||||||
| 	github.com/chzyer/readline v1.5.1 | 	github.com/chzyer/readline v1.5.1 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f | ||||||
| 	github.com/yuin/goldmark v1.7.8 | 	github.com/stretchr/testify v1.9.0 | ||||||
| 	go.abhg.dev/goldmark/frontmatter v0.2.0 |  | ||||||
| 	lmika.dev/pkg/modash v0.1.0 |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/BurntSushi/toml v1.2.1 // indirect | 	github.com/BurntSushi/toml v1.2.1 // indirect | ||||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||||
|  | 	github.com/yuin/goldmark v1.7.8 // indirect | ||||||
|  | 	go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect | ||||||
| 	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect | 	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								go.sum
									
									
									
									
									
								
							|  | @ -16,19 +16,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||||
|  | github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f h1:tz68Lhc1oR15HVz69IGbtdukdH0x70kBDEvvj5pTXyE= | ||||||
|  | github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f/go.mod h1:zHQvhjGXRro/Xp2C9dbC+ZUpE0gL4GYW75x1lk7hwzI= | ||||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= | github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= | ||||||
| github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= | github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= | ||||||
| go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= | go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= | ||||||
| go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= | go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= | ||||||
| golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= |  | ||||||
| golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
|  | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= | ||||||
|  | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| lmika.dev/pkg/modash v0.1.0 h1:fltroSvP0nKj9K0E6G+S9LULvB9Qhj47+SZ2b9v/v/c= |  | ||||||
| lmika.dev/pkg/modash v0.1.0/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI= |  | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"lmika.dev/pkg/modash/momap" | 	"github.com/lmika/gopkgs/fp/maps" | ||||||
| 	"os" | 	"os" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"sort" | 	"sort" | ||||||
|  | @ -33,7 +33,7 @@ func (d Doc) config(cmdName string, r *REPL) { | ||||||
| func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) { | func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 	switch { | 	switch { | ||||||
| 	case args.NArgs() == 0: | 	case args.NArgs() == 0: | ||||||
| 		names := momap.Keys(r.commandDocs) | 		names := maps.Keys(r.commandDocs) | ||||||
| 		sort.Strings(names) | 		sort.Strings(names) | ||||||
| 
 | 
 | ||||||
| 		tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) | 		tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import ( | ||||||
| type NoResults struct{} | type NoResults struct{} | ||||||
| 
 | 
 | ||||||
| func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error { | func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error { | ||||||
| 	res, err := r.inst.EvalString(ctx, expr) | 	res, err := r.inst.Eval(ctx, expr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, ucl.ErrNotConvertable) { | 		if errors.Is(err, ucl.ErrNotConvertable) { | ||||||
| 			return nil | 			return nil | ||||||
|  |  | ||||||
|  | @ -98,7 +98,6 @@ type astDotSuffix struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type astDot struct { | type astDot struct { | ||||||
| 	Pos       lexer.Position |  | ||||||
| 	Arg       astCmdArg      `parser:"@@"` | 	Arg       astCmdArg      `parser:"@@"` | ||||||
| 	DotSuffix []astDotSuffix `parser:"( DOT @@ )*"` | 	DotSuffix []astDotSuffix `parser:"( DOT @@ )*"` | ||||||
| } | } | ||||||
|  | @ -149,7 +148,7 @@ var scanner = lexer.MustStateful(lexer.Rules{ | ||||||
| 		{"NL", `[;\n][; \n\t]*`, nil}, | 		{"NL", `[;\n][; \n\t]*`, nil}, | ||||||
| 		{"PIPE", `\|`, nil}, | 		{"PIPE", `\|`, nil}, | ||||||
| 		{"EQ", `=`, nil}, | 		{"EQ", `=`, nil}, | ||||||
| 		{"Ident", `[-!?]*[a-zA-Z_!?-][\w-!?]*`, nil}, | 		{"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil}, | ||||||
| 	}, | 	}, | ||||||
| 	"String": { | 	"String": { | ||||||
| 		{"Escaped", `\\.`, nil}, | 		{"Escaped", `\\.`, nil}, | ||||||
|  | @ -173,6 +172,6 @@ var scanner = lexer.MustStateful(lexer.Rules{ | ||||||
| var parser = participle.MustBuild[astScript](participle.Lexer(scanner), | var parser = participle.MustBuild[astScript](participle.Lexer(scanner), | ||||||
| 	participle.Elide("Whitespace", "Comment")) | 	participle.Elide("Whitespace", "Comment")) | ||||||
| 
 | 
 | ||||||
| func parse(fname string, r io.Reader) (*astScript, error) { | func parse(r io.Reader) (*astScript, error) { | ||||||
| 	return parser.Parse("test", r) | 	return parser.Parse("test", r) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										124
									
								
								ucl/builtins.go
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								ucl/builtins.go
									
									
									
									
									
								
							|  | @ -6,8 +6,6 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 |  | ||||||
| 	"github.com/alecthomas/participle/v2/lexer" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
|  | @ -190,7 +188,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	l := args.args[0] | 	l := args.args[0] | ||||||
| 	r := args.args[1] | 	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) { | func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
|  | @ -201,7 +199,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	l := args.args[0] | 	l := args.args[0] | ||||||
| 	r := args.args[1] | 	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) { | func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
|  | @ -225,7 +223,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		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) { | func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
|  | @ -249,7 +247,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		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) { | func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
|  | @ -283,12 +281,12 @@ func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return BoolObject(!isTruthy(args.args[0])), nil | 	return BoolObject(!args.args[0].Truthy()), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var errObjectsNotEqual = errors.New("objects not equal") | var errObjectsNotEqual = errors.New("objects not equal") | ||||||
| 
 | 
 | ||||||
| func ObjectsEqual(l, r Object) bool { | func objectsEqual(l, r Object) bool { | ||||||
| 	if l == nil || r == nil { | 	if l == nil || r == nil { | ||||||
| 		return l == nil && r == nil | 		return l == nil && r == nil | ||||||
| 	} | 	} | ||||||
|  | @ -316,7 +314,7 @@ func ObjectsEqual(l, r Object) bool { | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
| 		for i := 0; i < lv.Len(); i++ { | 		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 | 				return false | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -334,7 +332,7 @@ func ObjectsEqual(l, r Object) bool { | ||||||
| 			rkv := rv.Value(k) | 			rkv := rv.Value(k) | ||||||
| 			if rkv == nil { | 			if rkv == nil { | ||||||
| 				return errObjectsNotEqual | 				return errObjectsNotEqual | ||||||
| 			} else if !ObjectsEqual(lkv, rkv) { | 			} else if !objectsEqual(lkv, rkv) { | ||||||
| 				return errObjectsNotEqual | 				return errObjectsNotEqual | ||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
|  | @ -372,18 +370,6 @@ func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	return StringObject(args.args[0].String()), nil | 	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) { | func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	if err := args.expectArgn(1); err != nil { | 	if err := args.expectArgn(1); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -511,10 +497,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	return IntObject(0), nil | 	return IntObject(0), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func indexLookup(ctx context.Context, obj, elem Object, pos lexer.Position) (Object, error) { | func indexLookup(ctx context.Context, obj, elem Object) (Object, error) { | ||||||
| 	if obj == nil { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| 	switch v := obj.(type) { | 	switch v := obj.(type) { | ||||||
| 	case Listable: | 	case Listable: | ||||||
| 		intIdx, ok := elem.(IntObject) | 		intIdx, ok := elem.(IntObject) | ||||||
|  | @ -530,48 +513,13 @@ func indexLookup(ctx context.Context, obj, elem Object, pos lexer.Position) (Obj | ||||||
| 	case Hashable: | 	case Hashable: | ||||||
| 		strIdx, ok := elem.(StringObject) | 		strIdx, ok := elem.(StringObject) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, nil | 			return nil, errors.New("expected string for Hashable") | ||||||
| 		} | 		} | ||||||
| 		return v.Value(string(strIdx)), nil | 		return v.Value(string(strIdx)), nil | ||||||
| 	default: |  | ||||||
| 		return nil, notIndexableError(pos) |  | ||||||
| 	} | 	} | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func indexAssign(ctx context.Context, obj, elem, toVal Object, pos lexer.Position) (_ Object, err error) { |  | ||||||
| 	if obj == nil { |  | ||||||
| 		return nil, assignToNilIndex(pos) |  | ||||||
| 	} |  | ||||||
| 	switch v := obj.(type) { |  | ||||||
| 	case ModListable: |  | ||||||
| 		intIdx, ok := elem.(IntObject) |  | ||||||
| 		if !ok { |  | ||||||
| 			return nil, nil |  | ||||||
| 		} |  | ||||||
| 		if int(intIdx) >= 0 && int(intIdx) < v.Len() { |  | ||||||
| 			err = v.SetIndex(int(intIdx), toVal) |  | ||||||
| 		} else if int(intIdx) < 0 && int(intIdx) >= -v.Len() { |  | ||||||
| 			err = v.SetIndex(v.Len()+int(intIdx), toVal) |  | ||||||
| 		} |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return toVal, nil |  | ||||||
| 	case ModHashable: |  | ||||||
| 		strIdx, ok := elem.(StringObject) |  | ||||||
| 		if !ok { |  | ||||||
| 			return nil, nil |  | ||||||
| 		} |  | ||||||
| 		err = v.SetValue(string(strIdx), toVal) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return toVal, nil |  | ||||||
| 	} |  | ||||||
| 	return nil, notModIndexableError(pos) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	if err := args.expectArgn(1); err != nil { | 	if err := args.expectArgn(1); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -579,7 +527,7 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 
 | 
 | ||||||
| 	val := args.args[0] | 	val := args.args[0] | ||||||
| 	for _, idx := range args.args[1:] { | 	for _, idx := range args.args[1:] { | ||||||
| 		newVal, err := indexLookup(ctx, val, idx, lexer.Position{}) | 		newVal, err := indexLookup(ctx, val, idx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -636,52 +584,6 @@ func (mi mappedIter) Next(ctx context.Context) (Object, error) { | ||||||
| 	return mi.inv.invoke(ctx, mi.args.fork([]Object{v})) | 	return mi.inv.invoke(ctx, mi.args.fork([]Object{v})) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func inBuiltin(ctx context.Context, args invocationArgs) (Object, error) { |  | ||||||
| 	if err := args.expectArgn(2); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	r := args.args[1] |  | ||||||
| 
 |  | ||||||
| 	if args.args[0] == nil { |  | ||||||
| 		return BoolObject(false), nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	switch t := args.args[0].(type) { |  | ||||||
| 	case StringObject: |  | ||||||
| 		var rs string |  | ||||||
| 		if r != nil { |  | ||||||
| 			rs = r.String() |  | ||||||
| 		} |  | ||||||
| 		return BoolObject(strings.Contains(t.String(), rs)), nil |  | ||||||
| 	case Listable: |  | ||||||
| 		l := t.Len() |  | ||||||
| 		for i := 0; i < l; i++ { |  | ||||||
| 			v := t.Index(i) |  | ||||||
| 
 |  | ||||||
| 			if ObjectsEqual(v, r) { |  | ||||||
| 				return BoolObject(true), nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return BoolObject(false), nil |  | ||||||
| 	case Hashable: |  | ||||||
| 		v := t.Value(r.String()) |  | ||||||
| 		return BoolObject(v != nil), nil |  | ||||||
| 	case Iterable: |  | ||||||
| 		for t.HasNext() { |  | ||||||
| 			v, err := t.Next(ctx) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 			if ObjectsEqual(v, r) { |  | ||||||
| 				return BoolObject(true), nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return BoolObject(false), nil |  | ||||||
| 	} |  | ||||||
| 	return nil, errors.New("expected listable") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 	if err := args.expectArgn(2); err != nil { | 	if err := args.expectArgn(2); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -788,7 +690,7 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 		} | 		} | ||||||
| 		return &newList, nil | 		return &newList, nil | ||||||
| 	case Hashable: | 	case Hashable: | ||||||
| 		newHash := HashObject{} | 		newHash := hashObject{} | ||||||
| 		if err := t.Each(func(k string, v Object) error { | 		if err := t.Each(func(k string, v Object) error { | ||||||
| 			if m, err := inv.invoke(ctx, args.fork([]Object{StringObject(k), v})); err != nil { | 			if m, err := inv.invoke(ctx, args.fork([]Object{StringObject(k), v})); err != nil { | ||||||
| 				return err | 				return err | ||||||
|  | @ -1259,7 +1161,7 @@ func procBuiltin(ctx context.Context, args macroArgs) (Object, error) { | ||||||
| 
 | 
 | ||||||
| 	obj := procObject{args.eval, args.ec, blockObj.block} | 	obj := procObject{args.eval, args.ec, blockObj.block} | ||||||
| 	if procName != "" { | 	if procName != "" { | ||||||
| 		args.ec.addUserCmd(procName, obj) | 		args.ec.addCmd(procName, obj) | ||||||
| 	} | 	} | ||||||
| 	return obj, nil | 	return obj, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -45,7 +45,7 @@ func TestCSV_ReadRecord(t *testing.T) { | ||||||
| 				ucl.WithOut(&bfr), | 				ucl.WithOut(&bfr), | ||||||
| 			) | 			) | ||||||
| 
 | 
 | ||||||
| 			_, err := inst.EvalString(context.Background(), tt.eval) | 			_, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.wantOut, bfr.String()) | 			assert.Equal(t, tt.wantOut, bfr.String()) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -1,64 +0,0 @@ | ||||||
| package builtins |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 
 |  | ||||||
| 	"lmika.dev/pkg/modash/moslice" |  | ||||||
| 	"ucl.lmika.dev/ucl" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func Fns() ucl.Module { |  | ||||||
| 	return ucl.Module{ |  | ||||||
| 		Name: "fns", |  | ||||||
| 		Builtins: map[string]ucl.BuiltinHandler{ |  | ||||||
| 			"ident": fnsIdent, |  | ||||||
| 			"uni": fnsUni, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func fnsIdent(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var inv ucl.Invokable |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&inv); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return ucl.GoFunction(func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 		if args.NArgs() == 0 { |  | ||||||
| 			return nil, nil |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var x ucl.Object |  | ||||||
| 		if err := args.Bind(&x); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		return inv.Invoke(ctx, x) |  | ||||||
| 	}), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func fnsUni(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var inv ucl.Invokable |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&inv); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	restArgs := moslice.Map(args.RestAsObjects(), func(o ucl.Object) any { return o }) |  | ||||||
| 
 |  | ||||||
| 	return ucl.GoFunction(func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 		fwdArgs := make([]any, len(restArgs)+1) |  | ||||||
| 
 |  | ||||||
| 		var o ucl.Object |  | ||||||
| 		if args.NArgs() != 0 { |  | ||||||
| 			if err := args.Bind(&o); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		fwdArgs[0] = o |  | ||||||
| 		copy(fwdArgs[1:], restArgs) |  | ||||||
| 
 |  | ||||||
| 		return inv.Invoke(ctx, fwdArgs...) |  | ||||||
| 	}), nil |  | ||||||
| } |  | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| package builtins_test |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| 	"testing" |  | ||||||
| 	"ucl.lmika.dev/ucl" |  | ||||||
| 	"ucl.lmika.dev/ucl/builtins" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func TestFns_Uni(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		descr string |  | ||||||
| 		eval  string |  | ||||||
| 		want  any |  | ||||||
| 	}{ |  | ||||||
| 		{descr: "uni 1", eval: `s = fns:uni add 2 ; $s 3`, want: 5}, |  | ||||||
| 		{descr: "uni 2", eval: `s = fns:uni (proc { |x| add $x 1 }) ; $s 3`, want: 4}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.descr, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Fns()), |  | ||||||
| 			) |  | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.Equal(t, tt.want, res) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -29,7 +29,7 @@ func TestFS_Cat(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.FS(testFS)), | 				ucl.WithModule(builtins.FS(testFS)), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ func TestItrs_ToList(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Itrs()), | 				ucl.WithModule(builtins.Itrs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package builtins | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 
 |  | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -11,46 +10,11 @@ func Lists() ucl.Module { | ||||||
| 	return ucl.Module{ | 	return ucl.Module{ | ||||||
| 		Name: "lists", | 		Name: "lists", | ||||||
| 		Builtins: map[string]ucl.BuiltinHandler{ | 		Builtins: map[string]ucl.BuiltinHandler{ | ||||||
| 			"append":  listAppend, |  | ||||||
| 			"first": listFirst, | 			"first": listFirst, | ||||||
| 			"batch":   listBatch, |  | ||||||
| 			"uniq":    listUniq, |  | ||||||
| 			"sublist": listSublist, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func listAppend(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var ( |  | ||||||
| 		what ucl.Object |  | ||||||
| 		item ucl.Object |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&what); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if what == nil { |  | ||||||
| 		what = ucl.NewListObject() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t, ok := what.(ucl.ModListable) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, errors.New("expected mutable list") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for args.NArgs() > 0 { |  | ||||||
| 		if err := args.Bind(&item); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if err := t.Insert(-1, item); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return t, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) { | func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		what  ucl.Object | 		what  ucl.Object | ||||||
|  | @ -94,149 +58,3 @@ func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 
 | 
 | ||||||
| 	return newList, nil | 	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 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func listBatch(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var ( |  | ||||||
| 		what      ucl.Object |  | ||||||
| 		groupSize int |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&what, &groupSize); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if groupSize <= 0 { |  | ||||||
| 		return nil, errors.New("group size must be > 0") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	groups := ucl.NewListObject() |  | ||||||
| 	var thisGroup *ucl.ListObject |  | ||||||
| 
 |  | ||||||
| 	if err := eachListOrIterItem(ctx, what, func(idx int, v ucl.Object) error { |  | ||||||
| 		if thisGroup == nil || thisGroup.Len() == groupSize { |  | ||||||
| 			thisGroup = ucl.NewListObject() |  | ||||||
| 			groups.Append(thisGroup) |  | ||||||
| 		} |  | ||||||
| 		thisGroup.Append(v) |  | ||||||
| 		return nil |  | ||||||
| 	}); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return groups, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func listSublist(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var ( |  | ||||||
| 		l             ucl.Listable |  | ||||||
| 		from, fromIdx int |  | ||||||
| 		to, toIdx     int |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&l, &from); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if l == nil { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if args.NArgs() >= 1 { |  | ||||||
| 		if err := args.Bind(&to); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		fromIdx, toIdx = listPos(l, from), listPos(l, to) |  | ||||||
| 	} else { |  | ||||||
| 		if from < 0 { |  | ||||||
| 			fromIdx, toIdx = listPos(l, from), l.Len() |  | ||||||
| 		} else { |  | ||||||
| 			fromIdx, toIdx = 0, listPos(l, from) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if fromIdx > toIdx { |  | ||||||
| 		return ucl.NewListObject(), nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	newList := ucl.NewListObjectOfLength(toIdx - fromIdx) |  | ||||||
| 	for i := fromIdx; i < toIdx; i++ { |  | ||||||
| 		if err := newList.SetIndex(i-fromIdx, l.Index(i)); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return newList, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func listPos(l ucl.Listable, pos int) int { |  | ||||||
| 	if pos < 0 { |  | ||||||
| 		return max(l.Len()+pos, 0) |  | ||||||
| 	} |  | ||||||
| 	return min(pos, l.Len()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -2,51 +2,12 @@ package builtins_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"testing" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"testing" | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| 	"ucl.lmika.dev/ucl/builtins" | 	"ucl.lmika.dev/ucl/builtins" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestLists_Append(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "append 1", eval: `lists:append [1 2 3] 4`, want: []any{1, 2, 3, 4}}, |  | ||||||
| 		{desc: "append 2", eval: `lists:append [1 2 3] 4 5 6`, want: []any{1, 2, 3, 4, 5, 6}}, |  | ||||||
| 		{desc: "append 3", eval: `lists:append [] 1 2 3`, want: []any{1, 2, 3}}, |  | ||||||
| 		{desc: "append 4", eval: `lists:append () 1 2 3`, want: []any{1, 2, 3}}, |  | ||||||
| 		{desc: "append 5", eval: `lists:append [1 2 3]`, want: []any{1, 2, 3}}, |  | ||||||
| 		{desc: "append 6", eval: `l = [] ; lists:append $l 1 2 3 ; $l`, want: []any{1, 2, 3}}, |  | ||||||
| 		{desc: "append 7", eval: `l = [1 2 3] ; lists:append $l [4 5 6] ; $l`, want: []any{1, 2, 3, []any{4, 5, 6}}}, |  | ||||||
| 		{desc: "append 8", eval: `lists:append (seq 3 | itrs:from | itrs:to-list) 4 5 6`, want: []any{0, 1, 2, 4, 5, 6}}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `lists:append "asa" 1`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `lists:append 123 1`, wantErr: true}, |  | ||||||
| 		{desc: "err 3", eval: `lists:append [:] 1`, 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 TestLists_First(t *testing.T) { | func TestLists_First(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		desc    string | 		desc    string | ||||||
|  | @ -73,115 +34,7 @@ func TestLists_First(t *testing.T) { | ||||||
| 				ucl.WithModule(builtins.Itrs()), | 				ucl.WithModule(builtins.Itrs()), | ||||||
| 				ucl.WithModule(builtins.Lists()), | 				ucl.WithModule(builtins.Lists()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { |  | ||||||
| 				assert.Error(t, err) |  | ||||||
| 			} else { |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want, res) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestLists_Batch(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "batch 1", eval: `lists:batch [1 2 3 4 5] 2`, want: []any{[]any{1, 2}, []any{3, 4}, []any{5}}}, |  | ||||||
| 		{desc: "batch 2", eval: `lists:batch [1 2 3 4] 2`, want: []any{[]any{1, 2}, []any{3, 4}}}, |  | ||||||
| 		{desc: "batch 3", eval: `lists:batch [1 2 3 4 5] 3`, want: []any{[]any{1, 2, 3}, []any{4, 5}}}, |  | ||||||
| 		{desc: "batch 4", eval: `lists:batch [1 2 3 4 5] 12`, want: []any{[]any{1, 2, 3, 4, 5}}}, |  | ||||||
| 		{desc: "batch 5", eval: `lists:batch [1 2 3 4 5] 1`, want: []any{[]any{1}, []any{2}, []any{3}, []any{4}, []any{5}}}, |  | ||||||
| 		{desc: "batch 6", eval: `lists:batch [1] 12`, want: []any{[]any{1}}}, |  | ||||||
| 		{desc: "batch 7", eval: `lists:batch [] 12`, want: []any{}}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `lists:batch [1 2 3] -3`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `lists:batch [1 2 3] 0`, 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 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 TestStrs_Sublist(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "sublist 1", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 5`, want: []any{"h", "e", "l", "l", "o"}}, |  | ||||||
| 		{desc: "sublist 2", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 5000`, want: []any{"h", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d"}}, |  | ||||||
| 		{desc: "sublist 3", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -5`, want: []any{"w", "o", "r", "l", "d"}}, |  | ||||||
| 		{desc: "sublist 4", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -5000`, want: []any{"h", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d"}}, |  | ||||||
| 		{desc: "sublist 5", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 0 5`, want: []any{"h", "e", "l", "l", "o"}}, |  | ||||||
| 		{desc: "sublist 6", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 10`, want: []any{"l", "o", ",", " ", "w", "o", "r"}}, |  | ||||||
| 		{desc: "sublist 7", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 10000`, want: []any{"l", "o", ",", " ", "w", "o", "r", "l", "d"}}, |  | ||||||
| 		{desc: "sublist 8", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 3 -5`, want: []any{"l", "o", ",", " "}}, |  | ||||||
| 		{desc: "sublist 9", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] -9 -5`, want: []any{"l", "o", ",", " "}}, |  | ||||||
| 		{desc: "sublist 10", eval: `lists:sublist [h e l l o ',' ' ' w o r l d] 8 5`, want: []any{}}, |  | ||||||
| 		{desc: "sublist 11", eval: `lists:sublist [] 8 5`, want: []any{}}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `lists:sublist`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `lists:sublist "asd"`, wantErr: true}, |  | ||||||
| 		{desc: "err 3", eval: `lists:sublist ["asd"]`, wantErr: true}, |  | ||||||
| 		{desc: "err 4", eval: `lists:sublist () 8 5`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Lists()), |  | ||||||
| 			) |  | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) |  | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ func TestLog_Puts(t *testing.T) { | ||||||
| 				})), | 				})), | ||||||
| 			) | 			) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  |  | ||||||
|  | @ -2,33 +2,20 @@ package builtins | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" |  | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" |  | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type OSProvider interface { |  | ||||||
| 	LookupEnv(string) (string, bool) |  | ||||||
| 	Exec(ctx context.Context, cmd string, args ...string) (*exec.Cmd, error) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type osHandlers struct { | type osHandlers struct { | ||||||
| 	provider OSProvider |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func OS() ucl.Module { | func OS() ucl.Module { | ||||||
| 	osh := osHandlers{ | 	osh := osHandlers{} | ||||||
| 		provider: builtinOSProvider{}, |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return ucl.Module{ | 	return ucl.Module{ | ||||||
| 		Name: "os", | 		Name: "os", | ||||||
| 		Builtins: map[string]ucl.BuiltinHandler{ | 		Builtins: map[string]ucl.BuiltinHandler{ | ||||||
| 			"env": osh.env, | 			"env": osh.env, | ||||||
| 			"exec": osh.exec, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | @ -39,7 +26,7 @@ func (oh osHandlers) env(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	val, ok := oh.provider.LookupEnv(envName) | 	val, ok := os.LookupEnv(envName) | ||||||
| 	if ok { | 	if ok { | ||||||
| 		return val, nil | 		return val, nil | ||||||
| 	} | 	} | ||||||
|  | @ -51,50 +38,3 @@ func (oh osHandlers) env(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 
 | 
 | ||||||
| 	return "", nil | 	return "", nil | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func (oh osHandlers) exec(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var cmdArgs []string |  | ||||||
| 
 |  | ||||||
| 	for args.NArgs() > 0 { |  | ||||||
| 		var s string |  | ||||||
| 		if err := args.Bind(&s); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		cmdArgs = append(cmdArgs, s) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(cmdArgs) == 0 { |  | ||||||
| 		return nil, errors.New("expected command") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cmd, err := oh.provider.Exec(ctx, cmdArgs[0], cmdArgs[1:]...) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if args.HasSwitch("in") { |  | ||||||
| 		var inVal string |  | ||||||
| 		if err := args.BindSwitch("in", &inVal); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		cmd.Stdin = strings.NewReader(inVal) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	res, err := cmd.Output() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return string(res), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type builtinOSProvider struct{} |  | ||||||
| 
 |  | ||||||
| func (builtinOSProvider) LookupEnv(key string) (string, bool) { |  | ||||||
| 	return os.LookupEnv(key) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (builtinOSProvider) Exec(ctx context.Context, name string, args ...string) (*exec.Cmd, error) { |  | ||||||
| 	return exec.CommandContext(ctx, name, args...), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -2,10 +2,8 @@ package builtins_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"testing" | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| 	"ucl.lmika.dev/ucl/builtins" | 	"ucl.lmika.dev/ucl/builtins" | ||||||
| ) | ) | ||||||
|  | @ -30,30 +28,7 @@ func TestOS_Env(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.OS()), | 				ucl.WithModule(builtins.OS()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.Equal(t, tt.want, res) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestOS_Exec(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		descr string |  | ||||||
| 		eval  string |  | ||||||
| 		want  any |  | ||||||
| 	}{ |  | ||||||
| 		{descr: "run command 1", eval: `os:exec "echo" "hello, world"`, want: "hello, world\n"}, |  | ||||||
| 		{descr: "run command 2", eval: `os:exec "date" "+%Y%m%d"`, want: time.Now().Format("20060102") + "\n"}, |  | ||||||
| 		{descr: "run command 3", eval: `os:exec "tr" "[a-z]" "[A-Z]" -in "hello"`, want: "HELLO"}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.descr, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.OS()), |  | ||||||
| 			) |  | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) |  | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 |  | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -18,68 +17,10 @@ func Strs() ucl.Module { | ||||||
| 			"split":      split, | 			"split":      split, | ||||||
| 			"join":       join, | 			"join":       join, | ||||||
| 			"has-prefix": hasPrefix, | 			"has-prefix": hasPrefix, | ||||||
| 			"has-suffix":  hasSuffix, |  | ||||||
| 			"trim-prefix": trimPrefix, |  | ||||||
| 			"trim-suffix": trimSuffix, |  | ||||||
| 			"substr":      strsSubstr, |  | ||||||
| 			"replace":     strsReplace, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func strsReplace(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var ( |  | ||||||
| 		s    string |  | ||||||
| 		from string |  | ||||||
| 		to   string |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&s, &from, &to); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var count = -1 |  | ||||||
| 	if args.HasSwitch("n") { |  | ||||||
| 		if err := args.BindSwitch("n", &count); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return strings.Replace(s, from, to, count), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func strsSubstr(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var ( |  | ||||||
| 		s    string |  | ||||||
| 		from int |  | ||||||
| 		to   int |  | ||||||
| 	) |  | ||||||
| 
 |  | ||||||
| 	if err := args.Bind(&s, &from); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if args.NArgs() >= 1 { |  | ||||||
| 		if err := args.Bind(&to); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		ffr := strPos(s, from) |  | ||||||
| 		tfr := strPos(s, to) |  | ||||||
| 		if ffr > tfr { |  | ||||||
| 			return "", nil |  | ||||||
| 		} |  | ||||||
| 		return s[ffr:tfr], nil |  | ||||||
| 
 |  | ||||||
| 	} else { |  | ||||||
| 		if from < 0 { |  | ||||||
| 			return s[strPos(s, from):], nil |  | ||||||
| 		} else { |  | ||||||
| 			return s[:strPos(s, from)], nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func toUpper(ctx context.Context, args ucl.CallArgs) (any, error) { | func toUpper(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 	var s string | 	var s string | ||||||
| 	if err := args.Bind(&s); err != nil { | 	if err := args.Bind(&s); err != nil { | ||||||
|  | @ -116,42 +57,19 @@ func hasPrefix(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 	return strings.HasPrefix(s, prefix), nil | 	return strings.HasPrefix(s, prefix), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func hasSuffix(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var s, suffix string |  | ||||||
| 	if err := args.Bind(&s, &suffix); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return strings.HasSuffix(s, suffix), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func trimPrefix(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var s, prefix string |  | ||||||
| 	if err := args.Bind(&s, &prefix); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return strings.TrimPrefix(s, prefix), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func trimSuffix(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 	var s, suffix string |  | ||||||
| 	if err := args.Bind(&s, &suffix); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return strings.TrimSuffix(s, suffix), nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func split(ctx context.Context, args ucl.CallArgs) (any, error) { | func split(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 	var ( | 	var s string | ||||||
| 		s   string | 	if err := args.Bind(&s); err != nil { | ||||||
| 		sep string |  | ||||||
| 	) |  | ||||||
| 	if err := args.Bind(&s, &sep); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	sep := "" | ||||||
|  | 	if args.NArgs() > 0 { | ||||||
|  | 		if err := args.Bind(&sep); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	n := -1 | 	n := -1 | ||||||
| 	if args.HasSwitch("max") { | 	if args.HasSwitch("max") { | ||||||
| 		if err := args.BindSwitch("max", &n); err != nil { | 		if err := args.BindSwitch("max", &n); err != nil { | ||||||
|  | @ -185,7 +103,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 			if i > 0 { | 			if i > 0 { | ||||||
| 				sb.WriteString(tok) | 				sb.WriteString(tok) | ||||||
| 			} | 			} | ||||||
| 			sb.WriteString(ucl.ObjectToString(t.Index(i))) | 			sb.WriteString(t.Index(i).String()) | ||||||
| 		} | 		} | ||||||
| 		return sb.String(), nil | 		return sb.String(), nil | ||||||
| 	case ucl.Iterable: | 	case ucl.Iterable: | ||||||
|  | @ -202,7 +120,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 			} else { | 			} else { | ||||||
| 				first = false | 				first = false | ||||||
| 			} | 			} | ||||||
| 			sb.WriteString(ucl.ObjectToString(v)) | 			sb.WriteString(v.String()) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return sb.String(), nil | 		return sb.String(), nil | ||||||
|  | @ -210,10 +128,3 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 
 | 
 | ||||||
| 	return nil, errors.New("expected listable or iterable as arg 1") | 	return nil, errors.New("expected listable or iterable as arg 1") | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func strPos(s string, pos int) int { |  | ||||||
| 	if pos < 0 { |  | ||||||
| 		return max(len(s)+pos, 0) |  | ||||||
| 	} |  | ||||||
| 	return min(pos, len(s)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -2,9 +2,8 @@ package builtins_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"testing" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"testing" | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| 	"ucl.lmika.dev/ucl/builtins" | 	"ucl.lmika.dev/ucl/builtins" | ||||||
| ) | ) | ||||||
|  | @ -29,7 +28,7 @@ func TestStrs_ToUpper(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -60,7 +59,7 @@ func TestStrs_ToLower(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -91,7 +90,7 @@ func TestStrs_Trim(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -123,7 +122,7 @@ func TestStrs_HasPrefix(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -146,15 +145,14 @@ func TestStrs_Split(t *testing.T) { | ||||||
| 		{desc: "split 3", eval: `strs:split "" ";"`, want: []string{""}}, | 		{desc: "split 3", eval: `strs:split "" ";"`, want: []string{""}}, | ||||||
| 		{desc: "split 4", eval: `strs:split " " ";"`, want: []string{" "}}, | 		{desc: "split 4", eval: `strs:split " " ";"`, want: []string{" "}}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "split by char 1", eval: `strs:split "123" ""`, want: []string{"1", "2", "3"}}, | 		{desc: "split by char 1", eval: `strs:split "123"`, want: []string{"1", "2", "3"}}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: []string{"1", "2,3"}}, | 		{desc: "split max 1", eval: `strs:split "1,2,3" "," -max 2`, want: []string{"1", "2,3"}}, | ||||||
| 		{desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: []string{"1", "2", "3"}}, | 		{desc: "split max 2", eval: `strs:split "1,2,3" "," -max 5`, want: []string{"1", "2", "3"}}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "split by char max 1", eval: `strs:split "12345" "" -max 3`, want: []string{"1", "2", "345"}}, | 		{desc: "split by char max 1", eval: `strs:split "12345" -max 3`, want: []string{"1", "2", "345"}}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "err 1", eval: `strs:split "1,2,3" -max []`, wantErr: true}, | 		{desc: "err 1", eval: `strs:split "1,2,3" -max []`, wantErr: true}, | ||||||
| 		{desc: "err 1", eval: `strs:split "1,2,3"`, wantErr: true}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -162,7 +160,7 @@ func TestStrs_Split(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -185,7 +183,6 @@ func TestStrs_Join(t *testing.T) { | ||||||
| 		{desc: "join 3", eval: `strs:join [a b c] ""`, want: "abc"}, | 		{desc: "join 3", eval: `strs:join [a b c] ""`, want: "abc"}, | ||||||
| 		{desc: "join 4", 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 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 { | 	for _, tt := range tests { | ||||||
|  | @ -194,188 +191,7 @@ func TestStrs_Join(t *testing.T) { | ||||||
| 				ucl.WithModule(builtins.Itrs()), | 				ucl.WithModule(builtins.Itrs()), | ||||||
| 				ucl.WithModule(builtins.Strs()), | 				ucl.WithModule(builtins.Strs()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { |  | ||||||
| 				assert.Error(t, err) |  | ||||||
| 			} else { |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want, res) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestStrs_HasSuffix(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "has suffix 1", eval: `strs:has-suffix "hello, world" "world"`, want: true}, |  | ||||||
| 		{desc: "has suffix 2", eval: `strs:has-suffix "hello, world" "hello"`, want: false}, |  | ||||||
| 		{desc: "has suffix 3", eval: `strs:has-suffix "" "world"`, want: false}, |  | ||||||
| 		{desc: "has suffix 4", eval: `strs:has-suffix "hello" ""`, want: true}, |  | ||||||
| 		{desc: "has suffix 5", eval: `strs:has-suffix "test.txt" ".txt"`, want: true}, |  | ||||||
| 		{desc: "has suffix 6", eval: `strs:has-suffix "test.txt" ".pdf"`, want: false}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `strs:has-suffix`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `strs:has-suffix "asd"`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Strs()), |  | ||||||
| 			) |  | ||||||
| 			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 TestStrs_TrimPrefix(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "trim prefix 1", eval: `strs:trim-prefix "hello, world" "hello, "`, want: "world"}, |  | ||||||
| 		{desc: "trim prefix 2", eval: `strs:trim-prefix "goodbye, world" "hello"`, want: "goodbye, world"}, |  | ||||||
| 		{desc: "trim prefix 3", eval: `strs:trim-prefix "" "world"`, want: ""}, |  | ||||||
| 		{desc: "trim prefix 4", eval: `strs:trim-prefix "hello" ""`, want: "hello"}, |  | ||||||
| 		{desc: "trim prefix 5", eval: `strs:trim-prefix "test.txt" "test"`, want: ".txt"}, |  | ||||||
| 		{desc: "trim prefix 6", eval: `strs:trim-prefix "/path/to/file" "/path/"`, want: "to/file"}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `strs:trim-prefix`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `strs:trim-prefix "asd"`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Strs()), |  | ||||||
| 			) |  | ||||||
| 			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 TestStrs_TrimSuffix(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "trim suffix 1", eval: `strs:trim-suffix "hello, world" ", world"`, want: "hello"}, |  | ||||||
| 		{desc: "trim suffix 2", eval: `strs:trim-suffix "hello, world" "goodbye"`, want: "hello, world"}, |  | ||||||
| 		{desc: "trim suffix 3", eval: `strs:trim-suffix "" "world"`, want: ""}, |  | ||||||
| 		{desc: "trim suffix 4", eval: `strs:trim-suffix "hello" ""`, want: "hello"}, |  | ||||||
| 		{desc: "trim suffix 5", eval: `strs:trim-suffix "test.txt" ".txt"`, want: "test"}, |  | ||||||
| 		{desc: "trim suffix 6", eval: `strs:trim-suffix "file.backup" ".backup"`, want: "file"}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `strs:trim-suffix`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `strs:trim-suffix "asd"`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Strs()), |  | ||||||
| 			) |  | ||||||
| 			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 TestStrs_Substr(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "substr 1", eval: `strs:substr "hello, world" 5`, want: "hello"}, |  | ||||||
| 		{desc: "substr 2", eval: `strs:substr "hello, world" 5000`, want: "hello, world"}, |  | ||||||
| 		{desc: "substr 3", eval: `strs:substr "hello, world" -5`, want: "world"}, |  | ||||||
| 		{desc: "substr 4", eval: `strs:substr "hello, world" -5000`, want: "hello, world"}, |  | ||||||
| 		{desc: "substr 5", eval: `strs:substr "hello, world" 0 5`, want: "hello"}, |  | ||||||
| 		{desc: "substr 6", eval: `strs:substr "hello, world" 3 10`, want: "lo, wor"}, |  | ||||||
| 		{desc: "substr 7", eval: `strs:substr "hello, world" 3 10000`, want: "lo, world"}, |  | ||||||
| 		{desc: "substr 8", eval: `strs:substr "hello, world" 3 -5`, want: "lo, "}, |  | ||||||
| 		{desc: "substr 9", eval: `strs:substr "hello, world" -9 -5`, want: "lo, "}, |  | ||||||
| 		{desc: "substr 10", eval: `strs:substr "hello, world" 8 5`, want: ""}, |  | ||||||
| 		{desc: "substr 11", eval: `strs:substr "" 8 5`, want: ""}, |  | ||||||
| 		{desc: "substr 12", eval: `strs:substr () 8 5`, want: ""}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `strs:substr`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `strs:substr "asd"`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Strs()), |  | ||||||
| 			) |  | ||||||
| 			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 TestStrs_Replace(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc    string |  | ||||||
| 		eval    string |  | ||||||
| 		want    any |  | ||||||
| 		wantErr bool |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "replace 1", eval: `strs:replace "hello hello hello" "hello" "world"`, want: "world world world"}, |  | ||||||
| 		{desc: "replace 2", eval: `strs:replace "hello hi hello hi" "hello" "lo"`, want: "lo hi lo hi"}, |  | ||||||
| 		{desc: "replace 3", eval: `strs:replace "hello hello hello" "hello" "world" -n 1`, want: "world hello hello"}, |  | ||||||
| 		{desc: "replace 4", eval: `strs:replace "hello hello hello" "hello" "world" -n 2`, want: "world world hello"}, |  | ||||||
| 		{desc: "replace 5", eval: `strs:replace "hello hello hello" "hello" "world" -n 0`, want: "hello hello hello"}, |  | ||||||
| 		{desc: "replace 6", eval: `strs:replace "each one" "" "|"`, want: "|e|a|c|h| |o|n|e|"}, |  | ||||||
| 		{desc: "replace 7", eval: `strs:replace "hello hello hello" "hello" ""`, want: "  "}, |  | ||||||
| 		{desc: "replace 8", eval: `strs:replace "nothing to replace here" "what" "why"`, want: "nothing to replace here"}, |  | ||||||
| 		{desc: "replace 9", eval: `strs:replace "" "what" "why"`, want: ""}, |  | ||||||
| 		{desc: "replace 10", eval: `strs:replace "" "hello" "world" -n 0`, want: ""}, |  | ||||||
| 
 |  | ||||||
| 		{desc: "err 1", eval: `strs:replace`, wantErr: true}, |  | ||||||
| 		{desc: "err 2", eval: `strs:replace "asd"`, wantErr: true}, |  | ||||||
| 		{desc: "err 3", eval: `strs:replace "asd" "asd"`, wantErr: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			inst := ucl.New( |  | ||||||
| 				ucl.WithModule(builtins.Strs()), |  | ||||||
| 			) |  | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) |  | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ func TestTime_FromUnix(t *testing.T) { | ||||||
| 			inst := ucl.New( | 			inst := ucl.New( | ||||||
| 				ucl.WithModule(builtins.Time()), | 				ucl.WithModule(builtins.Time()), | ||||||
| 			) | 			) | ||||||
| 			res, err := inst.EvalString(context.Background(), tt.eval) | 			res, err := inst.Eval(context.Background(), tt.eval) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -47,7 +47,7 @@ func TestTime_Sleep(t *testing.T) { | ||||||
| 			ucl.WithModule(builtins.Time()), | 			ucl.WithModule(builtins.Time()), | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		_, err := inst.EvalString(ctx, `time:sleep 1`) | 		_, err := inst.Eval(ctx, `time:sleep 1`) | ||||||
| 		assert.Error(t, err) | 		assert.Error(t, err) | ||||||
| 		assert.Equal(t, "context canceled", err.Error()) | 		assert.Equal(t, "context canceled", err.Error()) | ||||||
| 		assert.True(t, time.Now().Sub(st) < time.Second) | 		assert.True(t, time.Now().Sub(st) < time.Second) | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								ucl/env.go
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								ucl/env.go
									
									
									
									
									
								
							|  | @ -7,11 +7,12 @@ type evalCtx struct { | ||||||
| 	macros     map[string]macroable | 	macros     map[string]macroable | ||||||
| 	vars       map[string]Object | 	vars       map[string]Object | ||||||
| 	pseudoVars map[string]pseudoVar | 	pseudoVars map[string]pseudoVar | ||||||
| 	userCommandFrame bool // Frame to use for user-defined commands
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ec *evalCtx) forkAndIsolate() *evalCtx { | func (ec *evalCtx) forkAndIsolate() *evalCtx { | ||||||
| 	return &evalCtx{parent: ec, root: ec.root, userCommandFrame: true} | 	newEc := &evalCtx{parent: ec} | ||||||
|  | 	newEc.root = newEc | ||||||
|  | 	return newEc | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ec *evalCtx) fork() *evalCtx { | func (ec *evalCtx) fork() *evalCtx { | ||||||
|  | @ -26,25 +27,6 @@ func (ec *evalCtx) addCmd(name string, inv invokable) { | ||||||
| 	ec.root.commands[name] = inv | 	ec.root.commands[name] = inv | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ec *evalCtx) addUserCmd(name string, inv invokable) { |  | ||||||
| 	frame := ec |  | ||||||
| 	for frame != nil { |  | ||||||
| 		if frame.userCommandFrame { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 		frame = frame.parent |  | ||||||
| 	} |  | ||||||
| 	if frame == nil { |  | ||||||
| 		panic("no user command frame found") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if frame.commands == nil { |  | ||||||
| 		frame.commands = make(map[string]invokable) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	frame.commands[name] = inv |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ec *evalCtx) addMacro(name string, inv macroable) { | func (ec *evalCtx) addMacro(name string, inv macroable) { | ||||||
| 	if ec.root.macros == nil { | 	if ec.root.macros == nil { | ||||||
| 		ec.root.macros = make(map[string]macroable) | 		ec.root.macros = make(map[string]macroable) | ||||||
|  | @ -79,9 +61,6 @@ func (ec *evalCtx) setOrDefineVar(name string, val Object) { | ||||||
| 
 | 
 | ||||||
| func (ec *evalCtx) getVar(name string) (Object, bool) { | func (ec *evalCtx) getVar(name string) (Object, bool) { | ||||||
| 	if ec.vars == nil { | 	if ec.vars == nil { | ||||||
| 		if ec.parent == nil { |  | ||||||
| 			return nil, false |  | ||||||
| 		} |  | ||||||
| 		return ec.parent.getVar(name) | 		return ec.parent.getVar(name) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ package ucl | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 |  | ||||||
| 	"github.com/alecthomas/participle/v2/lexer" | 	"github.com/alecthomas/participle/v2/lexer" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -13,9 +12,6 @@ var ( | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally") | 	tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally") | ||||||
| 	notIndexableError         = newBadUsage("index only support on lists and hashes") |  | ||||||
| 	notModIndexableError      = newBadUsage("list or hash cannot be modified") |  | ||||||
| 	assignToNilIndex          = newBadUsage("assigning to nil index value") |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type errorWithPos struct { | type errorWithPos struct { | ||||||
|  |  | ||||||
							
								
								
									
										67
									
								
								ucl/eval.go
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								ucl/eval.go
									
									
									
									
									
								
							|  | @ -5,8 +5,6 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 |  | ||||||
| 	"github.com/alecthomas/participle/v2/lexer" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type evaluator struct { | type evaluator struct { | ||||||
|  | @ -207,7 +205,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object, | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		res, err = indexLookup(ctx, res, idx, n.Pos) | 		res, err = indexLookup(ctx, res, idx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -220,51 +218,7 @@ func (e evaluator) assignDot(ctx context.Context, ec *evalCtx, n astDot, toVal O | ||||||
| 		return e.assignArg(ctx, ec, n.Arg, toVal) | 		return e.assignArg(ctx, ec, n.Arg, toVal) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	val, err := e.evalArgForDotAssign(ctx, ec, n.Arg) | 	return nil, errors.New("TODO") | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for i, dot := range n.DotSuffix { |  | ||||||
| 		isLast := i == len(n.DotSuffix)-1 |  | ||||||
| 
 |  | ||||||
| 		var idx Object |  | ||||||
| 		if dot.KeyIdent != nil { |  | ||||||
| 			idx = StringObject(dot.KeyIdent.String()) |  | ||||||
| 		} else { |  | ||||||
| 			idx, err = e.evalPipeline(ctx, ec, dot.Pipeline) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if isLast { |  | ||||||
| 			val, err = indexAssign(ctx, val, idx, toVal, n.Pos) |  | ||||||
| 		} else { |  | ||||||
| 			val, err = indexLookup(ctx, val, idx, n.Pos) |  | ||||||
| 		} |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return val, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e evaluator) evalArgForDotAssign(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { |  | ||||||
| 	// Special case for dot assigns of 'a.b = c' where a is actually a var deref (i.e. $a)
 |  | ||||||
| 	// which is unnecessary for assignments. Likewise, having '$a.b = c' should be dissallowed
 |  | ||||||
| 
 |  | ||||||
| 	switch { |  | ||||||
| 	case n.Ident != nil: |  | ||||||
| 		if v, ok := ec.getVar(n.Ident.String()); ok { |  | ||||||
| 			return v, nil |  | ||||||
| 		} |  | ||||||
| 		return nil, nil |  | ||||||
| 	case n.Var != nil: |  | ||||||
| 		return nil, errors.New("cannot assign to a dereferenced variable") |  | ||||||
| 	} |  | ||||||
| 	return e.evalArg(ctx, ec, n) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { | func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { | ||||||
|  | @ -287,7 +241,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec | ||||||
| 			return mph.get(ctx, *n.PseudoVar) | 			return mph.get(ctx, *n.PseudoVar) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return nil, errors.New("unknown pseudo-variable: " + *n.PseudoVar) | 		return nil, errors.New("unknown pseudo-variable: " + *n.Var) | ||||||
| 	case n.MaybeSub != nil: | 	case n.MaybeSub != nil: | ||||||
| 		sub := n.MaybeSub.Sub | 		sub := n.MaybeSub.Sub | ||||||
| 		if sub == nil { | 		if sub == nil { | ||||||
|  | @ -304,13 +258,12 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec | ||||||
| 
 | 
 | ||||||
| func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) { | func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) { | ||||||
| 	switch { | 	switch { | ||||||
| 	case n.Ident != nil: |  | ||||||
| 		ec.setOrDefineVar(n.Ident.String(), toVal) |  | ||||||
| 		return toVal, nil |  | ||||||
| 	case n.Literal != nil: | 	case n.Literal != nil: | ||||||
| 		return nil, errors.New("cannot assign to a literal value") | 		// We may use this for variable setting?
 | ||||||
|  | 		return nil, errors.New("cannot assign to a literal") | ||||||
| 	case n.Var != nil: | 	case n.Var != nil: | ||||||
| 		return nil, errors.New("cannot assign to a dereferenced variable") | 		ec.setOrDefineVar(*n.Var, toVal) | ||||||
|  | 		return toVal, nil | ||||||
| 	case n.PseudoVar != nil: | 	case n.PseudoVar != nil: | ||||||
| 		pvar, ok := ec.getPseudoVar(*n.PseudoVar) | 		pvar, ok := ec.getPseudoVar(*n.PseudoVar) | ||||||
| 		if ok { | 		if ok { | ||||||
|  | @ -341,11 +294,11 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList | ||||||
| 	if loh.EmptyList { | 	if loh.EmptyList { | ||||||
| 		return &ListObject{}, nil | 		return &ListObject{}, nil | ||||||
| 	} else if loh.EmptyHash { | 	} else if loh.EmptyHash { | ||||||
| 		return HashObject{}, nil | 		return hashObject{}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if firstIsHash := loh.Elements[0].Right != nil; firstIsHash { | 	if firstIsHash := loh.Elements[0].Right != nil; firstIsHash { | ||||||
| 		h := HashObject{} | 		h := hashObject{} | ||||||
| 		for _, el := range loh.Elements { | 		for _, el := range loh.Elements { | ||||||
| 			if el.Right == nil { | 			if el.Right == nil { | ||||||
| 				return nil, errors.New("miss-match of lists and hash") | 				return nil, errors.New("miss-match of lists and hash") | ||||||
|  | @ -475,7 +428,7 @@ func (e evaluator) interpolateLongIdent(ctx context.Context, ec *evalCtx, n *ast | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		res, err = indexLookup(ctx, res, idx, lexer.Position{}) | 		res, err = indexLookup(ctx, res, idx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return "", err | 			return "", err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										50
									
								
								ucl/inst.go
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								ucl/inst.go
									
									
									
									
									
								
							|  | @ -53,9 +53,7 @@ type Module struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func New(opts ...InstOption) *Inst { | func New(opts ...InstOption) *Inst { | ||||||
| 	rootEC := &evalCtx{ | 	rootEC := &evalCtx{} | ||||||
| 		userCommandFrame: true, |  | ||||||
| 	} |  | ||||||
| 	rootEC.root = rootEC | 	rootEC.root = rootEC | ||||||
| 
 | 
 | ||||||
| 	rootEC.addCmd("echo", invokableFunc(echoBuiltin)) | 	rootEC.addCmd("echo", invokableFunc(echoBuiltin)) | ||||||
|  | @ -68,7 +66,6 @@ func New(opts ...InstOption) *Inst { | ||||||
| 	rootEC.addCmd("filter", invokableFunc(filterBuiltin)) | 	rootEC.addCmd("filter", invokableFunc(filterBuiltin)) | ||||||
| 	rootEC.addCmd("reduce", invokableFunc(reduceBuiltin)) | 	rootEC.addCmd("reduce", invokableFunc(reduceBuiltin)) | ||||||
| 	rootEC.addCmd("head", invokableFunc(firstBuiltin)) | 	rootEC.addCmd("head", invokableFunc(firstBuiltin)) | ||||||
| 	rootEC.addCmd("in", invokableFunc(inBuiltin)) |  | ||||||
| 
 | 
 | ||||||
| 	rootEC.addCmd("keys", invokableFunc(keysBuiltin)) | 	rootEC.addCmd("keys", invokableFunc(keysBuiltin)) | ||||||
| 
 | 
 | ||||||
|  | @ -82,9 +79,6 @@ func New(opts ...InstOption) *Inst { | ||||||
| 	rootEC.addCmd("str", invokableFunc(strBuiltin)) | 	rootEC.addCmd("str", invokableFunc(strBuiltin)) | ||||||
| 	rootEC.addCmd("int", invokableFunc(intBuiltin)) | 	rootEC.addCmd("int", invokableFunc(intBuiltin)) | ||||||
| 
 | 
 | ||||||
| 	rootEC.addCmd("nil?", invokableFunc(notNilBuiltin)) |  | ||||||
| 	rootEC.addCmd("!nil", invokableFunc(notNilBuiltin)) |  | ||||||
| 
 |  | ||||||
| 	rootEC.addCmd("add", invokableFunc(addBuiltin)) | 	rootEC.addCmd("add", invokableFunc(addBuiltin)) | ||||||
| 	rootEC.addCmd("sub", invokableFunc(subBuiltin)) | 	rootEC.addCmd("sub", invokableFunc(subBuiltin)) | ||||||
| 	rootEC.addCmd("mup", invokableFunc(mupBuiltin)) | 	rootEC.addCmd("mup", invokableFunc(mupBuiltin)) | ||||||
|  | @ -147,25 +141,8 @@ func (inst *Inst) Out() io.Writer { | ||||||
| 	return inst.out | 	return inst.out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type EvalOption func(*evalOptions) | func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) { | ||||||
| 
 | 	res, err := inst.eval(ctx, expr) | ||||||
| func WithSubEnv() EvalOption { |  | ||||||
| 	return func(opts *evalOptions) { |  | ||||||
| 		opts.forkEnv = true |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (inst *Inst) Eval(ctx context.Context, r io.Reader, options ...EvalOption) (any, error) { |  | ||||||
| 	opts := evalOptions{ |  | ||||||
| 		filename: "unnamed", |  | ||||||
| 		forkEnv:  false, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, opt := range options { |  | ||||||
| 		opt(&opts) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	res, err := inst.eval(ctx, r, opts) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errors.Is(err, ErrHalt) { | 		if errors.Is(err, ErrHalt) { | ||||||
| 			return nil, nil | 			return nil, nil | ||||||
|  | @ -181,29 +158,16 @@ func (inst *Inst) Eval(ctx context.Context, r io.Reader, options ...EvalOption) | ||||||
| 	return goRes, nil | 	return goRes, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (inst *Inst) EvalString(ctx context.Context, expr string) (any, error) { | func (inst *Inst) eval(ctx context.Context, expr string) (Object, error) { | ||||||
| 	return inst.Eval(ctx, strings.NewReader(expr)) | 	ast, err := parse(strings.NewReader(expr)) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type evalOptions struct { |  | ||||||
| 	filename string |  | ||||||
| 	forkEnv  bool |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (inst *Inst) eval(ctx context.Context, r io.Reader, opts evalOptions) (Object, error) { |  | ||||||
| 	ast, err := parse(opts.filename, r) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	eval := evaluator{inst: inst} | 	eval := evaluator{inst: inst} | ||||||
| 
 | 
 | ||||||
| 	env := inst.rootEC | 	// TODO: this should be a separate forkAndIsolate() session
 | ||||||
| 	if opts.forkEnv { | 	return eval.evalScript(ctx, inst.rootEC, ast) | ||||||
| 		env = env.forkAndIsolate() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return eval.evalScript(ctx, env, ast) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PseudoVarHandler interface { | type PseudoVarHandler interface { | ||||||
|  |  | ||||||
							
								
								
									
										209
									
								
								ucl/inst_test.go
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								ucl/inst_test.go
									
									
									
									
									
								
							|  | @ -3,13 +3,10 @@ package ucl_test | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"strings" |  | ||||||
| 
 |  | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| 
 | 
 | ||||||
| 	"testing" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestInst_Eval(t *testing.T) { | func TestInst_Eval(t *testing.T) { | ||||||
|  | @ -18,24 +15,23 @@ func TestInst_Eval(t *testing.T) { | ||||||
| 		expr    string | 		expr    string | ||||||
| 		want    any | 		want    any | ||||||
| 		wantObj bool | 		wantObj bool | ||||||
| 		wantAnErr bool |  | ||||||
| 		wantErr error | 		wantErr error | ||||||
| 	}{ | 	}{ | ||||||
| 		{desc: "simple string", expr: `firstarg "hello"`, want: "hello"}, | 		{desc: "simple string", expr: `firstarg "hello"`, want: "hello"}, | ||||||
| 		{desc: "simple int 1", expr: `firstarg 123`, want: 123}, | 		{desc: "simple int 1", expr: `firstarg 123`, want: 123}, | ||||||
| 		{desc: "simple int 2", expr: `firstarg -234`, want: -234}, | 		{desc: "simple int 2", expr: `firstarg -234`, want: -234}, | ||||||
| 		{desc: "simple ident 1", expr: `firstarg a-test`, want: "a-test"}, | 		{desc: "simple ident", expr: `firstarg a-test`, want: "a-test"}, | ||||||
| 
 | 
 | ||||||
| 		// String interpolation
 | 		// String interpolation
 | ||||||
| 		{desc: "interpolate string 1", expr: `what = "world" ; firstarg "hello $what"`, want: "hello world"}, | 		{desc: "interpolate string 1", expr: `$what = "world" ; firstarg "hello $what"`, want: "hello world"}, | ||||||
| 		{desc: "interpolate string 2", expr: `what = "world" ; when = "now" ; firstarg "$when, hello $what"`, want: "now, hello world"}, | 		{desc: "interpolate string 2", expr: `$what = "world" ; $when = "now" ; firstarg "$when, hello $what"`, want: "now, hello world"}, | ||||||
| 		{desc: "interpolate string 3", expr: `what = "world" ; when = "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"}, | 		{desc: "interpolate string 3", expr: `$what = "world" ; $when = "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"}, | ||||||
| 		{desc: "interpolate string 4", expr: `crazy = [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"}, | 		{desc: "interpolate string 4", expr: `$crazy = [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"}, | ||||||
| 		{desc: "interpolate string 5", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"}, | 		{desc: "interpolate string 5", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"}, | ||||||
| 		{desc: "interpolate string 6", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"}, | 		{desc: "interpolate string 6", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"}, | ||||||
| 		{desc: "interpolate string 7", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"}, | 		{desc: "interpolate string 7", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"}, | ||||||
| 		{desc: "interpolate string 8", expr: `words = ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"}, | 		{desc: "interpolate string 8", expr: `$words = ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"}, | ||||||
| 		{desc: "interpolate string 9", expr: `what = "world" ; firstarg "hello $($what)"`, want: "hello world"}, | 		{desc: "interpolate string 9", expr: `$what = "world" ; firstarg "hello $($what)"`, want: "hello world"}, | ||||||
| 		{desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"}, | 		{desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"}, | ||||||
| 		{desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"}, | 		{desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"}, | ||||||
| 		{desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"}, | 		{desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"}, | ||||||
|  | @ -62,71 +58,53 @@ func TestInst_Eval(t *testing.T) { | ||||||
| 		// Multi-statements
 | 		// Multi-statements
 | ||||||
| 		{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"}, | 		{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"}, | ||||||
| 		{desc: "multi 2", expr: `list "hello" | toUpper ; firstarg "world"`, want: "world"}, | 		{desc: "multi 2", expr: `list "hello" | toUpper ; firstarg "world"`, want: "world"}, | ||||||
| 		{desc: "multi 3", expr: `new = "this is new" ; firstarg $new`, want: "this is new"}, | 		{desc: "multi 3", expr: `$new = "this is new" ; firstarg $new`, want: "this is new"}, | ||||||
| 
 | 
 | ||||||
| 		// Lists
 | 		// Lists
 | ||||||
| 		{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}}, | 		{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}}, | ||||||
| 		{desc: "list 2", expr: `one = "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}}, | 		{desc: "list 2", expr: `$one = "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}}, | ||||||
| 		{desc: "list 3", expr: `firstarg []`, want: []any{}}, | 		{desc: "list 3", expr: `firstarg []`, want: []any{}}, | ||||||
| 		{desc: "list 4", expr: `x = ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}}, | 		{desc: "list 4", expr: `$x = ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}}, | ||||||
| 
 | 
 | ||||||
| 		// Maps
 | 		// Maps
 | ||||||
| 		{desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, | 		{desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, | ||||||
| 		{desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, | 		{desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}}, | ||||||
| 		{desc: "map 3", expr: ` | 		{desc: "map 3", expr: ` | ||||||
| 			one = "one" ; n1 = "1" | 			$one = "one" ; $n1 = "1" | ||||||
| 			firstarg [ | 			firstarg [ | ||||||
| 				$one:$n1 | 				$one:$n1 | ||||||
| 				(list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | head) | 				(list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | head) | ||||||
| 				three:"3" | 				three:"3" | ||||||
| 			]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}}, | 			]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}}, | ||||||
| 		{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}}, | 		{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}}, | ||||||
| 		{desc: "map 5", expr: `x = ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}}, | 		{desc: "map 5", expr: `$x = ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}}, | ||||||
| 		{desc: "map 6", expr: `x = [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}}, | 		{desc: "map 6", expr: `$x = [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}}, | ||||||
| 
 | 
 | ||||||
| 		// Dots
 | 		// Dots
 | ||||||
| 		{desc: "dot expr 1", expr: `x = [1 2 3] ; $x.(0)`, want: 1}, | 		{desc: "dot expr 1", expr: `$x = [1 2 3] ; $x.(0)`, want: 1}, | ||||||
| 		{desc: "dot expr 2", expr: `x = [1 2 3] ; $x.(1)`, want: 2}, | 		{desc: "dot expr 2", expr: `$x = [1 2 3] ; $x.(1)`, want: 2}, | ||||||
| 		{desc: "dot expr 3", expr: `x = [1 2 3] ; $x.(2)`, want: 3}, | 		{desc: "dot expr 3", expr: `$x = [1 2 3] ; $x.(2)`, want: 3}, | ||||||
| 		{desc: "dot expr 4", expr: `x = [1 2 3] ; $x.(3)`, want: nil}, | 		{desc: "dot expr 4", expr: `$x = [1 2 3] ; $x.(3)`, want: nil}, | ||||||
| 		{desc: "dot expr 5", expr: `x = [1 2 3] ; $x.(add 1 1)`, want: 3}, | 		{desc: "dot expr 5", expr: `$x = [1 2 3] ; $x.(add 1 1)`, want: 3}, | ||||||
| 		{desc: "dot expr 6", expr: `x = [1 2 3] ; $x.(-1)`, want: 3}, | 		{desc: "dot expr 6", expr: `$x = [1 2 3] ; $x.(-1)`, want: 3}, | ||||||
| 		{desc: "dot expr 7", expr: `x = [1 2 3] ; $x.(-2)`, want: 2}, | 		{desc: "dot expr 7", expr: `$x = [1 2 3] ; $x.(-2)`, want: 2}, | ||||||
| 		{desc: "dot expr 8", expr: `x = [1 2 3] ; $x.(-3)`, want: 1}, | 		{desc: "dot expr 8", expr: `$x = [1 2 3] ; $x.(-3)`, want: 1}, | ||||||
| 		{desc: "dot expr 9", expr: `x = [1 2 3] ; $x.(-4)`, want: nil}, | 		{desc: "dot expr 9", expr: `$x = [1 2 3] ; $x.(-4)`, want: nil}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "dot idents 1", expr: `x = [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"}, | 		{desc: "dot idents 1", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"}, | ||||||
| 		{desc: "dot idents 2", expr: `x = [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"}, | 		{desc: "dot idents 2", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"}, | ||||||
| 		{desc: "dot idents 3", expr: `x = [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil}, | 		{desc: "dot idents 3", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil}, | ||||||
| 		{desc: "dot idents 4", expr: `x = [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"}, | 		{desc: "dot idents 4", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"}, | ||||||
| 		{desc: "dot idents 5", expr: `x = [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"}, | 		{desc: "dot idents 5", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"}, | ||||||
| 		{desc: "dot idents 6", expr: `x = [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil}, | 		{desc: "dot idents 6", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil}, | ||||||
| 		{desc: "dot idents 7", expr: `x = [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"}, | 		{desc: "dot idents 7", expr: `$x = [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"}, | ||||||
| 		{desc: "dot idents 8", expr: `x = [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"}, | 		{desc: "dot idents 8", expr: `$x = [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"}, | ||||||
| 		{desc: "dot idents 9", expr: `x = [MORE:"stuff"] ; $x.y`, want: nil}, | 		{desc: "dot idents 9", expr: `$x = [MORE:"stuff"] ; x.y`, want: nil}, | ||||||
| 
 |  | ||||||
| 		{desc: "dot err 1", expr: `x = [1 2 3] ; $x.Hello`, want: nil}, |  | ||||||
| 		{desc: "dot err 2", expr: `x = [1 2 3] ; $x.("world")`, want: nil}, |  | ||||||
| 		{desc: "dot err 4", expr: `x = [a:1 b:2] ; $x.(5)`, want: nil}, |  | ||||||
| 		{desc: "dot err 3", expr: `x = [a:1 b:2] ; $x.(0)`, want: nil}, |  | ||||||
| 		{desc: "dot err 5", expr: `x = 123 ; $x.(5)`, wantAnErr: true}, |  | ||||||
| 		{desc: "dot err 6", expr: `x = 123 ; $x.Five`, wantAnErr: true}, |  | ||||||
| 
 | 
 | ||||||
| 		{desc: "parse comments 1", expr: parseComments1, wantObj: true, wantErr: nil}, | 		{desc: "parse comments 1", expr: parseComments1, wantObj: true, wantErr: nil}, | ||||||
| 		{desc: "parse comments 2", expr: parseComments2, wantObj: true, wantErr: nil}, | 		{desc: "parse comments 2", expr: parseComments2, wantObj: true, wantErr: nil}, | ||||||
| 		{desc: "parse comments 3", expr: parseComments3, wantObj: true, wantErr: nil}, | 		{desc: "parse comments 3", expr: parseComments3, wantObj: true, wantErr: nil}, | ||||||
| 		{desc: "parse comments 4", expr: parseComments4, wantObj: true, wantErr: nil}, | 		{desc: "parse comments 4", expr: parseComments4, wantObj: true, wantErr: nil}, | ||||||
| 
 |  | ||||||
| 		// Assign dots
 |  | ||||||
| 		{desc: "assign dot 1", expr: `x = [1 2 3] ; x.(0) = 4 ; "$x"`, want: "[4 2 3]"}, |  | ||||||
| 		{desc: "assign dot 2", expr: `x = [1 2 3] ; x.(1) = 5 ; "$x"`, want: "[1 5 3]"}, |  | ||||||
| 		{desc: "assign dot 3", expr: `x = [1 2 3] ; x.(-1) = 6 ; "$x"`, want: "[1 2 6]"}, |  | ||||||
| 		{desc: "assign dot 4", expr: `y = [a:1 b:2] ; y.a = "hello" ; "$y"`, want: `[a:hello b:2]`}, |  | ||||||
| 		{desc: "assign dot 5", expr: `y = [a:1 b:2] ; y.b = "world" ; "$y"`, want: `[a:1 b:world]`}, |  | ||||||
| 		{desc: "assign dot 6", expr: `y = [a:"b" b:2] ; y.($y.a) = "world" ; "$y"`, want: `[a:b b:world]`}, |  | ||||||
| 		{desc: "assign dot 7", expr: `z = [a:[1 2] b:[3 3]] ; z.a.(1) = 3 ; "$z"`, want: `[a:[1 3] b:[3 3]]`}, |  | ||||||
| 		{desc: "assign dot 8", expr: `z = [[1 2] [3 4]] ; z.(1).(0) = 5 ; "$z"`, want: `[[1 2] [5 4]]`}, |  | ||||||
| 		{desc: "assign dot 7", expr: `z = [[a:1 b:2] [c:3 d:4]] ; z.(1).a = 5 ; "$z"`, want: `[[a:1 b:2] [a:5 c:3 d:4]]`}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -135,12 +113,10 @@ func TestInst_Eval(t *testing.T) { | ||||||
| 			outW := bytes.NewBuffer(nil) | 			outW := bytes.NewBuffer(nil) | ||||||
| 
 | 
 | ||||||
| 			inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin()) | 			inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin()) | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 
 | 
 | ||||||
| 			if tt.wantErr != nil { | 			if tt.wantErr != nil { | ||||||
| 				assert.ErrorIs(t, err, tt.wantErr) | 				assert.ErrorIs(t, err, tt.wantErr) | ||||||
| 			} else if tt.wantAnErr { |  | ||||||
| 				assert.Error(t, err) |  | ||||||
| 			} else if tt.wantObj { | 			} else if tt.wantObj { | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				_, isObj := res.(ucl.Object) | 				_, isObj := res.(ucl.Object) | ||||||
|  | @ -153,117 +129,6 @@ func TestInst_Eval(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestInst_Eval_WithSubEnv(t *testing.T) { |  | ||||||
| 	t.Run("global symbols should not leak across environments", func(t *testing.T) { |  | ||||||
| 		ctx := t.Context() |  | ||||||
| 
 |  | ||||||
| 		inst := ucl.New() |  | ||||||
| 
 |  | ||||||
| 		res, err := inst.Eval(ctx, strings.NewReader(`a = "hello" ; $a`), ucl.WithSubEnv()) |  | ||||||
| 		assert.NoError(t, err) |  | ||||||
| 		assert.Equal(t, "hello", res) |  | ||||||
| 
 |  | ||||||
| 		res, err = inst.Eval(ctx, strings.NewReader(`$a`), ucl.WithSubEnv()) |  | ||||||
| 		assert.NoError(t, err) |  | ||||||
| 		assert.Nil(t, res) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("environments should not leak when using hooks", func(t *testing.T) { |  | ||||||
| 		tests := []struct { |  | ||||||
| 			descr string |  | ||||||
| 			eval1 string |  | ||||||
| 			eval2 string |  | ||||||
| 			want1 any |  | ||||||
| 			want2 any |  | ||||||
| 		}{ |  | ||||||
| 			{ |  | ||||||
| 				descr: "reading vars", |  | ||||||
| 				eval1: `a = "hello" ; hook { $a }`, |  | ||||||
| 				eval2: `a = "world" ; hook { $a }`, |  | ||||||
| 				want1: "hello", |  | ||||||
| 				want2: "world", |  | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				descr: "modifying vars", |  | ||||||
| 				eval1: `a = "hello" ; hook { a = "new value" ; $a }`, |  | ||||||
| 				eval2: `a = "world" ; hook { $a }`, |  | ||||||
| 				want1: "new value", |  | ||||||
| 				want2: "world", |  | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				descr: "defining procs", |  | ||||||
| 				eval1: `proc say_hello { "hello" } ; hook { say_hello }`, |  | ||||||
| 				eval2: `proc say_hello { "world" } ; hook { say_hello }`, |  | ||||||
| 				want1: "hello", |  | ||||||
| 				want2: "world", |  | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				descr: "exporting procs 1", |  | ||||||
| 				eval1: `export say_hello { "hello" } ; hook { say_hello }`, |  | ||||||
| 				eval2: `hook { say_hello }`, |  | ||||||
| 				want1: "hello", |  | ||||||
| 				want2: "hello", |  | ||||||
| 			}, |  | ||||||
| 			{ |  | ||||||
| 				descr: "exporting procs 2", |  | ||||||
| 				eval1: `a = "hello" ; export say_hello { a = "world"; $a } ; hook { say_hello }`, |  | ||||||
| 				eval2: `a = "other" ; hook { say_hello }`, |  | ||||||
| 				want1: "world", |  | ||||||
| 				want2: "world", |  | ||||||
| 			}, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, tt := range tests { |  | ||||||
| 			t.Run(tt.descr, func(t *testing.T) { |  | ||||||
| 				ctx := t.Context() |  | ||||||
| 
 |  | ||||||
| 				hooks := make([]ucl.Invokable, 0) |  | ||||||
| 
 |  | ||||||
| 				inst := ucl.New() |  | ||||||
| 				inst.SetBuiltin("hook", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var hookProc ucl.Invokable |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&hookProc); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 					hooks = append(hooks, hookProc) |  | ||||||
| 
 |  | ||||||
| 					return nil, nil |  | ||||||
| 				}) |  | ||||||
| 				inst.SetBuiltin("export", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var ( |  | ||||||
| 						name     string |  | ||||||
| 						hookProc ucl.Invokable |  | ||||||
| 					) |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&name, &hookProc); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 					inst.SetBuiltinInvokable(name, hookProc) |  | ||||||
| 
 |  | ||||||
| 					return nil, nil |  | ||||||
| 				}) |  | ||||||
| 
 |  | ||||||
| 				res, err := inst.Eval(ctx, strings.NewReader(tt.eval1), ucl.WithSubEnv()) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Nil(t, res) |  | ||||||
| 
 |  | ||||||
| 				res, err = inst.Eval(ctx, strings.NewReader(tt.eval2), ucl.WithSubEnv()) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Nil(t, res) |  | ||||||
| 
 |  | ||||||
| 				h1, err := hooks[0].Invoke(ctx, ucl.CallArgs{}) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want1, h1) |  | ||||||
| 
 |  | ||||||
| 				h2, err := hooks[1].Invoke(ctx, ucl.CallArgs{}) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want2, h2) |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestInst_SetPseudoVar(t *testing.T) { | func TestInst_SetPseudoVar(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		desc       string | 		desc       string | ||||||
|  | @ -290,7 +155,7 @@ func TestInst_SetPseudoVar(t *testing.T) { | ||||||
| 			inst.SetPseudoVar("bar", bar) | 			inst.SetPseudoVar("bar", bar) | ||||||
| 			inst.SetMissingPseudoVarHandler(missingPseudoVarType{}) | 			inst.SetMissingPseudoVarHandler(missingPseudoVarType{}) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(t.Context(), tt.expr) | 			res, err := inst.Eval(t.Context(), tt.expr) | ||||||
| 
 | 
 | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
|  |  | ||||||
							
								
								
									
										83
									
								
								ucl/objs.go
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								ucl/objs.go
									
									
									
									
									
								
							|  | @ -5,12 +5,11 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"sort" |  | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"lmika.dev/pkg/modash/moslice" | 	"github.com/lmika/gopkgs/fp/slices" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Object interface { | type Object interface { | ||||||
|  | @ -36,13 +35,9 @@ type ModListable interface { | ||||||
| 
 | 
 | ||||||
| 	// Insert adds a new item to the list. idx can be a positive
 | 	// Insert adds a new item to the list. idx can be a positive
 | ||||||
| 	// number from 0 to len(), in which case the object will be inserted
 | 	// number from 0 to len(), in which case the object will be inserted
 | ||||||
| 	// at that position, shifting all other elements to the right.
 | 	// at that position.  If idx is negative, then the item will be inserted
 | ||||||
| 	// If idx is negative, then the item will be inserted
 |  | ||||||
| 	// at that position from the right.
 | 	// at that position from the right.
 | ||||||
| 	Insert(idx int, obj Object) error | 	Insert(idx int, obj Object) error | ||||||
| 
 |  | ||||||
| 	// SetIndex replaces the item at index position idx with obj.
 |  | ||||||
| 	SetIndex(idx int, obj Object) error |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Hashable interface { | type Hashable interface { | ||||||
|  | @ -51,22 +46,12 @@ type Hashable interface { | ||||||
| 	Each(func(k string, v Object) error) error | 	Each(func(k string, v Object) error) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ModHashable interface { |  | ||||||
| 	Hashable |  | ||||||
| 	SetValue(k string, val Object) error |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ListObject []Object | type ListObject []Object | ||||||
| 
 | 
 | ||||||
| func NewListObject() *ListObject { | func NewListObject() *ListObject { | ||||||
| 	return &ListObject{} | 	return &ListObject{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewListObjectOfLength(l int) *ListObject { |  | ||||||
| 	o := make(ListObject, l) |  | ||||||
| 	return &o |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (lo *ListObject) Append(o Object) { | func (lo *ListObject) Append(o Object) { | ||||||
| 	*lo = append(*lo, o) | 	*lo = append(*lo, o) | ||||||
| } | } | ||||||
|  | @ -95,11 +80,6 @@ func (s *ListObject) Index(i int) Object { | ||||||
| 	return (*s)[i] | 	return (*s)[i] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ListObject) SetIndex(i int, toVal Object) error { |  | ||||||
| 	(*s)[i] = toVal |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type StringListObject []string | type StringListObject []string | ||||||
| 
 | 
 | ||||||
| func (ss StringListObject) String() string { | func (ss StringListObject) String() string { | ||||||
|  | @ -130,24 +110,16 @@ func (i iteratorObject) Truthy() bool { | ||||||
| 	return i.Iterable.HasNext() | 	return i.Iterable.HasNext() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type HashObject map[string]Object | type hashObject map[string]Object | ||||||
| 
 | 
 | ||||||
| func (s HashObject) String() string { | func (s hashObject) String() string { | ||||||
| 	if len(s) == 0 { | 	if len(s) == 0 { | ||||||
| 		return "[:]" | 		return "[:]" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Return the keys in sorted order
 |  | ||||||
| 	keys := make([]string, 0, len(s)) |  | ||||||
| 	for k := range s { |  | ||||||
| 		keys = append(keys, k) |  | ||||||
| 	} |  | ||||||
| 	sort.Strings(keys) |  | ||||||
| 
 |  | ||||||
| 	sb := strings.Builder{} | 	sb := strings.Builder{} | ||||||
| 	sb.WriteString("[") | 	sb.WriteString("[") | ||||||
| 	for _, k := range keys { | 	for k, v := range s { | ||||||
| 		v := s[k] |  | ||||||
| 		if sb.Len() != 1 { | 		if sb.Len() != 1 { | ||||||
| 			sb.WriteString(" ") | 			sb.WriteString(" ") | ||||||
| 		} | 		} | ||||||
|  | @ -159,19 +131,19 @@ func (s HashObject) String() string { | ||||||
| 	return sb.String() | 	return sb.String() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s HashObject) Truthy() bool { | func (s hashObject) Truthy() bool { | ||||||
| 	return len(s) > 0 | 	return len(s) > 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s HashObject) Len() int { | func (s hashObject) Len() int { | ||||||
| 	return len(s) | 	return len(s) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s HashObject) Value(k string) Object { | func (s hashObject) Value(k string) Object { | ||||||
| 	return s[k] | 	return s[k] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s HashObject) Each(fn func(k string, v Object) error) error { | func (s hashObject) Each(fn func(k string, v Object) error) error { | ||||||
| 	for k, v := range s { | 	for k, v := range s { | ||||||
| 		if err := fn(k, v); err != nil { | 		if err := fn(k, v); err != nil { | ||||||
| 			return err | 			return err | ||||||
|  | @ -180,11 +152,6 @@ func (s HashObject) Each(fn func(k string, v Object) error) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s HashObject) SetValue(k string, val Object) error { |  | ||||||
| 	s[k] = val |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type StringObject string | type StringObject string | ||||||
| 
 | 
 | ||||||
| func (s StringObject) String() string { | func (s StringObject) String() string { | ||||||
|  | @ -252,7 +219,7 @@ func toGoValue(obj Object) (interface{}, bool) { | ||||||
| 			xs = append(xs, x) | 			xs = append(xs, x) | ||||||
| 		} | 		} | ||||||
| 		return xs, true | 		return xs, true | ||||||
| 	case HashObject: | 	case hashObject: | ||||||
| 		xs := make(map[string]interface{}) | 		xs := make(map[string]interface{}) | ||||||
| 		for k, va := range v { | 		for k, va := range v { | ||||||
| 			x, ok := toGoValue(va) | 			x, ok := toGoValue(va) | ||||||
|  | @ -268,8 +235,6 @@ func toGoValue(obj Object) (interface{}, bool) { | ||||||
| 		return v, true | 		return v, true | ||||||
| 	case proxyObject: | 	case proxyObject: | ||||||
| 		return v.p, true | 		return v.p, true | ||||||
| 	case OpaqueObject: |  | ||||||
| 		return v.v, true |  | ||||||
| 	case listableProxyObject: | 	case listableProxyObject: | ||||||
| 		return v.orig.Interface(), true | 		return v.orig.Interface(), true | ||||||
| 	case structProxyObject: | 	case structProxyObject: | ||||||
|  | @ -528,25 +493,6 @@ func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (Object, | ||||||
| 	return i(ctx, args) | 	return i(ctx, args) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type GoFunction func(ctx context.Context, args CallArgs) (any, error) |  | ||||||
| 
 |  | ||||||
| func (gf GoFunction) String() string { |  | ||||||
| 	return "(proc)" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (gf GoFunction) Truthy() bool { |  | ||||||
| 	return gf != nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (gf GoFunction) invoke(ctx context.Context, args invocationArgs) (Object, error) { |  | ||||||
| 	v, err := gf(ctx, CallArgs{args: args}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return fromGoValue(v) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type blockObject struct { | type blockObject struct { | ||||||
| 	block    *astBlock | 	block    *astBlock | ||||||
| 	closedEC *evalCtx | 	closedEC *evalCtx | ||||||
|  | @ -631,7 +577,7 @@ func newStructProxyObject(v reflect.Value, orig reflect.Value) structProxyObject | ||||||
| 	return structProxyObject{ | 	return structProxyObject{ | ||||||
| 		v:    v, | 		v:    v, | ||||||
| 		orig: orig, | 		orig: orig, | ||||||
| 		vf:   moslice.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }), | 		vf:   slices.Filter(reflect.VisibleFields(v.Type()), func(t reflect.StructField) bool { return t.IsExported() }), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -734,10 +680,3 @@ func isBreakErr(err error) bool { | ||||||
| 
 | 
 | ||||||
| 	return errors.As(err, &errBreak{}) || errors.As(err, &errReturn{}) || errors.Is(err, ErrHalt) | 	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() |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -5,10 +5,9 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type testIterator struct { | type testIterator struct { | ||||||
|  | @ -58,19 +57,6 @@ func WithTestBuiltin() InstOption { | ||||||
| 			return &a, nil | 			return &a, nil | ||||||
| 		})) | 		})) | ||||||
| 
 | 
 | ||||||
| 		i.rootEC.addCmd("rearrange", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { |  | ||||||
| 			var as ListObject = make([]Object, 0) |  | ||||||
| 
 |  | ||||||
| 			for _, a := range args.args { |  | ||||||
| 				vs, ok := args.kwargs[a.String()] |  | ||||||
| 				if ok { |  | ||||||
| 					as = append(as, vs.Index(0)) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return &as, nil |  | ||||||
| 		})) |  | ||||||
| 
 |  | ||||||
| 		i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { | 		i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) { | ||||||
| 			if len(args.args) == 0 { | 			if len(args.args) == 0 { | ||||||
| 				return nil, errors.New("an error occurred") | 				return nil, errors.New("an error occurred") | ||||||
|  | @ -145,10 +131,10 @@ func TestBuiltins_Echo(t *testing.T) { | ||||||
| 			echo "world"	# command after this | 			echo "world"	# command after this | ||||||
| ; | ; | ||||||
| 		`, want: "Hello\nworld\n"}, | 		`, want: "Hello\nworld\n"}, | ||||||
| 		{desc: "interpolated string 1", expr: `what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"}, | 		{desc: "interpolated string 1", expr: `$what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"}, | ||||||
| 		{desc: "interpolated string 2", expr: `what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"}, | 		{desc: "interpolated string 2", expr: `$what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"}, | ||||||
| 		{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"}, | 		{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"}, | ||||||
| 		{desc: "interpolated string 4", expr: `what = "Hello" ; where = "world" ; echo "$what, $where"`, want: "Hello, world\n"}, | 		{desc: "interpolated string 4", expr: `$what = "Hello" ; $where = "world" ; echo "$what, $where"`, want: "Hello, world\n"}, | ||||||
| 		{desc: "interpolated string 5", expr: ` | 		{desc: "interpolated string 5", expr: ` | ||||||
| 			for [123 "foo" true ()] { |x| | 			for [123 "foo" true ()] { |x| | ||||||
|                 echo "[[$x]]" |                 echo "[[$x]]" | ||||||
|  | @ -165,7 +151,7 @@ func TestBuiltins_Echo(t *testing.T) { | ||||||
| 			outW := bytes.NewBuffer(nil) | 			outW := bytes.NewBuffer(nil) | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 
 | 
 | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Nil(t, res) | 			assert.Nil(t, res) | ||||||
|  | @ -181,19 +167,19 @@ func TestBuiltins_If(t *testing.T) { | ||||||
| 		want string | 		want string | ||||||
| 	}{ | 	}{ | ||||||
| 		{desc: "single then", expr: ` | 		{desc: "single then", expr: ` | ||||||
| 			x = "Hello" | 			$x = "Hello" | ||||||
| 			if $x { | 			if $x { | ||||||
| 				echo "true" | 				echo "true" | ||||||
| 			}`, want: "true\n(nil)\n"}, | 			}`, want: "true\n(nil)\n"}, | ||||||
| 		{desc: "single then and else", expr: ` | 		{desc: "single then and else", expr: ` | ||||||
| 			x = "Hello" | 			$x = "Hello" | ||||||
| 			if $x { | 			if $x { | ||||||
| 				echo "true" | 				echo "true" | ||||||
| 			} else { | 			} else { | ||||||
| 				echo "false" | 				echo "false" | ||||||
| 			}`, want: "true\n(nil)\n"}, | 			}`, want: "true\n(nil)\n"}, | ||||||
| 		{desc: "single then, elif and else", expr: ` | 		{desc: "single then, elif and else", expr: ` | ||||||
| 			x = "Hello" | 			$x = "Hello" | ||||||
| 			if $y { | 			if $y { | ||||||
| 				echo "y is true" | 				echo "y is true" | ||||||
| 			} elif $x { | 			} elif $x { | ||||||
|  | @ -202,14 +188,14 @@ func TestBuiltins_If(t *testing.T) { | ||||||
| 				echo "nothings x" | 				echo "nothings x" | ||||||
| 			}`, want: "x is true\n(nil)\n"}, | 			}`, want: "x is true\n(nil)\n"}, | ||||||
| 		{desc: "single then and elif, no else", expr: ` | 		{desc: "single then and elif, no else", expr: ` | ||||||
| 			x = "Hello" | 			$x = "Hello" | ||||||
| 			if $y { | 			if $y { | ||||||
| 				echo "y is true" | 				echo "y is true" | ||||||
| 			} elif $x { | 			} elif $x { | ||||||
| 				echo "x is true" | 				echo "x is true" | ||||||
| 			}`, want: "x is true\n(nil)\n"}, | 			}`, want: "x is true\n(nil)\n"}, | ||||||
| 		{desc: "single then, two elif, and else", expr: ` | 		{desc: "single then, two elif, and else", expr: ` | ||||||
| 			x = "Hello" | 			$x = "Hello" | ||||||
| 			if $z { | 			if $z { | ||||||
| 				echo "z is true" | 				echo "z is true" | ||||||
| 			} elif $y { | 			} elif $y { | ||||||
|  | @ -227,15 +213,15 @@ func TestBuiltins_If(t *testing.T) { | ||||||
| 			} else { | 			} else { | ||||||
| 				echo "none is true" | 				echo "none is true" | ||||||
| 			}`, want: "none is true\n(nil)\n"}, | 			}`, want: "none is true\n(nil)\n"}, | ||||||
| 		{desc: "compressed then", expr: `x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"}, | 		{desc: "compressed then", expr: `$x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"}, | ||||||
| 		{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"}, | 		{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"}, | ||||||
| 		{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"}, | 		{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 1", expr: `i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | 		{desc: "if of itr 1", expr: `$i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 2", expr: `i = itr ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | 		{desc: "if of itr 2", expr: `$i = itr ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 3", expr: `i = itr ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, | 		{desc: "if of itr 3", expr: `$i = itr ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 4", expr: `i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | 		{desc: "if of itr 4", expr: `$i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 5", expr: `i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, | 		{desc: "if of itr 5", expr: `$i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"}, | ||||||
| 		{desc: "if of itr 6", expr: `i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | 		{desc: "if of itr 6", expr: `$i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -295,53 +281,53 @@ func TestBuiltins_While(t *testing.T) { | ||||||
| 		want string | 		want string | ||||||
| 	}{ | 	}{ | ||||||
| 		{desc: "iterate while true 1", expr: ` | 		{desc: "iterate while true 1", expr: ` | ||||||
| 			x = 0	 | 			$x = 0	 | ||||||
| 			while (lt $x 5) { | 			while (lt $x 5) { | ||||||
| 				echo $x | 				echo $x | ||||||
| 				x = add $x 1 | 				$x = (add $x 1) | ||||||
| 			} | 			} | ||||||
| 			echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, | 			echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, | ||||||
| 		{desc: "iterate while true 2", expr: ` | 		{desc: "iterate while true 2", expr: ` | ||||||
| 			x = 20	 | 			$x = 20	 | ||||||
| 			while (lt $x 5) { | 			while (lt $x 5) { | ||||||
| 				echo $x | 				echo $x | ||||||
| 				x = (add $x 1) | 				$x = (add $x 1) | ||||||
| 			} | 			} | ||||||
| 			echo "done"`, want: "done\n(nil)\n"}, | 			echo "done"`, want: "done\n(nil)\n"}, | ||||||
| 		{desc: "iterate while true with pipeline", expr: ` | 		{desc: "iterate while true with pipeline", expr: ` | ||||||
| 			x = 0 | 			$x = 0 | ||||||
| 			while (lt $x 5) { | 			while (lt $x 5) { | ||||||
| 				echo $x | 				echo $x | ||||||
| 				x = (add $x 1) | 				$x = (add $x 1) | ||||||
| 				if (ge $x 3) { | 				if (ge $x 3) { | ||||||
| 					break "Ahh" | 					break "Ahh" | ||||||
| 				} | 				} | ||||||
| 			} | echo " was the break" | 			} | echo " was the break" | ||||||
| 			echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"}, | 			echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"}, | ||||||
| 		{desc: "iterate for ever with break 1", expr: ` | 		{desc: "iterate for ever with break 1", expr: ` | ||||||
| 			x = 0 | 			$x = 0 | ||||||
| 			while { | 			while { | ||||||
| 				echo $x | 				echo $x | ||||||
| 				x = add $x 1 | 				$x = (add $x 1) | ||||||
| 				if (ge $x 5) { | 				if (ge $x 5) { | ||||||
| 					break | 					break | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, | 			echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"}, | ||||||
| 		{desc: "iterate for ever with break 2", expr: ` | 		{desc: "iterate for ever with break 2", expr: ` | ||||||
| 			x = 0 | 			$x = 0 | ||||||
| 			echo (while { | 			echo (while { | ||||||
| 				echo $x | 				echo $x | ||||||
| 				x = add $x 1 | 				$x = add $x 1 | ||||||
| 				if (ge $x 5) { | 				if (ge $x 5) { | ||||||
| 					break $x | 					break $x | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 			`, want: "0\n1\n2\n3\n4\n5\n(nil)\n"}, | 			`, want: "0\n1\n2\n3\n4\n5\n(nil)\n"}, | ||||||
| 		{desc: "iterate for ever with continue", expr: ` | 		{desc: "iterate for ever with continue", expr: ` | ||||||
| 			x = 0 | 			$x = 0 | ||||||
| 			while { | 			while { | ||||||
| 				x = (add $x 1) | 				$x = (add $x 1) | ||||||
| 				if (or (eq $x 2) (eq $x 4)) { | 				if (or (eq $x 2) (eq $x 4)) { | ||||||
| 					echo "quack" | 					echo "quack" | ||||||
| 					continue | 					continue | ||||||
|  | @ -501,10 +487,10 @@ func TestBuiltins_Procs(t *testing.T) { | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			helloGreater = makeGreeter "Hello" | 			$helloGreater = makeGreeter "Hello" | ||||||
| 			$helloGreater "world" | 			$helloGreater "world" | ||||||
| 
 | 
 | ||||||
| 			goodbye = makeGreeter "Goodbye cruel" | 			$goodbye = makeGreeter "Goodbye cruel" | ||||||
| 			$goodbye "world" | 			$goodbye "world" | ||||||
| 
 | 
 | ||||||
| 			call (makeGreeter "Quick") ["call me"] | 			call (makeGreeter "Quick") ["call me"] | ||||||
|  | @ -512,19 +498,16 @@ func TestBuiltins_Procs(t *testing.T) { | ||||||
| 			`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"}, | 			`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"}, | ||||||
| 		{desc: "modifying closed over variables", expr: ` | 		{desc: "modifying closed over variables", expr: ` | ||||||
| 			proc makeSetter { | 			proc makeSetter { | ||||||
| 				bla = "X" | 				$bla = "X" | ||||||
| 				proc appendToBla { |x| | 				proc appendToBla { |x| | ||||||
| 					bla = cat $bla $x | 					$bla = cat $bla $x | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		 | 		 | ||||||
| 			er = makeSetter | 			$er = makeSetter | ||||||
| 			echo (call $er ["xxx"]) | 			echo (call $er ["xxx"]) | ||||||
| 			echo (call $er ["yyy"]) | 			echo (call $er ["yyy"]) | ||||||
| 			`, want: "Xxxx\nXxxxyyy\n(nil)\n"}, | 			`, want: "Xxxx\nXxxxyyy\n(nil)\n"}, | ||||||
| 		{desc: "calling with kwargs", expr: ` |  | ||||||
| 			echo (call rearrange [b a] [a:"ey" b:"bee"]) |  | ||||||
| 			`, want: "[bee ey]\n(nil)\n"}, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -623,7 +606,7 @@ func TestBuiltins_Return(t *testing.T) { | ||||||
| 				echo "world" | 				echo "world" | ||||||
| 			} | 			} | ||||||
| 			proc greet { | 			proc greet { | ||||||
| 				what = (greetWhat) | 				$what = (greetWhat) | ||||||
| 				echo "Hello, " $what | 				echo "Hello, " $what | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -687,7 +670,7 @@ func TestBuiltins_Return(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			proc test-thing { | 			proc test-thing { | ||||||
| 				for [1 2 3] { |x| | 				for [1 2 3] { |x| | ||||||
| 					myClosure = proc { echo $x } | 					$myClosure = proc { echo $x } | ||||||
| 					do-thing $myClosure | 					do-thing $myClosure | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -714,12 +697,12 @@ func TestBuiltins_Return(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			proc test-thing {	 | 			proc test-thing {	 | ||||||
| 				[1 2 3] | map { |x| | 				[1 2 3] | map { |x| | ||||||
| 					myProc = proc { echo $x } | 					$myProc = proc { echo $x } | ||||||
| 					proc { do-thing $myProc } | 					proc { do-thing $myProc } | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			hello = "xx" | 			$hello = "xx" | ||||||
| 			for (test-thing) { |y| call $y ; echo $hello } | 			for (test-thing) { |y| call $y ; echo $hello } | ||||||
| 		`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, | 		`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, | ||||||
| 		{desc: "check closure 7", expr: ` | 		{desc: "check closure 7", expr: ` | ||||||
|  | @ -728,15 +711,15 @@ func TestBuiltins_Return(t *testing.T) { | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			proc test-thing { | 			proc test-thing { | ||||||
| 				f = 0 | 				$f = 0 | ||||||
| 				[1 2 3] | map { |x| | 				[1 2 3] | map { |x| | ||||||
| 					myProc = proc { echo $f } | 					$myProc = proc { echo $f } | ||||||
| 					f = (add $f 1) | 					$f = (add $f 1) | ||||||
| 					proc { do-thing $myProc }	 | 					proc { do-thing $myProc }	 | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			hello = "xx" | 			$hello = "xx" | ||||||
| 			for (test-thing) { |y| call $y ; echo $hello } | 			for (test-thing) { |y| call $y ; echo $hello } | ||||||
| 		`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"}, | 		`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"}, | ||||||
| 		{desc: "check closure 7", expr: ` | 		{desc: "check closure 7", expr: ` | ||||||
|  | @ -745,16 +728,16 @@ func TestBuiltins_Return(t *testing.T) { | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			proc test-thing { | 			proc test-thing { | ||||||
| 				f = 1 | 				$f = 1 | ||||||
| 				[1 2 3] | map { |x| | 				[1 2 3] | map { |x| | ||||||
| 					g = $f | 					$g = $f | ||||||
| 					myProc = (proc { echo $g })	 | 					$myProc = (proc { echo $g })	 | ||||||
| 					f = (add $f 1) | 					$f = (add $f 1) | ||||||
| 					proc { do-thing $myProc }	 | 					proc { do-thing $myProc }	 | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			hello = "xx" | 			$hello = "xx" | ||||||
| 			for (test-thing) { |y| call $y ; echo $hello } | 			for (test-thing) { |y| call $y ; echo $hello } | ||||||
| 		`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, | 		`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"}, | ||||||
| 	} | 	} | ||||||
|  | @ -917,11 +900,11 @@ func TestBuiltins_Try(t *testing.T) { | ||||||
| 			} | 			} | ||||||
| 		`, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "boom"}, | 		`, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "boom"}, | ||||||
| 		{desc: "try 12", expr: ` | 		{desc: "try 12", expr: ` | ||||||
| 			a = try { "e" } catch { "f" } | 			$a = try { "e" } catch { "f" } | ||||||
| 			echo $a | 			echo $a | ||||||
| 		`, want: "e\n(nil)\n"}, | 		`, want: "e\n(nil)\n"}, | ||||||
| 		{desc: "try 13", expr: ` | 		{desc: "try 13", expr: ` | ||||||
| 			a = try { error "bang" } catch { "f" } | 			$a = try { error "bang" } catch { "f" } | ||||||
| 			echo $a | 			echo $a | ||||||
| 		`, want: "f\n(nil)\n"}, | 		`, want: "f\n(nil)\n"}, | ||||||
| 		{desc: "try 14", expr: ` | 		{desc: "try 14", expr: ` | ||||||
|  | @ -1069,77 +1052,6 @@ func TestBuiltins_Seq(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestBuiltins_Call(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc string |  | ||||||
| 		expr string |  | ||||||
| 		want string |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "call simple", expr: ` |  | ||||||
| 			call add [1 2] |  | ||||||
| 			`, want: "3\n"}, |  | ||||||
| 		{desc: "call with proc", expr: ` |  | ||||||
| 			call (proc { |x y| add $x $y }) [3 4] |  | ||||||
| 			`, want: "7\n"}, |  | ||||||
| 		{desc: "meta call", expr: ` |  | ||||||
| 			call "call" ["add" [1 3]] |  | ||||||
| 			`, want: "4\n"}, |  | ||||||
| 		{desc: "curry proc", expr: ` |  | ||||||
| 			proc curry { |name b| |  | ||||||
| 				proc { |a| call $name [$a $b] } |  | ||||||
| 			} |  | ||||||
| 			add2 = curry add 2 |  | ||||||
| 			map [1 2 3] $add2 | cat |  | ||||||
| 			`, want: "[3 4 5]\n"}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			ctx := context.Background() |  | ||||||
| 			outW := bytes.NewBuffer(nil) |  | ||||||
| 
 |  | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) |  | ||||||
| 			err := evalAndDisplay(ctx, inst, tt.expr) |  | ||||||
| 
 |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.Equal(t, tt.want, outW.String()) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestBuiltins_In(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc string |  | ||||||
| 		expr string |  | ||||||
| 		want string |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "in str 1", expr: `in "absolute" "sol"`, want: "true\n"}, |  | ||||||
| 		{desc: "in str 2", expr: `in "absolute" "not here"`, want: "false\n"}, |  | ||||||
| 		{desc: "in list 1", expr: `in [1 2 3] 2`, want: "true\n"}, |  | ||||||
| 		{desc: "in list 2", expr: `in [1 2 3] 4`, want: "false\n"}, |  | ||||||
| 		{desc: "in map as key 1", expr: `in [a:1 b:2 c:3] a`, want: "true\n"}, |  | ||||||
| 		{desc: "in map as key 2", expr: `in [a:1 b:2 c:3] gad`, want: "false\n"}, |  | ||||||
| 		{desc: "in itr 1", expr: `in (itr) 2`, want: "true\n"}, |  | ||||||
| 		{desc: "in itr 2", expr: `in (itr) 8`, want: "false\n"}, |  | ||||||
| 		{desc: "in itr 3", expr: `itr = itr ; in $itr 2 ; head $itr`, want: "3\n"}, |  | ||||||
| 		{desc: "in itr 4", expr: `itr = itr ; in $itr 8 ; head $itr`, want: "(nil)\n"}, |  | ||||||
| 		{desc: "in nil", expr: `in () 4`, want: "false\n"}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.desc, func(t *testing.T) { |  | ||||||
| 			ctx := context.Background() |  | ||||||
| 			outW := bytes.NewBuffer(nil) |  | ||||||
| 
 |  | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) |  | ||||||
| 			err := evalAndDisplay(ctx, inst, tt.expr) |  | ||||||
| 
 |  | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.Equal(t, tt.want, outW.String()) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestBuiltins_Map(t *testing.T) { | func TestBuiltins_Map(t *testing.T) { | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		desc string | 		desc string | ||||||
|  | @ -1152,12 +1064,12 @@ func TestBuiltins_Map(t *testing.T) { | ||||||
| 			map ["a" "b" "c"] (proc { |x| makeUpper $x })  | 			map ["a" "b" "c"] (proc { |x| makeUpper $x })  | ||||||
| 			`, want: "A\nB\nC\n"}, | 			`, want: "A\nB\nC\n"}, | ||||||
| 		{desc: "map list 2", expr: ` | 		{desc: "map list 2", expr: ` | ||||||
| 			makeUpper = proc { |x| $x | toUpper } | 			$makeUpper = proc { |x| $x | toUpper } | ||||||
| 
 | 
 | ||||||
| 			map ["a" "b" "c"] $makeUpper  | 			map ["a" "b" "c"] $makeUpper  | ||||||
| 			`, want: "A\nB\nC\n"}, | 			`, want: "A\nB\nC\n"}, | ||||||
| 		{desc: "map list with pipe", expr: ` | 		{desc: "map list with pipe", expr: ` | ||||||
| 			makeUpper = proc { |x| $x | toUpper } | 			$makeUpper = proc { |x| $x | toUpper } | ||||||
| 
 | 
 | ||||||
| 			["a" "b" "c"] | map $makeUpper  | 			["a" "b" "c"] | map $makeUpper  | ||||||
| 			`, want: "A\nB\nC\n"}, | 			`, want: "A\nB\nC\n"}, | ||||||
|  | @ -1165,15 +1077,15 @@ func TestBuiltins_Map(t *testing.T) { | ||||||
| 			map ["a" "b" "c"] { |x| toUpper $x }  | 			map ["a" "b" "c"] { |x| toUpper $x }  | ||||||
| 			`, want: "A\nB\nC\n"}, | 			`, want: "A\nB\nC\n"}, | ||||||
| 		{desc: "map list with stream", expr: ` | 		{desc: "map list with stream", expr: ` | ||||||
| 			makeUpper = proc { |x| toUpper $x } | 			$makeUpper = proc { |x| toUpper $x } | ||||||
| 		 | 		 | ||||||
| 			l = ["a" "b" "c"] | map $makeUpper | 			$l = ["a" "b" "c"] | map $makeUpper | ||||||
| 			echo $l | 			echo $l | ||||||
| 			`, want: "[A B C]\n(nil)\n"}, | 			`, want: "[A B C]\n(nil)\n"}, | ||||||
| 		{desc: "map itr stream", expr: ` | 		{desc: "map itr stream", expr: ` | ||||||
| 			add2 = proc { |x| add $x 2 } | 			$add2 = proc { |x| add $x 2 } | ||||||
| 		 | 		 | ||||||
| 			l = itr | map $add2 | 			$l = itr | map $add2 | ||||||
| 			for $l { |x| echo $x } | 			for $l { |x| echo $x } | ||||||
| 			`, want: "3\n4\n5\n(nil)\n"}, | 			`, want: "3\n4\n5\n(nil)\n"}, | ||||||
| 	} | 	} | ||||||
|  | @ -1397,7 +1309,7 @@ func TestBuiltins_Keys(t *testing.T) { | ||||||
| 				}, nil | 				}, nil | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Len(t, res, len(tt.wantItems)) | 			assert.Len(t, res, len(tt.wantItems)) | ||||||
| 			for _, i := range tt.wantItems { | 			for _, i := range tt.wantItems { | ||||||
|  | @ -1427,7 +1339,7 @@ func TestBuiltins_Filter(t *testing.T) { | ||||||
| 		}}, | 		}}, | ||||||
| 		{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}}, | 		{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "filter itr 1", expr: `s = "" ; itr | filter { |x| ne $x 2 } | for { |x| s = "$s $x" }; $s`, want: " 1 3"}, | 		{desc: "filter itr 1", expr: `$s = "" ; itr | filter { |x| ne $x 2 } | for { |x| $s = "$s $x" }; $s`, want: " 1 3"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -1437,7 +1349,7 @@ func TestBuiltins_Filter(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1463,7 +1375,7 @@ func TestBuiltins_Reduce(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1479,10 +1391,10 @@ func TestBuiltins_Head(t *testing.T) { | ||||||
| 		{desc: "head list 1", expr: `head [1 2 3]`, want: 1}, | 		{desc: "head list 1", expr: `head [1 2 3]`, want: 1}, | ||||||
| 
 | 
 | ||||||
| 		{desc: "head itr 1", expr: `head (itr)`, want: 1}, | 		{desc: "head itr 1", expr: `head (itr)`, want: 1}, | ||||||
| 		{desc: "head itr 2", expr: `h = (itr) ; head $h`, want: 1}, | 		{desc: "head itr 2", expr: `$h = (itr) ; head $h`, want: 1}, | ||||||
| 		{desc: "head itr 3", expr: `h = (itr) ; head $h ; head $h`, want: 2}, | 		{desc: "head itr 3", expr: `$h = (itr) ; head $h ; head $h`, want: 2}, | ||||||
| 		{desc: "head itr 4", expr: `h = (itr) ; head $h ; head $h ; head $h`, want: 3}, | 		{desc: "head itr 4", expr: `$h = (itr) ; head $h ; head $h ; head $h`, want: 3}, | ||||||
| 		{desc: "head itr 5", expr: `h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil}, | 		{desc: "head itr 5", expr: `$h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -1492,7 +1404,7 @@ func TestBuiltins_Head(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(ctx, tt.expr) | 			res, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1547,7 +1459,7 @@ func TestBuiltins_LtLeGtLe(t *testing.T) { | ||||||
| 			inst.SetVar("true", true) | 			inst.SetVar("true", true) | ||||||
| 			inst.SetVar("false", false) | 			inst.SetVar("false", false) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 
 | 
 | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
|  | @ -1615,11 +1527,11 @@ func TestBuiltins_EqNe(t *testing.T) { | ||||||
| 			inst.SetVar("true", true) | 			inst.SetVar("true", true) | ||||||
| 			inst.SetVar("false", false) | 			inst.SetVar("false", false) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, eqRes) | 			assert.Equal(t, tt.want, eqRes) | ||||||
| 
 | 
 | ||||||
| 			neRes, err := inst.EvalString(ctx, strings.ReplaceAll(tt.expr, "eq", "ne")) | 			neRes, err := inst.Eval(ctx, strings.ReplaceAll(tt.expr, "eq", "ne")) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, !tt.want, neRes) | 			assert.Equal(t, !tt.want, neRes) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1650,7 +1562,7 @@ func TestBuiltins_Str(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, eqRes) | 			assert.Equal(t, tt.want, eqRes) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1686,7 +1598,7 @@ func TestBuiltins_Int(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -1742,7 +1654,7 @@ func TestBuiltins_AddSubMupDivMod(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 			inst := New(WithOut(outW), WithTestBuiltin()) | 			inst := New(WithOut(outW), WithTestBuiltin()) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -1793,7 +1705,7 @@ func TestBuiltins_AndOrNot(t *testing.T) { | ||||||
| 			inst.SetVar("true", true) | 			inst.SetVar("true", true) | ||||||
| 			inst.SetVar("false", false) | 			inst.SetVar("false", false) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			if tt.wantErr { | 			if tt.wantErr { | ||||||
| 				assert.Error(t, err) | 				assert.Error(t, err) | ||||||
| 			} else { | 			} else { | ||||||
|  | @ -1818,7 +1730,7 @@ func TestBuiltins_Cat(t *testing.T) { | ||||||
| 		{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"}, | 		{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"}, | ||||||
| 		{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"}, | 		{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"}, | ||||||
| 		{desc: "cat 8", expr: `cat`, want: ""}, | 		{desc: "cat 8", expr: `cat`, want: ""}, | ||||||
| 		{desc: "cat 9", expr: `x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"}, | 		{desc: "cat 9", expr: `$x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
|  | @ -1830,74 +1742,7 @@ func TestBuiltins_Cat(t *testing.T) { | ||||||
| 			inst.SetVar("true", true) | 			inst.SetVar("true", true) | ||||||
| 			inst.SetVar("false", false) | 			inst.SetVar("false", false) | ||||||
| 
 | 
 | ||||||
| 			eqRes, err := inst.EvalString(ctx, tt.expr) | 			eqRes, err := inst.Eval(ctx, tt.expr) | ||||||
| 			assert.NoError(t, err) |  | ||||||
| 			assert.Equal(t, tt.want, eqRes) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestBuiltins_Not(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		desc string |  | ||||||
| 		expr string |  | ||||||
| 		want any |  | ||||||
| 	}{ |  | ||||||
| 		{desc: "not 1", expr: `not 1`, want: false}, |  | ||||||
| 		{desc: "not 2", expr: `not 0`, want: true}, |  | ||||||
| 		{desc: "not 3", expr: `not ()`, want: true}, |  | ||||||
| 		{desc: "not 4", expr: `not $true`, want: false}, |  | ||||||
| 		{desc: "not 5", expr: `not $false`, want: true}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	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 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.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, eqRes) | 			assert.Equal(t, tt.want, eqRes) | ||||||
| 		}) | 		}) | ||||||
|  | @ -1905,7 +1750,7 @@ func TestBuiltins_NotNil(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error { | func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error { | ||||||
| 	res, err := inst.eval(ctx, strings.NewReader(expr), evalOptions{}) | 	res, err := inst.eval(ctx, expr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -5,7 +5,7 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 
 | 
 | ||||||
| 	"lmika.dev/pkg/modash/moslice" | 	"github.com/lmika/gopkgs/fp/slices" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error) | type BuiltinHandler func(ctx context.Context, args CallArgs) (any, error) | ||||||
|  | @ -34,10 +34,6 @@ func (ca *CallArgs) Bind(vars ...interface{}) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (ca *CallArgs) RestAsObjects() []Object { |  | ||||||
| 	return ca.args.args |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (ca *CallArgs) CanBind(vars ...interface{}) bool { | func (ca *CallArgs) CanBind(vars ...interface{}) bool { | ||||||
| 	if len(ca.args.args) < len(vars) { | 	if len(ca.args.args) < len(vars) { | ||||||
| 		return false | 		return false | ||||||
|  | @ -85,10 +81,6 @@ func (inst *Inst) SetBuiltin(name string, fn BuiltinHandler) { | ||||||
| 	inst.rootEC.addCmd(name, userBuiltin{fn: fn}) | 	inst.rootEC.addCmd(name, userBuiltin{fn: fn}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (inst *Inst) SetBuiltinInvokable(name string, fn Invokable) { |  | ||||||
| 	inst.rootEC.addCmd(name, fn.inv) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type userBuiltin struct { | type userBuiltin struct { | ||||||
| 	fn func(ctx context.Context, args CallArgs) (any, error) | 	fn func(ctx context.Context, args CallArgs) (any, error) | ||||||
| } | } | ||||||
|  | @ -111,30 +103,17 @@ func (ca CallArgs) bindArg(v interface{}, arg Object) error { | ||||||
| 		*t, _ = toGoValue(arg) | 		*t, _ = toGoValue(arg) | ||||||
| 		return nil | 		return nil | ||||||
| 	case *Invokable: | 	case *Invokable: | ||||||
| 		switch ait := arg.(type) { | 		i, ok := arg.(invokable) | ||||||
| 		case invokable: | 		if !ok { | ||||||
| 			*t = Invokable{ |  | ||||||
| 				inv:  ait, |  | ||||||
| 				eval: ca.args.eval, |  | ||||||
| 				inst: ca.args.inst, |  | ||||||
| 				ec:   ca.args.ec, |  | ||||||
| 			} |  | ||||||
| 			return nil |  | ||||||
| 		case StringObject: |  | ||||||
| 			iv := ca.args.ec.lookupInvokable(string(ait)) |  | ||||||
| 			if iv == nil { |  | ||||||
| 				return errors.New("'" + string(ait) + "' is not invokable") |  | ||||||
| 			} |  | ||||||
| 			*t = Invokable{ |  | ||||||
| 				inv:  iv, |  | ||||||
| 				eval: ca.args.eval, |  | ||||||
| 				inst: ca.args.inst, |  | ||||||
| 				ec:   ca.args.ec, |  | ||||||
| 			} |  | ||||||
| 			return nil |  | ||||||
| 		default: |  | ||||||
| 			return errors.New("exepected invokable") | 			return errors.New("exepected invokable") | ||||||
| 		} | 		} | ||||||
|  | 		*t = Invokable{ | ||||||
|  | 			inv:  i, | ||||||
|  | 			eval: ca.args.eval, | ||||||
|  | 			inst: ca.args.inst, | ||||||
|  | 			ec:   ca.args.ec, | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
| 	case *Listable: | 	case *Listable: | ||||||
| 		i, ok := arg.(Listable) | 		i, ok := arg.(Listable) | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -180,12 +159,10 @@ func (ca CallArgs) bindArg(v interface{}, arg Object) error { | ||||||
| 	switch t := arg.(type) { | 	switch t := arg.(type) { | ||||||
| 	case proxyObject: | 	case proxyObject: | ||||||
| 		return bindProxyObject(v, reflect.ValueOf(t.p)) | 		return bindProxyObject(v, reflect.ValueOf(t.p)) | ||||||
| 	case OpaqueObject: |  | ||||||
| 		return bindProxyObject(v, reflect.ValueOf(t.v)) |  | ||||||
| 	case listableProxyObject: | 	case listableProxyObject: | ||||||
| 		return bindProxyObject(v, t.orig) | 		return bindProxyObject(v, t.v) | ||||||
| 	case structProxyObject: | 	case structProxyObject: | ||||||
| 		return bindProxyObject(v, t.orig) | 		return bindProxyObject(v, t.v) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return bindProxyObject(v, reflect.ValueOf(arg)) | 	return bindProxyObject(v, reflect.ValueOf(arg)) | ||||||
|  | @ -204,9 +181,9 @@ func canBindArg(v interface{}, arg Object) bool { | ||||||
| 	case proxyObject: | 	case proxyObject: | ||||||
| 		return canBindProxyObject(v, reflect.ValueOf(t.p)) | 		return canBindProxyObject(v, reflect.ValueOf(t.p)) | ||||||
| 	case listableProxyObject: | 	case listableProxyObject: | ||||||
| 		return canBindProxyObject(v, t.orig) | 		return canBindProxyObject(v, t.v) | ||||||
| 	case structProxyObject: | 	case structProxyObject: | ||||||
| 		return canBindProxyObject(v, t.orig) | 		return canBindProxyObject(v, t.v) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return true | 	return true | ||||||
|  | @ -291,7 +268,7 @@ func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) { | ||||||
| 		inst: i.inst, | 		inst: i.inst, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	invArgs.args, err = moslice.MapWithError(args, func(a any) (Object, error) { | 	invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) { | ||||||
| 		return fromGoValue(a) | 		return fromGoValue(a) | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 |  | ||||||
| 	"ucl.lmika.dev/ucl" | 	"ucl.lmika.dev/ucl" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | @ -25,64 +24,11 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 			return x + y, nil | 			return x + y, nil | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		res, err := inst.EvalString(context.Background(), `add2 "Hello, " "World"`) | 		res, err := inst.Eval(context.Background(), `add2 "Hello, " "World"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, "Hello, World", res) | 		assert.Equal(t, "Hello, World", res) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("simple builtin accepting and returning pointers", func(t *testing.T) { |  | ||||||
| 		type point struct { |  | ||||||
| 			x, y int |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		tests := []struct { |  | ||||||
| 			descr string |  | ||||||
| 			expr  string |  | ||||||
| 			want  string |  | ||||||
| 		}{ |  | ||||||
| 			{descr: "pass via args", expr: `vec 1 2 | vadd`, want: "3"}, |  | ||||||
| 			{descr: "pass via vars", expr: `x = (vec 2 3) ; vadd $x`, want: "5"}, |  | ||||||
| 			{descr: "pass twice", expr: `vadd (vadd2 (vec 1 2) (vec 3 4))`, want: "10"}, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, tt := range tests { |  | ||||||
| 			t.Run(tt.descr, func(t *testing.T) { |  | ||||||
| 				inst := ucl.New() |  | ||||||
| 				inst.SetBuiltin("vec", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var x, y int |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&x, &y); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return &point{x, y}, nil |  | ||||||
| 				}) |  | ||||||
| 				inst.SetBuiltin("vadd", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var v *point |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&v); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return v.x + v.y, nil |  | ||||||
| 				}) |  | ||||||
| 				inst.SetBuiltin("vadd2", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var v, u *point |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&v, &u); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return &point{v.x + u.x, v.y + u.y}, nil |  | ||||||
| 				}) |  | ||||||
| 
 |  | ||||||
| 				res, err := inst.EvalString(context.Background(), tt.expr) |  | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want, fmt.Sprint(res)) |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("bind shift arguments", func(t *testing.T) { | 	t.Run("bind shift arguments", func(t *testing.T) { | ||||||
| 		inst := ucl.New() | 		inst := ucl.New() | ||||||
| 		inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) { | 		inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
|  | @ -98,7 +44,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 			return x + y, nil | 			return x + y, nil | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		res, err := inst.EvalString(context.Background(), `add2 "Hello, " "World"`) | 		res, err := inst.Eval(context.Background(), `add2 "Hello, " "World"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, "Hello, World", res) | 		assert.Equal(t, "Hello, World", res) | ||||||
| 	}) | 	}) | ||||||
|  | @ -139,7 +85,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 		for _, tt := range tests { | 		for _, tt := range tests { | ||||||
| 			t.Run(tt.descr, func(t *testing.T) { | 			t.Run(tt.descr, func(t *testing.T) { | ||||||
| 				res, err := inst.EvalString(context.Background(), tt.expr) | 				res, err := inst.Eval(context.Background(), tt.expr) | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				assert.Equal(t, tt.want, res) | 				assert.Equal(t, tt.want, res) | ||||||
| 			}) | 			}) | ||||||
|  | @ -159,10 +105,10 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return ucl.Opaque(pair{x, y}), nil | 			return pair{x, y}, nil | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		res, err := inst.EvalString(context.Background(), `add2 "Hello" "World"`) | 		res, err := inst.Eval(context.Background(), `add2 "Hello" "World"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, pair{"Hello", "World"}, res) | 		assert.Equal(t, pair{"Hello", "World"}, res) | ||||||
| 	}) | 	}) | ||||||
|  | @ -178,7 +124,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 			want  string | 			want  string | ||||||
| 		}{ | 		}{ | ||||||
| 			{descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"}, | 			{descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"}, | ||||||
| 			{descr: "pass via vars", expr: `x = (add2 "blue" "green") ; join $x`, want: "blue:green"}, | 			{descr: "pass via vars", expr: `$x = (add2 "blue" "green") ; join $x`, want: "blue:green"}, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		for _, tt := range tests { | 		for _, tt := range tests { | ||||||
|  | @ -191,7 +137,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 						return nil, err | 						return nil, err | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					return ucl.Opaque(pair{x, y}), nil | 					return pair{x, y}, nil | ||||||
| 				}) | 				}) | ||||||
| 				inst.SetBuiltin("join", func(ctx context.Context, args ucl.CallArgs) (any, error) { | 				inst.SetBuiltin("join", func(ctx context.Context, args ucl.CallArgs) (any, error) { | ||||||
| 					var x pair | 					var x pair | ||||||
|  | @ -203,50 +149,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 					return x.x + ":" + x.y, nil | 					return x.x + ":" + x.y, nil | ||||||
| 				}) | 				}) | ||||||
| 
 | 
 | ||||||
| 				res, err := inst.EvalString(context.Background(), tt.expr) | 				res, err := inst.Eval(context.Background(), tt.expr) | ||||||
| 				assert.NoError(t, err) |  | ||||||
| 				assert.Equal(t, tt.want, res) |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("builtin operating on and returning proxy object for pointers", func(t *testing.T) { |  | ||||||
| 		type pair struct { |  | ||||||
| 			x, y string |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		tests := []struct { |  | ||||||
| 			descr string |  | ||||||
| 			expr  string |  | ||||||
| 			want  string |  | ||||||
| 		}{ |  | ||||||
| 			{descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"}, |  | ||||||
| 			{descr: "pass via vars", expr: `x = (add2 "blue" "green") ; join $x`, want: "blue:green"}, |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, tt := range tests { |  | ||||||
| 			t.Run(tt.descr, func(t *testing.T) { |  | ||||||
| 				inst := ucl.New() |  | ||||||
| 				inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var x, y string |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&x, &y); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return ucl.Opaque(&pair{x, y}), nil |  | ||||||
| 				}) |  | ||||||
| 				inst.SetBuiltin("join", func(ctx context.Context, args ucl.CallArgs) (any, error) { |  | ||||||
| 					var x *pair |  | ||||||
| 
 |  | ||||||
| 					if err := args.Bind(&x); err != nil { |  | ||||||
| 						return nil, err |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					return x.x + ":" + x.y, nil |  | ||||||
| 				}) |  | ||||||
| 
 |  | ||||||
| 				res, err := inst.EvalString(context.Background(), tt.expr) |  | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				assert.Equal(t, tt.want, res) | 				assert.Equal(t, tt.want, res) | ||||||
| 			}) | 			}) | ||||||
|  | @ -273,7 +176,7 @@ func TestInst_SetBuiltin(t *testing.T) { | ||||||
| 					return []string{"1", "2", "3"}, nil | 					return []string{"1", "2", "3"}, nil | ||||||
| 				}) | 				}) | ||||||
| 
 | 
 | ||||||
| 				res, err := inst.EvalString(context.Background(), tt.expr) | 				res, err := inst.Eval(context.Background(), tt.expr) | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				assert.Equal(t, tt.want, res) | 				assert.Equal(t, tt.want, res) | ||||||
| 				assert.Equal(t, tt.wantOut, outW.String()) | 				assert.Equal(t, tt.wantOut, outW.String()) | ||||||
|  | @ -303,11 +206,11 @@ func TestCallArgs_Bind(t *testing.T) { | ||||||
| 			return ds.DoString(), nil | 			return ds.DoString(), nil | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		va, err := inst.EvalString(ctx, `dostr (sa)`) | 		va, err := inst.Eval(ctx, `dostr (sa)`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, "do string A: a val", va) | 		assert.Equal(t, "do string A: a val", va) | ||||||
| 
 | 
 | ||||||
| 		vb, err := inst.EvalString(ctx, `dostr (sb)`) | 		vb, err := inst.Eval(ctx, `dostr (sb)`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, "do string B: foo bar", vb) | 		assert.Equal(t, "do string B: foo bar", vb) | ||||||
| 	}) | 	}) | ||||||
|  | @ -337,7 +240,7 @@ func TestCallArgs_Bind(t *testing.T) { | ||||||
| 					return fmt.Sprintf("[%v]", v), nil | 					return fmt.Sprintf("[%v]", v), nil | ||||||
| 				}) | 				}) | ||||||
| 
 | 
 | ||||||
| 				res, err := inst.EvalString(ctx, tt.eval) | 				res, err := inst.Eval(ctx, tt.eval) | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				assert.Equal(t, tt.want, res) | 				assert.Equal(t, tt.want, res) | ||||||
| 			}) | 			}) | ||||||
|  | @ -392,7 +295,7 @@ func TestCallArgs_CanBind(t *testing.T) { | ||||||
| 				return nil, nil | 				return nil, nil | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
| 			_, err := inst.EvalString(ctx, tt.eval) | 			_, err := inst.Eval(ctx, tt.eval) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  | @ -429,7 +332,7 @@ func TestCallArgs_CanBind(t *testing.T) { | ||||||
| 					return h.Value(k), nil | 					return h.Value(k), nil | ||||||
| 				}) | 				}) | ||||||
| 
 | 
 | ||||||
| 				res, err := inst.EvalString(ctx, tt.eval) | 				res, err := inst.Eval(ctx, tt.eval) | ||||||
| 				if tt.wantErr { | 				if tt.wantErr { | ||||||
| 					assert.Error(t, err) | 					assert.Error(t, err) | ||||||
| 					assert.Nil(t, res) | 					assert.Nil(t, res) | ||||||
|  | @ -467,7 +370,7 @@ func TestCallArgs_CanBind(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 		ctx := context.Background() | 		ctx := context.Background() | ||||||
| 
 | 
 | ||||||
| 		res, err := inst.EvalString(ctx, `wrap { |x| toUpper $x }`) | 		res, err := inst.Eval(ctx, `wrap { |x| toUpper $x }`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Equal(t, "[[HELLO]]", res) | 		assert.Equal(t, "[[HELLO]]", res) | ||||||
| 	}) | 	}) | ||||||
|  | @ -498,7 +401,7 @@ func TestCallArgs_CanBind(t *testing.T) { | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Nil(t, before) | 		assert.Nil(t, before) | ||||||
| 
 | 
 | ||||||
| 		res, err := inst.EvalString(ctx, `wrap { |x| toUpper $x }`) | 		res, err := inst.Eval(ctx, `wrap { |x| toUpper $x }`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.Nil(t, res) | 		assert.Nil(t, res) | ||||||
| 
 | 
 | ||||||
|  | @ -534,7 +437,7 @@ func TestCallArgs_MissingCommandHandler(t *testing.T) { | ||||||
| 					return fmt.Sprintf("was %v", name), nil | 					return fmt.Sprintf("was %v", name), nil | ||||||
| 				})) | 				})) | ||||||
| 
 | 
 | ||||||
| 			res, err := inst.EvalString(ctx, tt.eval) | 			res, err := inst.Eval(ctx, tt.eval) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Equal(t, tt.want, res) | 			assert.Equal(t, tt.want, res) | ||||||
| 		}) | 		}) | ||||||
|  | @ -557,27 +460,27 @@ func TestCallArgs_IsTopLevel(t *testing.T) { | ||||||
| 			return nil, nil | 			return nil, nil | ||||||
| 		}) | 		}) | ||||||
| 
 | 
 | ||||||
| 		_, err := inst.EvalString(ctx, `lvl "one"`) | 		_, err := inst.Eval(ctx, `lvl "one"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, res["one"]) | 		assert.True(t, res["one"]) | ||||||
| 
 | 
 | ||||||
| 		_, err = inst.EvalString(ctx, `echo (lvl "two")`) | 		_, err = inst.Eval(ctx, `echo (lvl "two")`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, res["two"]) | 		assert.True(t, res["two"]) | ||||||
| 
 | 
 | ||||||
| 		_, err = inst.EvalString(ctx, `proc doLvl { |n| lvl $n } ; doLvl "three"`) | 		_, err = inst.Eval(ctx, `proc doLvl { |n| lvl $n } ; doLvl "three"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, res["three"]) | 		assert.False(t, res["three"]) | ||||||
| 
 | 
 | ||||||
| 		_, err = inst.EvalString(ctx, `doLvl "four"`) | 		_, err = inst.Eval(ctx, `doLvl "four"`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, res["four"]) | 		assert.False(t, res["four"]) | ||||||
| 
 | 
 | ||||||
| 		_, err = inst.EvalString(ctx, `["a"] | map { |x| doLvl "five" ; $x }`) | 		_, err = inst.Eval(ctx, `["a"] | map { |x| doLvl "five" ; $x }`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.False(t, res["five"]) | 		assert.False(t, res["five"]) | ||||||
| 
 | 
 | ||||||
| 		_, err = inst.EvalString(ctx, `if 1 { lvl "six" }`) | 		_, err = inst.Eval(ctx, `if 1 { lvl "six" }`) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.True(t, res["six"]) | 		assert.True(t, res["six"]) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue