Compare commits
5 Commits
feature/is
...
main
Author | SHA1 | Date |
---|---|---|
Leon Mika | 9b3b8287fa | |
Leon Mika | c4e4a0977b | |
Leon Mika | 2f54a9311e | |
lmika | 5b913266e9 | |
Leon Mika | bb78a39cdb |
|
@ -68,6 +68,7 @@ type astDot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type astCmd struct {
|
type astCmd struct {
|
||||||
|
Pos lexer.Position
|
||||||
Name astDot `parser:"@@"`
|
Name astDot `parser:"@@"`
|
||||||
Args []astDot `parser:"@@*"`
|
Args []astDot `parser:"@@*"`
|
||||||
}
|
}
|
||||||
|
|
183
ucl/builtins.go
183
ucl/builtins.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -29,7 +29,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func addBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return intObject(0), nil
|
return intObject(0), nil
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func subBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return intObject(0), nil
|
return intObject(0), nil
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func mupBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return intObject(1), nil
|
return intObject(1), nil
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func divBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return intObject(1), nil
|
return intObject(1), nil
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func modBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return intObject(0), nil
|
return intObject(0), nil
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func setBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return newVal, nil
|
return newVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func toUpperBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return strObject(strings.ToUpper(sarg)), nil
|
return strObject(strings.ToUpper(sarg)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func eqBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
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) {
|
||||||
if err := args.expectArgn(2); err != nil {
|
if err := args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
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) {
|
||||||
if err := args.expectArgn(2); err != nil {
|
if err := args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return boolObject(isLess), nil
|
return boolObject(isLess), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func leBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func leBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
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) {
|
||||||
if err := args.expectArgn(2); err != nil {
|
if err := args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return boolObject(isGreater), nil
|
return boolObject(isGreater), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func geBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func geBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
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) {
|
||||||
if err := args.expectArgn(2); err != nil {
|
if err := args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return args.args[len(args.args)-1], nil
|
return args.args[len(args.args)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func orBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return boolObject(false), nil
|
return boolObject(false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func notBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -318,8 +318,8 @@ func objectsEqual(l, r object) bool {
|
||||||
if rv, ok := r.(boolObject); ok {
|
if rv, ok := r.(boolObject); ok {
|
||||||
return lv == rv
|
return lv == rv
|
||||||
}
|
}
|
||||||
case listable:
|
case Listable:
|
||||||
rv, ok := r.(listable)
|
rv, ok := r.(Listable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -342,7 +342,7 @@ func objectsEqual(l, r object) bool {
|
||||||
if lv.Len() != rv.Len() {
|
if lv.Len() != rv.Len() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if err := lv.Each(func(k string, lkv object) error {
|
if err := lv.Each(func(k string, lkv Object) error {
|
||||||
rkv := rv.Value(k)
|
rkv := rv.Value(k)
|
||||||
if rkv == nil {
|
if rkv == nil {
|
||||||
return errObjectsNotEqual
|
return errObjectsNotEqual
|
||||||
|
@ -365,7 +365,7 @@ func objectsEqual(l, r object) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func objectsLessThan(l, r object) (bool, error) {
|
func objectsLessThan(l, r Object) (bool, error) {
|
||||||
switch lv := l.(type) {
|
switch lv := l.(type) {
|
||||||
case strObject:
|
case strObject:
|
||||||
if rv, ok := r.(strObject); ok {
|
if rv, ok := r.(strObject); ok {
|
||||||
|
@ -379,7 +379,7 @@ func objectsLessThan(l, r object) (bool, error) {
|
||||||
return false, errors.New("objects are not comparable")
|
return false, errors.New("objects are not comparable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func strBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -391,7 +391,7 @@ func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return strObject(args.args[0].String()), nil
|
return strObject(args.args[0].String()), 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
|
||||||
}
|
}
|
||||||
|
@ -419,7 +419,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, errors.New("cannot convert to int")
|
return nil, errors.New("cannot convert to int")
|
||||||
}
|
}
|
||||||
|
|
||||||
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func concatBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
for _, a := range args.args {
|
for _, a := range args.args {
|
||||||
|
@ -432,7 +432,7 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return strObject(sb.String()), nil
|
return strObject(sb.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func callBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -445,7 +445,7 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return inv.invoke(ctx, args.shift(1))
|
return inv.invoke(ctx, args.shift(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func lenBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -453,7 +453,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
switch v := args.args[0].(type) {
|
switch v := args.args[0].(type) {
|
||||||
case strObject:
|
case strObject:
|
||||||
return intObject(len(string(v))), nil
|
return intObject(len(string(v))), nil
|
||||||
case listable:
|
case Listable:
|
||||||
return intObject(v.Len()), nil
|
return intObject(v.Len()), nil
|
||||||
case hashable:
|
case hashable:
|
||||||
return intObject(v.Len()), nil
|
return intObject(v.Len()), nil
|
||||||
|
@ -462,9 +462,9 @@ 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) (object, error) {
|
func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
intIdx, ok := elem.(intObject)
|
intIdx, ok := elem.(intObject)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -483,7 +483,7 @@ func indexLookup(ctx context.Context, obj, elem object) (object, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -500,7 +500,7 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func keysBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -509,7 +509,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case hashable:
|
case hashable:
|
||||||
keys := make(listObject, 0, v.Len())
|
keys := make(listObject, 0, v.Len())
|
||||||
if err := v.Each(func(k string, _ object) error {
|
if err := v.Each(func(k string, _ Object) error {
|
||||||
keys = append(keys, strObject(k))
|
keys = append(keys, strObject(k))
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -521,7 +521,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -532,12 +532,12 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
newList := listObject{}
|
newList := listObject{}
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
m, err := inv.invoke(ctx, args.fork([]object{v}))
|
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -548,7 +548,7 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, errors.New("expected listable")
|
return nil, errors.New("expected listable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func filterBuiltin(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
|
||||||
}
|
}
|
||||||
|
@ -559,12 +559,12 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
newList := listObject{}
|
newList := listObject{}
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
m, err := inv.invoke(ctx, args.fork([]object{v}))
|
m, err := inv.invoke(ctx, args.fork([]Object{v}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if m.Truthy() {
|
} else if m.Truthy() {
|
||||||
|
@ -574,8 +574,8 @@ 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{strObject(k), v})); err != nil {
|
if m, err := inv.invoke(ctx, args.fork([]Object{strObject(k), v})); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if m.Truthy() {
|
} else if m.Truthy() {
|
||||||
newHash[k] = v
|
newHash[k] = v
|
||||||
|
@ -589,14 +589,14 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, errors.New("expected listable")
|
return nil, errors.New("expected listable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func reduceBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
var err error
|
var err error
|
||||||
if err = args.expectArgn(2); err != nil {
|
if err = args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accum object
|
accum Object
|
||||||
setFirst bool
|
setFirst bool
|
||||||
block invokable
|
block invokable
|
||||||
)
|
)
|
||||||
|
@ -615,7 +615,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
|
@ -625,7 +625,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccum, err := block.invoke(ctx, args.fork([]object{v, accum}))
|
newAccum, err := block.invoke(ctx, args.fork([]Object{v, accum}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -635,8 +635,8 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return accum, nil
|
return accum, nil
|
||||||
case hashable:
|
case hashable:
|
||||||
// TODO: should raise error?
|
// TODO: should raise error?
|
||||||
if err := t.Each(func(k string, v object) error {
|
if err := t.Each(func(k string, v Object) error {
|
||||||
newAccum, err := block.invoke(ctx, args.fork([]object{strObject(k), v, accum}))
|
newAccum, err := block.invoke(ctx, args.fork([]Object{strObject(k), v, accum}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -650,13 +650,13 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return nil, errors.New("expected listable")
|
return nil, errors.New("expected listable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func firstBuiltin(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
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := args.args[0].(type) {
|
switch t := args.args[0].(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
if t.Len() == 0 {
|
if t.Len() == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -692,7 +692,7 @@ func (s seqObject) Len() int {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s seqObject) Index(i int) object {
|
func (s seqObject) Index(i int) Object {
|
||||||
l := s.Len()
|
l := s.Len()
|
||||||
if i < 0 || i > l {
|
if i < 0 || i > l {
|
||||||
return nil
|
return nil
|
||||||
|
@ -703,7 +703,7 @@ func (s seqObject) Index(i int) object {
|
||||||
return intObject(s.from + i)
|
return intObject(s.from + i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func seqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
inclusive := false
|
inclusive := false
|
||||||
if inc, ok := args.kwargs["inc"]; ok {
|
if inc, ok := args.kwargs["inc"]; ok {
|
||||||
inclusive = (inc.Len() == 0) || inc.Truthy()
|
inclusive = (inc.Len() == 0) || inc.Truthy()
|
||||||
|
@ -732,7 +732,7 @@ func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
if args.nargs() < 2 {
|
if args.nargs() < 2 {
|
||||||
return nil, errors.New("need at least 2 arguments")
|
return nil, errors.New("need at least 2 arguments")
|
||||||
}
|
}
|
||||||
|
@ -770,9 +770,64 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
return nil, errors.New("malformed if-elif-else")
|
return nil, errors.New("malformed if-elif-else")
|
||||||
}
|
}
|
||||||
|
|
||||||
func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
func tryBuiltin(ctx context.Context, args macroArgs) (_ Object, fnErr error) {
|
||||||
|
if args.nargs() < 1 {
|
||||||
|
return nil, errors.New("need at least 1 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
var currError errObject
|
||||||
|
defer func() {
|
||||||
|
if errors.As(fnErr, &errBadUsage{}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.nargs() >= 2 && args.identIs(ctx, args.nargs()-2, "finally") {
|
||||||
|
var blockArgs []Object = nil
|
||||||
|
if fnErr != nil {
|
||||||
|
blockArgs = []Object{errObject{err: fnErr}}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := args.evalBlock(ctx, args.nargs()-1, blockArgs, false)
|
||||||
|
if err != nil && fnErr == nil {
|
||||||
|
fnErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
res, err := args.evalBlock(ctx, 0, nil, false)
|
||||||
|
args.shift(1)
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currError = errObject{err: err}
|
||||||
|
|
||||||
|
for args.identIs(ctx, 0, "catch") {
|
||||||
|
args.shift(1)
|
||||||
|
|
||||||
|
if args.nargs() < 1 {
|
||||||
|
return nil, errors.New("need at least 1 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := args.evalBlock(ctx, 0, []Object{currError}, false)
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currError = errObject{err: err}
|
||||||
|
args.shift(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.identIs(ctx, 0, "finally") && args.nargs() > 2 {
|
||||||
|
return nil, tooManyFinallyBlocksError(args.ast.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, currError.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
var (
|
var (
|
||||||
items object
|
items Object
|
||||||
blockIdx int
|
blockIdx int
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
@ -795,16 +850,16 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
last object
|
last Object
|
||||||
breakErr errBreak
|
breakErr errBreak
|
||||||
)
|
)
|
||||||
|
|
||||||
switch t := items.(type) {
|
switch t := items.(type) {
|
||||||
case listable:
|
case Listable:
|
||||||
l := t.Len()
|
l := t.Len()
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
v := t.Index(i)
|
v := t.Index(i)
|
||||||
last, err = args.evalBlock(ctx, blockIdx, []object{v}, true) // TO INCLUDE: the index
|
last, err = args.evalBlock(ctx, blockIdx, []Object{v}, true) // TO INCLUDE: the index
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.As(err, &breakErr) {
|
if errors.As(err, &breakErr) {
|
||||||
if !breakErr.isCont {
|
if !breakErr.isCont {
|
||||||
|
@ -816,8 +871,8 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case hashable:
|
case hashable:
|
||||||
err := t.Each(func(k string, v object) error {
|
err := t.Each(func(k string, v Object) error {
|
||||||
last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true)
|
last, err = args.evalBlock(ctx, blockIdx, []Object{strObject(k), v}, true)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if errors.As(err, &breakErr) {
|
if errors.As(err, &breakErr) {
|
||||||
|
@ -832,25 +887,25 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||||
return last, nil
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func breakBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func breakBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) < 1 {
|
if len(args.args) < 1 {
|
||||||
return nil, errBreak{}
|
return nil, errBreak{}
|
||||||
}
|
}
|
||||||
return nil, errBreak{ret: args.args[0]}
|
return nil, errBreak{ret: args.args[0]}
|
||||||
}
|
}
|
||||||
|
|
||||||
func continueBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func continueBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return nil, errBreak{isCont: true}
|
return nil, errBreak{isCont: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func returnBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) < 1 {
|
if len(args.args) < 1 {
|
||||||
return nil, errReturn{}
|
return nil, errReturn{}
|
||||||
}
|
}
|
||||||
return nil, errReturn{ret: args.args[0]}
|
return nil, errReturn{ret: args.args[0]}
|
||||||
}
|
}
|
||||||
|
|
||||||
func procBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
func procBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
if args.nargs() < 1 {
|
if args.nargs() < 1 {
|
||||||
return nil, errors.New("need at least one arguments")
|
return nil, errors.New("need at least one arguments")
|
||||||
}
|
}
|
||||||
|
@ -894,7 +949,7 @@ func (b procObject) Truthy() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (b procObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
newEc := b.ec.fork()
|
newEc := b.ec.fork()
|
||||||
|
|
||||||
for i, name := range b.block.Names {
|
for i, name := range b.block.Names {
|
||||||
|
|
|
@ -13,6 +13,8 @@ func Strs() ucl.Module {
|
||||||
"to-upper": toUpper,
|
"to-upper": toUpper,
|
||||||
"to-lower": toLower,
|
"to-lower": toLower,
|
||||||
"trim": trim,
|
"trim": trim,
|
||||||
|
"join": join,
|
||||||
|
"has-suffix": hasSuffix,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,3 +45,41 @@ func trim(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
|
||||||
return strings.TrimSpace(s), nil
|
return strings.TrimSpace(s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func join(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
l ucl.Listable
|
||||||
|
joinStr string
|
||||||
|
)
|
||||||
|
if err := args.Bind(&l, &joinStr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if l == nil {
|
||||||
|
return "", nil
|
||||||
|
} else if l.Len() == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
for i := 0; i < l.Len(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(joinStr)
|
||||||
|
}
|
||||||
|
sb.WriteString(l.Index(i).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSuffix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
|
var (
|
||||||
|
s string
|
||||||
|
suffix string
|
||||||
|
)
|
||||||
|
if err := args.Bind(&s, &suffix); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.HasSuffix(s, suffix), nil
|
||||||
|
}
|
||||||
|
|
|
@ -100,3 +100,66 @@ func TestStrs_Trim(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrs_HasSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "suffix 1", eval: `strs:has-suffix "hello" "lo"`, want: true},
|
||||||
|
{desc: "suffix 2", eval: `strs:has-suffix "bellow" "low"`, want: true},
|
||||||
|
{desc: "suffix 3", eval: `strs:has-suffix "goodbye" "lo"`, want: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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_Join(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
eval string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "join 1", eval: `strs:join ["a" "b" "c"] ","`, want: "a,b,c"},
|
||||||
|
{desc: "join 2", eval: `strs:join ["a" "b" "c"] "\n"`, want: "a\nb\nc"},
|
||||||
|
{desc: "join 3", eval: `strs:join ["a" "b" "c"] ""`, want: "abc"},
|
||||||
|
{desc: "join 4", eval: `strs:join [] ","`, want: ""},
|
||||||
|
|
||||||
|
// Hmm, not super happy about this one...
|
||||||
|
{desc: "join 5", eval: `strs:join ["a" "b"] ["what"]`, want: "a[what]b"},
|
||||||
|
|
||||||
|
{desc: "err join 1", eval: `strs:join 123 ","`, wantErr: true},
|
||||||
|
{desc: "err join 2", eval: `strs:join () ","`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
inst := ucl.New(
|
||||||
|
ucl.WithModule(builtins.Strs()),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
ucl/env.go
10
ucl/env.go
|
@ -5,7 +5,7 @@ type evalCtx struct {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||||
|
@ -34,7 +34,7 @@ func (ec *evalCtx) addMacro(name string, inv macroable) {
|
||||||
ec.root.macros[name] = inv
|
ec.root.macros[name] = inv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) setVar(name string, val object) bool {
|
func (ec *evalCtx) setVar(name string, val Object) bool {
|
||||||
if ec == nil || ec.vars == nil {
|
if ec == nil || ec.vars == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -47,18 +47,18 @@ func (ec *evalCtx) setVar(name string, val object) bool {
|
||||||
return ec.parent.setVar(name, val)
|
return ec.parent.setVar(name, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) setOrDefineVar(name string, val object) {
|
func (ec *evalCtx) setOrDefineVar(name string, val Object) {
|
||||||
if ec.setVar(name, val) {
|
if ec.setVar(name, val) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ec.vars == nil {
|
if ec.vars == nil {
|
||||||
ec.vars = make(map[string]object)
|
ec.vars = make(map[string]Object)
|
||||||
}
|
}
|
||||||
ec.vars[name] = val
|
ec.vars[name] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *evalCtx) getVar(name string) (object, bool) {
|
func (ec *evalCtx) getVar(name string) (Object, bool) {
|
||||||
if ec.vars == nil {
|
if ec.vars == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package ucl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorWithPos struct {
|
||||||
|
err error
|
||||||
|
pos lexer.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorWithPos) Error() string {
|
||||||
|
return fmt.Sprintf("%v:%v - %v", e.pos.Line, e.pos.Offset, e.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorWithPos) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type errBadUsage struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBadUsage(msg string) func(pos lexer.Position) error {
|
||||||
|
return func(pos lexer.Position) error {
|
||||||
|
return errorWithPos{err: errBadUsage{msg: msg}, pos: pos}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errBadUsage) Error() string {
|
||||||
|
return "bad usage: " + e.msg
|
||||||
|
}
|
26
ucl/eval.go
26
ucl/eval.go
|
@ -10,7 +10,7 @@ type evaluator struct {
|
||||||
inst *Inst
|
inst *Inst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (lastRes object, err error) {
|
func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (lastRes Object, err error) {
|
||||||
// TODO: push scope?
|
// TODO: push scope?
|
||||||
|
|
||||||
for _, s := range n.Statements {
|
for _, s := range n.Statements {
|
||||||
|
@ -22,11 +22,11 @@ func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (las
|
||||||
return lastRes, nil
|
return lastRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (lastRes object, err error) {
|
func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (lastRes Object, err error) {
|
||||||
return e.evalStatement(ctx, ec, n.Statements)
|
return e.evalStatement(ctx, ec, n.Statements)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) {
|
func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (Object, error) {
|
||||||
if n == nil {
|
if n == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) {
|
func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
||||||
res, err := e.evalCmd(ctx, ec, nil, n.First)
|
res, err := e.evalCmd(ctx, ec, nil, n.First)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -69,7 +69,7 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) {
|
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd) (Object, error) {
|
||||||
switch {
|
switch {
|
||||||
case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
|
case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
|
||||||
name := ast.Name.Arg.Ident.String()
|
name := ast.Name.Arg.Ident.String()
|
||||||
|
@ -105,7 +105,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object,
|
||||||
return nameElem, nil
|
return nameElem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd, cmd invokable) (object, error) {
|
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) {
|
||||||
var (
|
var (
|
||||||
pargs listObject
|
pargs listObject
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*listObject
|
||||||
|
@ -138,7 +138,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o
|
||||||
return cmd.invoke(ctx, invArgs)
|
return cmd.invoke(ctx, invArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pipeArg object, ast *astCmd, cmd macroable) (object, error) {
|
func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pipeArg Object, ast *astCmd, cmd macroable) (Object, error) {
|
||||||
return cmd.invokeMacro(ctx, macroArgs{
|
return cmd.invokeMacro(ctx, macroArgs{
|
||||||
eval: e,
|
eval: e,
|
||||||
ec: ec,
|
ec: ec,
|
||||||
|
@ -148,7 +148,7 @@ func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pip
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object, error) {
|
func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object, error) {
|
||||||
res, err := e.evalArg(ctx, ec, n.Arg)
|
res, err := e.evalArg(ctx, ec, n.Arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -157,7 +157,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dot := range n.DotSuffix {
|
for _, dot := range n.DotSuffix {
|
||||||
var idx object
|
var idx Object
|
||||||
if dot.KeyIdent != nil {
|
if dot.KeyIdent != nil {
|
||||||
idx = strObject(dot.KeyIdent.String())
|
idx = strObject(dot.KeyIdent.String())
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,7 +175,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object,
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
switch {
|
switch {
|
||||||
case n.Literal != nil:
|
case n.Literal != nil:
|
||||||
return e.evalLiteral(ctx, ec, n.Literal)
|
return e.evalLiteral(ctx, ec, n.Literal)
|
||||||
|
@ -200,7 +200,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (objec
|
||||||
return nil, errors.New("unhandled arg type")
|
return nil, errors.New("unhandled arg type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (object, error) {
|
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) {
|
||||||
if loh.EmptyList {
|
if loh.EmptyList {
|
||||||
return listObject{}, nil
|
return listObject{}, nil
|
||||||
} else if loh.EmptyHash {
|
} else if loh.EmptyHash {
|
||||||
|
@ -243,7 +243,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (object, error) {
|
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) {
|
||||||
switch {
|
switch {
|
||||||
case n.Str != nil:
|
case n.Str != nil:
|
||||||
uq, err := strconv.Unquote(*n.Str)
|
uq, err := strconv.Unquote(*n.Str)
|
||||||
|
@ -257,7 +257,7 @@ func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral)
|
||||||
return nil, errors.New("unhandled literal type")
|
return nil, errors.New("unhandled literal type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (object, error) {
|
func (e evaluator) evalSub(ctx context.Context, ec *evalCtx, n *astPipeline) (Object, error) {
|
||||||
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
pipelineRes, err := e.evalPipeline(ctx, ec, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -14,13 +14,13 @@ func EvalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
||||||
return displayResult(ctx, inst, res)
|
return displayResult(ctx, inst, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayResult(ctx context.Context, inst *Inst, res object) (err error) {
|
func displayResult(ctx context.Context, inst *Inst, res Object) (err error) {
|
||||||
switch v := res.(type) {
|
switch v := res.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case listable:
|
case Listable:
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -88,6 +88,7 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||||
|
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
||||||
|
|
||||||
inst := &Inst{
|
inst := &Inst{
|
||||||
out: os.Stdout,
|
out: os.Stdout,
|
||||||
|
@ -133,7 +134,7 @@ func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) {
|
||||||
return goRes, nil
|
return goRes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (inst *Inst) eval(ctx context.Context, expr string) (object, error) {
|
func (inst *Inst) eval(ctx context.Context, expr string) (Object, error) {
|
||||||
ast, err := parse(strings.NewReader(expr))
|
ast, err := parse(strings.NewReader(expr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
78
ucl/objs.go
78
ucl/objs.go
|
@ -11,30 +11,30 @@ import (
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
"github.com/lmika/gopkgs/fp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type object interface {
|
type Object interface {
|
||||||
String() string
|
String() string
|
||||||
Truthy() bool
|
Truthy() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type listable interface {
|
type Listable interface {
|
||||||
Len() int
|
Len() int
|
||||||
Index(i int) object
|
Index(i int) Object
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashable interface {
|
type hashable interface {
|
||||||
Len() int
|
Len() int
|
||||||
Value(k string) object
|
Value(k string) Object
|
||||||
Each(func(k string, v object) error) error
|
Each(func(k string, v Object) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type listObject []object
|
type listObject []Object
|
||||||
|
|
||||||
func (lo *listObject) Append(o object) {
|
func (lo *listObject) Append(o Object) {
|
||||||
*lo = append(*lo, o)
|
*lo = append(*lo, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) String() string {
|
func (s listObject) String() string {
|
||||||
return fmt.Sprintf("%v", []object(s))
|
return fmt.Sprintf("%v", []Object(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) Truthy() bool {
|
func (s listObject) Truthy() bool {
|
||||||
|
@ -45,11 +45,11 @@ func (s listObject) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s listObject) Index(i int) object {
|
func (s listObject) Index(i int) Object {
|
||||||
return s[i]
|
return s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -78,11 +78,11 @@ 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
|
||||||
|
@ -124,7 +124,7 @@ func (b boolObject) Truthy() bool {
|
||||||
return bool(b)
|
return bool(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGoValue(obj object) (interface{}, bool) {
|
func toGoValue(obj Object) (interface{}, bool) {
|
||||||
switch v := obj.(type) {
|
switch v := obj.(type) {
|
||||||
case OpaqueObject:
|
case OpaqueObject:
|
||||||
return v.v, true
|
return v.v, true
|
||||||
|
@ -167,7 +167,7 @@ func toGoValue(obj object) (interface{}, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromGoValue(v any) (object, error) {
|
func fromGoValue(v any) (Object, error) {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
case OpaqueObject:
|
case OpaqueObject:
|
||||||
return t, nil
|
return t, nil
|
||||||
|
@ -184,7 +184,7 @@ func fromGoValue(v any) (object, error) {
|
||||||
return fromGoReflectValue(reflect.ValueOf(v))
|
return fromGoReflectValue(reflect.ValueOf(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromGoReflectValue(resVal reflect.Value) (object, error) {
|
func fromGoReflectValue(resVal reflect.Value) (Object, error) {
|
||||||
if !resVal.IsValid() {
|
if !resVal.IsValid() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ type macroArgs struct {
|
||||||
eval evaluator
|
eval evaluator
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
hasPipe bool
|
hasPipe bool
|
||||||
pipeArg object
|
pipeArg Object
|
||||||
ast *astCmd
|
ast *astCmd
|
||||||
argShift int
|
argShift int
|
||||||
}
|
}
|
||||||
|
@ -259,7 +259,7 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) {
|
||||||
if n >= len(ma.ast.Args[ma.argShift:]) {
|
if n >= len(ma.ast.Args[ma.argShift:]) {
|
||||||
return nil, errors.New("not enough arguments") // FIX
|
return nil, errors.New("not enough arguments") // FIX
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
|
||||||
return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) {
|
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushScope bool) (Object, error) {
|
||||||
obj, err := ma.evalArg(ctx, n)
|
obj, err := ma.evalArg(ctx, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -303,11 +303,21 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
|
||||||
return nil, errors.New("expected an invokable arg")
|
return nil, errors.New("expected an invokable arg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errObject struct{ err error }
|
||||||
|
|
||||||
|
func (eo errObject) String() string {
|
||||||
|
return "error:" + eo.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eo errObject) Truthy() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type invocationArgs struct {
|
type invocationArgs struct {
|
||||||
eval evaluator
|
eval evaluator
|
||||||
inst *Inst
|
inst *Inst
|
||||||
ec *evalCtx
|
ec *evalCtx
|
||||||
args []object
|
args []Object
|
||||||
kwargs map[string]*listObject
|
kwargs map[string]*listObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +370,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
||||||
return nil, errors.New("expected an invokable arg")
|
return nil, errors.New("expected an invokable arg")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ia invocationArgs) fork(args []object) invocationArgs {
|
func (ia invocationArgs) fork(args []Object) invocationArgs {
|
||||||
return invocationArgs{
|
return invocationArgs{
|
||||||
eval: ia.eval,
|
eval: ia.eval,
|
||||||
inst: ia.inst,
|
inst: ia.inst,
|
||||||
|
@ -383,22 +393,22 @@ func (ia invocationArgs) shift(i int) invocationArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokable is an object that can be executed as a command
|
// invokable is an Object that can be executed as a command
|
||||||
type invokable interface {
|
type invokable interface {
|
||||||
invoke(ctx context.Context, args invocationArgs) (object, error)
|
invoke(ctx context.Context, args invocationArgs) (Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroable interface {
|
type macroable interface {
|
||||||
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
invokeMacro(ctx context.Context, args macroArgs) (Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeInvokable interface {
|
type pipeInvokable interface {
|
||||||
invokable
|
invokable
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokableFunc func(ctx context.Context, args invocationArgs) (object, error)
|
type invokableFunc func(ctx context.Context, args invocationArgs) (Object, error)
|
||||||
|
|
||||||
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (i invokableFunc) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return i(ctx, args)
|
return i(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +424,7 @@ func (bo blockObject) Truthy() bool {
|
||||||
return len(bo.block.Statements) > 0
|
return len(bo.block.Statements) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
ec := args.ec.fork()
|
ec := args.ec.fork()
|
||||||
for i, n := range bo.block.Names {
|
for i, n := range bo.block.Names {
|
||||||
if i < len(args.args) {
|
if i < len(args.args) {
|
||||||
|
@ -425,13 +435,13 @@ func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object,
|
||||||
return args.eval.evalBlock(ctx, ec, bo.block)
|
return args.eval.evalBlock(ctx, ec, bo.block)
|
||||||
}
|
}
|
||||||
|
|
||||||
type macroFunc func(ctx context.Context, args macroArgs) (object, error)
|
type macroFunc func(ctx context.Context, args macroArgs) (Object, error)
|
||||||
|
|
||||||
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (object, error) {
|
func (i macroFunc) invokeMacro(ctx context.Context, args macroArgs) (Object, error) {
|
||||||
return i(ctx, args)
|
return i(ctx, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTruthy(obj object) bool {
|
func isTruthy(obj Object) bool {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -467,7 +477,7 @@ func (p listableProxyObject) Len() int {
|
||||||
return p.v.Len()
|
return p.v.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p listableProxyObject) Index(i int) object {
|
func (p listableProxyObject) Index(i int) Object {
|
||||||
e, err := fromGoValue(p.v.Index(i).Interface())
|
e, err := fromGoValue(p.v.Index(i).Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -501,7 +511,7 @@ func (s structProxyObject) Len() int {
|
||||||
return len(s.vf)
|
return len(s.vf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s structProxyObject) Value(k string) object {
|
func (s structProxyObject) Value(k string) Object {
|
||||||
f := s.v.FieldByName(k)
|
f := s.v.FieldByName(k)
|
||||||
if !f.IsValid() {
|
if !f.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
|
@ -521,7 +531,7 @@ func (s structProxyObject) Value(k string) object {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s structProxyObject) Each(fn func(k string, v object) error) error {
|
func (s structProxyObject) Each(fn func(k string, v Object) error) error {
|
||||||
for _, f := range s.vf {
|
for _, f := range s.vf {
|
||||||
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -553,7 +563,7 @@ func (p OpaqueObject) Truthy() bool {
|
||||||
|
|
||||||
type errBreak struct {
|
type errBreak struct {
|
||||||
isCont bool
|
isCont bool
|
||||||
ret object
|
ret Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errBreak) Error() string {
|
func (e errBreak) Error() string {
|
||||||
|
@ -564,7 +574,7 @@ func (e errBreak) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type errReturn struct {
|
type errReturn struct {
|
||||||
ret object
|
ret Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errReturn) Error() string {
|
func (e errReturn) Error() string {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ucl
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -13,15 +14,15 @@ import (
|
||||||
// Builtins used for test
|
// Builtins used for test
|
||||||
func WithTestBuiltin() InstOption {
|
func WithTestBuiltin() InstOption {
|
||||||
return func(i *Inst) {
|
return func(i *Inst) {
|
||||||
i.rootEC.addCmd("firstarg", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("firstarg", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return args.args[0], nil
|
return args.args[0], nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("toUpper", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("toUpper", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return strObject(strings.ToUpper(args.args[0].String())), nil
|
return strObject(strings.ToUpper(args.args[0].String())), nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("sjoin", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return strObject(""), nil
|
return strObject(""), nil
|
||||||
}
|
}
|
||||||
|
@ -36,14 +37,21 @@ func WithTestBuiltin() InstOption {
|
||||||
return strObject(line.String()), nil
|
return strObject(line.String()), nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (object, error) {
|
i.rootEC.addCmd("list", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
return listObject(args.args), nil
|
return listObject(args.args), nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
i.rootEC.addCmd("joinpipe", 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 {
|
||||||
|
return nil, errors.New("an error occurred")
|
||||||
|
}
|
||||||
|
return nil, errors.New(args.args[0].String())
|
||||||
|
}))
|
||||||
|
|
||||||
|
i.rootEC.addCmd("joinpipe", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
|
|
||||||
lst, ok := args.args[0].(listable)
|
lst, ok := args.args[0].(Listable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return strObject(""), nil
|
return strObject(""), nil
|
||||||
}
|
}
|
||||||
|
@ -192,6 +200,201 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Try(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{desc: "single try - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "good"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "good\nafter\n(nil)\n"},
|
||||||
|
{desc: "single try - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
}
|
||||||
|
echo "after"`, wantErr: "bang"},
|
||||||
|
{desc: "try with catch - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "good"
|
||||||
|
} catch {
|
||||||
|
echo "something happened"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "good\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with catch - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch {
|
||||||
|
echo "something happened"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "something happened\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with catch with passed in error - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch { |err|
|
||||||
|
echo (cat "the error was = " $err)
|
||||||
|
}
|
||||||
|
echo "after"`, want: "the error was = error:bang\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with two catch - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "i'm good"
|
||||||
|
} catch {
|
||||||
|
echo "i'm also good"
|
||||||
|
} catch {
|
||||||
|
echo "wow, we made it here"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "i'm good\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with two catch - first unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch {
|
||||||
|
echo "i'm also good"
|
||||||
|
} catch {
|
||||||
|
echo "wow, we made it here"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "i'm also good\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with two catch - both unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch {
|
||||||
|
error "boom"
|
||||||
|
} catch {
|
||||||
|
echo "wow, we made it here"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "wow, we made it here\nafter\n(nil)\n"},
|
||||||
|
{desc: "return value - single try", expr: `
|
||||||
|
set x (try { error "bang" })
|
||||||
|
$x`, wantErr: "bang"},
|
||||||
|
{desc: "return value - single try", expr: `
|
||||||
|
set x (try { error "bang" } catch { |err| $err })
|
||||||
|
$x`, want: "error:bang\n"},
|
||||||
|
{desc: "return value - try and catch - successful", expr: `
|
||||||
|
set x (try { error "bang" } catch { "hello" })
|
||||||
|
$x`, want: "hello\n"},
|
||||||
|
{desc: "return value - try and catch - unsuccessful", expr: `
|
||||||
|
set x (try { error "bang" } catch { error "boom" })
|
||||||
|
$x`, wantErr: "boom"},
|
||||||
|
{desc: "try with finally - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "all good"
|
||||||
|
} finally {
|
||||||
|
echo "always at end"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "all good\nalways at end\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with finally - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} finally {
|
||||||
|
echo "always at end"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "always at end\n", wantErr: "bang"},
|
||||||
|
{desc: "try with catch and finally - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "all good"
|
||||||
|
} catch {
|
||||||
|
echo "was caught"
|
||||||
|
} finally {
|
||||||
|
echo "always at end"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "all good\nalways at end\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with catch and finally - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch {
|
||||||
|
echo "was caught"
|
||||||
|
} finally {
|
||||||
|
echo "always at end"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "was caught\nalways at end\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with catch and finally - catch unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} catch {
|
||||||
|
error "boom"
|
||||||
|
} finally {
|
||||||
|
echo "always at end"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "always at end\n", wantErr: "boom"},
|
||||||
|
{desc: "try with finally - finally result discarded", expr: `
|
||||||
|
set a (try {
|
||||||
|
"return me"
|
||||||
|
} finally {
|
||||||
|
"not met"
|
||||||
|
})
|
||||||
|
echo $a`, want: "return me\n(nil)\n"},
|
||||||
|
{desc: "try with finally - error discarded if try fails result discarded", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} finally {
|
||||||
|
error "kaboom"
|
||||||
|
}`, wantErr: "bang"},
|
||||||
|
{desc: "try with finally - error not discarded if try succeeds", expr: `
|
||||||
|
try {
|
||||||
|
echo "all good"
|
||||||
|
} finally {
|
||||||
|
error "kaboom"
|
||||||
|
}`, want: "all good\n", wantErr: "kaboom"},
|
||||||
|
{desc: "try with finally with error - successful", expr: `
|
||||||
|
try {
|
||||||
|
echo "all good"
|
||||||
|
} finally { |err|
|
||||||
|
echo (cat "the error was " $err)
|
||||||
|
if (eq $err ()) { echo "that's nil" }
|
||||||
|
}
|
||||||
|
echo "after"`, want: "all good\nthe error was \nthat's nil\nafter\n(nil)\n"},
|
||||||
|
{desc: "try with finally - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} finally { |err|
|
||||||
|
echo (cat "the error was " $err)
|
||||||
|
}
|
||||||
|
echo "after"`, want: "the error was error:bang\n", wantErr: "bang"},
|
||||||
|
{desc: "try with too many finallies - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} finally {
|
||||||
|
echo "and do this"
|
||||||
|
} finally { |err|
|
||||||
|
echo (cat "the error was " $err)
|
||||||
|
}
|
||||||
|
echo "after"`, wantErr: "2:4 - bad usage: try needs at most 1 finally"},
|
||||||
|
{desc: "try with finally in catch - unsuccessful", expr: `
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
error "bang"
|
||||||
|
} finally { |err|
|
||||||
|
echo (cat "the error was " $err)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
echo "outer caught"
|
||||||
|
} finally {
|
||||||
|
echo "outer"
|
||||||
|
}
|
||||||
|
echo "after"`, want: "the error was error:bang\nouter caught\nouter\nafter\n(nil)\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)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, tt.wantErr, err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, outW.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuiltins_ForEach(t *testing.T) {
|
func TestBuiltins_ForEach(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
|
@ -85,7 +85,7 @@ type userBuiltin struct {
|
||||||
fn func(ctx context.Context, args CallArgs) (any, error)
|
fn func(ctx context.Context, args CallArgs) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
v, err := u.fn(ctx, CallArgs{args: args})
|
v, err := u.fn(ctx, CallArgs{args: args})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -94,7 +94,7 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
||||||
return fromGoValue(v)
|
return fromGoValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
func (ca CallArgs) bindArg(v interface{}, arg Object) error {
|
||||||
switch t := v.(type) {
|
switch t := v.(type) {
|
||||||
case *interface{}:
|
case *interface{}:
|
||||||
*t, _ = toGoValue(arg)
|
*t, _ = toGoValue(arg)
|
||||||
|
@ -110,6 +110,13 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
||||||
ec: ca.args.ec,
|
ec: ca.args.ec,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
case *Listable:
|
||||||
|
i, ok := arg.(Listable)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("exepected listable")
|
||||||
|
}
|
||||||
|
*t = i
|
||||||
|
return nil
|
||||||
case *string:
|
case *string:
|
||||||
if arg != nil {
|
if arg != nil {
|
||||||
*t = arg.String()
|
*t = arg.String()
|
||||||
|
@ -153,7 +160,7 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func canBindArg(v interface{}, arg object) bool {
|
func canBindArg(v interface{}, arg Object) bool {
|
||||||
switch v.(type) {
|
switch v.(type) {
|
||||||
case *string:
|
case *string:
|
||||||
return true
|
return true
|
||||||
|
@ -233,7 +240,7 @@ type missingHandlerInvokable struct {
|
||||||
handler MissingBuiltinHandler
|
handler MissingBuiltinHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (object, error) {
|
func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
v, err := m.handler(ctx, m.name, CallArgs{args: args})
|
v, err := m.handler(ctx, m.name, CallArgs{args: args})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -265,7 +272,7 @@ func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
|
||||||
inst: i.inst,
|
inst: i.inst,
|
||||||
}
|
}
|
||||||
|
|
||||||
invArgs.args, err = slices.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 {
|
||||||
|
|
Loading…
Reference in New Issue