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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,74 +11,16 @@ func Strs() ucl.Module {
|
||||||
return ucl.Module{
|
return ucl.Module{
|
||||||
Name: "strs",
|
Name: "strs",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"to-upper": toUpper,
|
"to-upper": toUpper,
|
||||||
"to-lower": toLower,
|
"to-lower": toLower,
|
||||||
"trim": trim,
|
"trim": trim,
|
||||||
"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)
|
||||||
|
|
|
||||||
39
ucl/env.go
39
ucl/env.go
|
|
@ -1,17 +1,18 @@
|
||||||
package ucl
|
package ucl
|
||||||
|
|
||||||
type evalCtx struct {
|
type evalCtx struct {
|
||||||
root *evalCtx
|
root *evalCtx
|
||||||
parent *evalCtx
|
parent *evalCtx
|
||||||
commands map[string]invokable
|
commands map[string]invokable
|
||||||
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 {
|
||||||
|
|
|
||||||
219
ucl/inst_test.go
219
ucl/inst_test.go
|
|
@ -3,39 +3,35 @@ 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) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
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