Compare commits

..

5 Commits

Author SHA1 Message Date
Leon Mika 9b3b8287fa Added strs:has-suffix
Build / build (push) Successful in 2m2s Details
2024-10-22 11:17:15 +11:00
Leon Mika c4e4a0977b Added strs:split
Build / build (push) Successful in 1m55s Details
This involved exposing the internal object types, which I think was about time
2024-10-22 09:14:24 +11:00
Leon Mika 2f54a9311e Finished implementing try catch
Build / build (push) Successful in 2m5s Details
2024-10-21 22:00:06 +11:00
lmika 5b913266e9 Merge pull request 'issue-2: added some more string functions' (#6) from feature/issue-2 into main
Build / build (push) Successful in 2m6s Details
Added the functions strs:to-upper, strs:to-lower, strs:trim
2024-10-21 10:01:48 +00:00
Leon Mika bb78a39cdb Started working on 'try' statement
Build / build (push) Successful in 2m8s Details
2024-10-21 15:21:28 +11:00
12 changed files with 550 additions and 133 deletions

View File

@ -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:"@@*"`
} }

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
} }

37
ucl/errors.go Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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 {