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 {
|
||||
Pos lexer.Position
|
||||
Name astDot `parser:"@@"`
|
||||
Args []astDot `parser:"@@*"`
|
||||
}
|
||||
|
|
183
ucl/builtins.go
183
ucl/builtins.go
|
@ -8,7 +8,7 @@ import (
|
|||
"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 _, err := fmt.Fprintln(args.inst.Out()); err != nil {
|
||||
return nil, err
|
||||
|
@ -29,7 +29,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return intObject(0), nil
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return intObject(0), nil
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return intObject(1), nil
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return intObject(1), nil
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return intObject(0), nil
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
|
||||
var errObjectsNotEqual = errors.New("objects not equal")
|
||||
|
||||
func objectsEqual(l, r object) bool {
|
||||
func objectsEqual(l, r Object) bool {
|
||||
if l == nil || r == nil {
|
||||
return l == nil && r == nil
|
||||
}
|
||||
|
@ -318,8 +318,8 @@ func objectsEqual(l, r object) bool {
|
|||
if rv, ok := r.(boolObject); ok {
|
||||
return lv == rv
|
||||
}
|
||||
case listable:
|
||||
rv, ok := r.(listable)
|
||||
case Listable:
|
||||
rv, ok := r.(Listable)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ func objectsEqual(l, r object) bool {
|
|||
if lv.Len() != rv.Len() {
|
||||
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)
|
||||
if rkv == nil {
|
||||
return errObjectsNotEqual
|
||||
|
@ -365,7 +365,7 @@ func objectsEqual(l, r object) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func objectsLessThan(l, r object) (bool, error) {
|
||||
func objectsLessThan(l, r Object) (bool, error) {
|
||||
switch lv := l.(type) {
|
||||
case strObject:
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -391,7 +391,7 @@ func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -419,7 +419,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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
|
||||
|
||||
for _, a := range args.args {
|
||||
|
@ -432,7 +432,7 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -445,7 +445,7 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -453,7 +453,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
switch v := args.args[0].(type) {
|
||||
case strObject:
|
||||
return intObject(len(string(v))), nil
|
||||
case listable:
|
||||
case Listable:
|
||||
return intObject(v.Len()), nil
|
||||
case hashable:
|
||||
return intObject(v.Len()), nil
|
||||
|
@ -462,9 +462,9 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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) {
|
||||
case listable:
|
||||
case Listable:
|
||||
intIdx, ok := elem.(intObject)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
|
@ -483,7 +483,7 @@ func indexLookup(ctx context.Context, obj, elem object) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -509,7 +509,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
switch v := val.(type) {
|
||||
case hashable:
|
||||
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))
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
@ -521,7 +521,7 @@ func keysBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -532,12 +532,12 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
case Listable:
|
||||
l := t.Len()
|
||||
newList := listObject{}
|
||||
for i := 0; i < l; 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -548,7 +548,7 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -559,12 +559,12 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
case Listable:
|
||||
l := t.Len()
|
||||
newList := listObject{}
|
||||
for i := 0; i < l; 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 {
|
||||
return nil, err
|
||||
} else if m.Truthy() {
|
||||
|
@ -574,8 +574,8 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return newList, nil
|
||||
case hashable:
|
||||
newHash := hashObject{}
|
||||
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 err := t.Each(func(k string, v Object) error {
|
||||
if m, err := inv.invoke(ctx, args.fork([]Object{strObject(k), v})); err != nil {
|
||||
return err
|
||||
} else if m.Truthy() {
|
||||
newHash[k] = v
|
||||
|
@ -589,14 +589,14 @@ func filterBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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
|
||||
if err = args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
accum object
|
||||
accum Object
|
||||
setFirst bool
|
||||
block invokable
|
||||
)
|
||||
|
@ -615,7 +615,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
case Listable:
|
||||
l := t.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
v := t.Index(i)
|
||||
|
@ -625,7 +625,7 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
newAccum, err := block.invoke(ctx, args.fork([]object{v, accum}))
|
||||
newAccum, err := block.invoke(ctx, args.fork([]Object{v, accum}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -635,8 +635,8 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
return accum, nil
|
||||
case hashable:
|
||||
// TODO: should raise error?
|
||||
if err := t.Each(func(k string, v object) error {
|
||||
newAccum, err := block.invoke(ctx, args.fork([]object{strObject(k), v, accum}))
|
||||
if err := t.Each(func(k string, v Object) error {
|
||||
newAccum, err := block.invoke(ctx, args.fork([]Object{strObject(k), v, accum}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -650,13 +650,13 @@ func reduceBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch t := args.args[0].(type) {
|
||||
case listable:
|
||||
case Listable:
|
||||
if t.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -692,7 +692,7 @@ func (s seqObject) Len() int {
|
|||
return l
|
||||
}
|
||||
|
||||
func (s seqObject) Index(i int) object {
|
||||
func (s seqObject) Index(i int) Object {
|
||||
l := s.Len()
|
||||
if i < 0 || i > l {
|
||||
return nil
|
||||
|
@ -703,7 +703,7 @@ func (s seqObject) Index(i int) object {
|
|||
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
|
||||
if inc, ok := args.kwargs["inc"]; ok {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
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 (
|
||||
items object
|
||||
items Object
|
||||
blockIdx int
|
||||
err error
|
||||
)
|
||||
|
@ -795,16 +850,16 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
}
|
||||
|
||||
var (
|
||||
last object
|
||||
last Object
|
||||
breakErr errBreak
|
||||
)
|
||||
|
||||
switch t := items.(type) {
|
||||
case listable:
|
||||
case Listable:
|
||||
l := t.Len()
|
||||
for i := 0; i < l; 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 errors.As(err, &breakErr) {
|
||||
if !breakErr.isCont {
|
||||
|
@ -816,8 +871,8 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
}
|
||||
}
|
||||
case hashable:
|
||||
err := t.Each(func(k string, v object) error {
|
||||
last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true)
|
||||
err := t.Each(func(k string, v Object) error {
|
||||
last, err = args.evalBlock(ctx, blockIdx, []Object{strObject(k), v}, true)
|
||||
return err
|
||||
})
|
||||
if errors.As(err, &breakErr) {
|
||||
|
@ -832,25 +887,25 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
|||
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 {
|
||||
return nil, errBreak{}
|
||||
}
|
||||
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}
|
||||
}
|
||||
|
||||
func returnBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||
func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if len(args.args) < 1 {
|
||||
return nil, errReturn{}
|
||||
}
|
||||
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 {
|
||||
return nil, errors.New("need at least one arguments")
|
||||
}
|
||||
|
@ -894,7 +949,7 @@ func (b procObject) Truthy() bool {
|
|||
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()
|
||||
|
||||
for i, name := range b.block.Names {
|
||||
|
|
|
@ -10,9 +10,11 @@ func Strs() ucl.Module {
|
|||
return ucl.Module{
|
||||
Name: "strs",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"to-upper": toUpper,
|
||||
"to-lower": toLower,
|
||||
"trim": trim,
|
||||
"to-upper": toUpper,
|
||||
"to-lower": toLower,
|
||||
"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
|
||||
}
|
||||
|
||||
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
|
||||
commands map[string]invokable
|
||||
macros map[string]macroable
|
||||
vars map[string]object
|
||||
vars map[string]Object
|
||||
}
|
||||
|
||||
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||
|
@ -34,7 +34,7 @@ func (ec *evalCtx) addMacro(name string, inv macroable) {
|
|||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -47,18 +47,18 @@ func (ec *evalCtx) setVar(name string, val object) bool {
|
|||
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) {
|
||||
return
|
||||
}
|
||||
|
||||
if ec.vars == nil {
|
||||
ec.vars = make(map[string]object)
|
||||
ec.vars = make(map[string]Object)
|
||||
}
|
||||
ec.vars[name] = val
|
||||
}
|
||||
|
||||
func (ec *evalCtx) getVar(name string) (object, bool) {
|
||||
func (ec *evalCtx) getVar(name string) (Object, bool) {
|
||||
if ec.vars == nil {
|
||||
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
|
||||
}
|
||||
|
||||
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?
|
||||
|
||||
for _, s := range n.Statements {
|
||||
|
@ -22,11 +22,11 @@ func (e evaluator) evalBlock(ctx context.Context, ec *evalCtx, n *astBlock) (las
|
|||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStateme
|
|||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,7 +69,7 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
|
|||
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 {
|
||||
case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
|
||||
name := ast.Name.Arg.Ident.String()
|
||||
|
@ -105,7 +105,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object,
|
|||
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 (
|
||||
pargs 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)
|
||||
}
|
||||
|
||||
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{
|
||||
eval: e,
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -157,7 +157,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object,
|
|||
}
|
||||
|
||||
for _, dot := range n.DotSuffix {
|
||||
var idx object
|
||||
var idx Object
|
||||
if dot.KeyIdent != nil {
|
||||
idx = strObject(dot.KeyIdent.String())
|
||||
} else {
|
||||
|
@ -175,7 +175,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object,
|
|||
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 {
|
||||
case n.Literal != nil:
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
return listObject{}, nil
|
||||
} else if loh.EmptyHash {
|
||||
|
@ -243,7 +243,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
|||
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 {
|
||||
case n.Str != nil:
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -14,13 +14,13 @@ func EvalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
|
|||
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) {
|
||||
case nil:
|
||||
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
|
||||
return err
|
||||
}
|
||||
case listable:
|
||||
case Listable:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||
return err
|
||||
|
|
|
@ -88,6 +88,7 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addMacro("if", macroFunc(ifBuiltin))
|
||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||
rootEC.addMacro("try", macroFunc(tryBuiltin))
|
||||
|
||||
inst := &Inst{
|
||||
out: os.Stdout,
|
||||
|
@ -133,7 +134,7 @@ func (inst *Inst) Eval(ctx context.Context, expr string) (any, error) {
|
|||
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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
78
ucl/objs.go
78
ucl/objs.go
|
@ -11,30 +11,30 @@ import (
|
|||
"github.com/lmika/gopkgs/fp/slices"
|
||||
)
|
||||
|
||||
type object interface {
|
||||
type Object interface {
|
||||
String() string
|
||||
Truthy() bool
|
||||
}
|
||||
|
||||
type listable interface {
|
||||
type Listable interface {
|
||||
Len() int
|
||||
Index(i int) object
|
||||
Index(i int) Object
|
||||
}
|
||||
|
||||
type hashable interface {
|
||||
Len() int
|
||||
Value(k string) object
|
||||
Each(func(k string, v object) error) error
|
||||
Value(k string) Object
|
||||
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)
|
||||
}
|
||||
|
||||
func (s listObject) String() string {
|
||||
return fmt.Sprintf("%v", []object(s))
|
||||
return fmt.Sprintf("%v", []Object(s))
|
||||
}
|
||||
|
||||
func (s listObject) Truthy() bool {
|
||||
|
@ -45,11 +45,11 @@ func (s listObject) Len() int {
|
|||
return len(s)
|
||||
}
|
||||
|
||||
func (s listObject) Index(i int) object {
|
||||
func (s listObject) Index(i int) Object {
|
||||
return s[i]
|
||||
}
|
||||
|
||||
type hashObject map[string]object
|
||||
type hashObject map[string]Object
|
||||
|
||||
func (s hashObject) String() string {
|
||||
if len(s) == 0 {
|
||||
|
@ -78,11 +78,11 @@ func (s hashObject) Len() int {
|
|||
return len(s)
|
||||
}
|
||||
|
||||
func (s hashObject) Value(k string) object {
|
||||
func (s hashObject) Value(k string) Object {
|
||||
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 {
|
||||
if err := fn(k, v); err != nil {
|
||||
return err
|
||||
|
@ -124,7 +124,7 @@ func (b boolObject) Truthy() bool {
|
|||
return bool(b)
|
||||
}
|
||||
|
||||
func toGoValue(obj object) (interface{}, bool) {
|
||||
func toGoValue(obj Object) (interface{}, bool) {
|
||||
switch v := obj.(type) {
|
||||
case OpaqueObject:
|
||||
return v.v, true
|
||||
|
@ -167,7 +167,7 @@ func toGoValue(obj object) (interface{}, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
func fromGoValue(v any) (object, error) {
|
||||
func fromGoValue(v any) (Object, error) {
|
||||
switch t := v.(type) {
|
||||
case OpaqueObject:
|
||||
return t, nil
|
||||
|
@ -184,7 +184,7 @@ func fromGoValue(v any) (object, error) {
|
|||
return fromGoReflectValue(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
func fromGoReflectValue(resVal reflect.Value) (object, error) {
|
||||
func fromGoReflectValue(resVal reflect.Value) (Object, error) {
|
||||
if !resVal.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ type macroArgs struct {
|
|||
eval evaluator
|
||||
ec *evalCtx
|
||||
hasPipe bool
|
||||
pipeArg object
|
||||
pipeArg Object
|
||||
ast *astCmd
|
||||
argShift int
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
|||
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:]) {
|
||||
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])
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
eval evaluator
|
||||
inst *Inst
|
||||
ec *evalCtx
|
||||
args []object
|
||||
args []Object
|
||||
kwargs map[string]*listObject
|
||||
}
|
||||
|
||||
|
@ -360,7 +370,7 @@ func (ia invocationArgs) invokableArg(i int) (invokable, error) {
|
|||
return nil, errors.New("expected an invokable arg")
|
||||
}
|
||||
|
||||
func (ia invocationArgs) fork(args []object) invocationArgs {
|
||||
func (ia invocationArgs) fork(args []Object) invocationArgs {
|
||||
return invocationArgs{
|
||||
eval: ia.eval,
|
||||
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 {
|
||||
invoke(ctx context.Context, args invocationArgs) (object, error)
|
||||
invoke(ctx context.Context, args invocationArgs) (Object, error)
|
||||
}
|
||||
|
||||
type macroable interface {
|
||||
invokeMacro(ctx context.Context, args macroArgs) (object, error)
|
||||
invokeMacro(ctx context.Context, args macroArgs) (Object, error)
|
||||
}
|
||||
|
||||
type pipeInvokable interface {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -414,7 +424,7 @@ func (bo blockObject) Truthy() bool {
|
|||
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()
|
||||
for i, n := range bo.block.Names {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func isTruthy(obj object) bool {
|
||||
func isTruthy(obj Object) bool {
|
||||
if obj == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -467,7 +477,7 @@ func (p listableProxyObject) Len() int {
|
|||
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())
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -501,7 +511,7 @@ func (s structProxyObject) Len() int {
|
|||
return len(s.vf)
|
||||
}
|
||||
|
||||
func (s structProxyObject) Value(k string) object {
|
||||
func (s structProxyObject) Value(k string) Object {
|
||||
f := s.v.FieldByName(k)
|
||||
if !f.IsValid() {
|
||||
return nil
|
||||
|
@ -521,7 +531,7 @@ func (s structProxyObject) Value(k string) object {
|
|||
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 {
|
||||
v, err := fromGoValue(s.v.FieldByName(f.Name).Interface())
|
||||
if err != nil {
|
||||
|
@ -553,7 +563,7 @@ func (p OpaqueObject) Truthy() bool {
|
|||
|
||||
type errBreak struct {
|
||||
isCont bool
|
||||
ret object
|
||||
ret Object
|
||||
}
|
||||
|
||||
func (e errBreak) Error() string {
|
||||
|
@ -564,7 +574,7 @@ func (e errBreak) Error() string {
|
|||
}
|
||||
|
||||
type errReturn struct {
|
||||
ret object
|
||||
ret Object
|
||||
}
|
||||
|
||||
func (e errReturn) Error() string {
|
||||
|
|
|
@ -3,6 +3,7 @@ package ucl
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -13,15 +14,15 @@ import (
|
|||
// Builtins used for test
|
||||
func WithTestBuiltin() InstOption {
|
||||
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
|
||||
}))
|
||||
|
||||
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
|
||||
}))
|
||||
|
||||
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 {
|
||||
return strObject(""), nil
|
||||
}
|
||||
|
@ -36,14 +37,21 @@ func WithTestBuiltin() InstOption {
|
|||
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
|
||||
}))
|
||||
|
||||
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{}
|
||||
|
||||
lst, ok := args.args[0].(listable)
|
||||
lst, ok := args.args[0].(Listable)
|
||||
if !ok {
|
||||
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) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
|
|
|
@ -85,7 +85,7 @@ type userBuiltin struct {
|
|||
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})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -94,7 +94,7 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
|
|||
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) {
|
||||
case *interface{}:
|
||||
*t, _ = toGoValue(arg)
|
||||
|
@ -110,6 +110,13 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
|||
ec: ca.args.ec,
|
||||
}
|
||||
return nil
|
||||
case *Listable:
|
||||
i, ok := arg.(Listable)
|
||||
if !ok {
|
||||
return errors.New("exepected listable")
|
||||
}
|
||||
*t = i
|
||||
return nil
|
||||
case *string:
|
||||
if arg != nil {
|
||||
*t = arg.String()
|
||||
|
@ -153,7 +160,7 @@ func (ca CallArgs) bindArg(v interface{}, arg object) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func canBindArg(v interface{}, arg object) bool {
|
||||
func canBindArg(v interface{}, arg Object) bool {
|
||||
switch v.(type) {
|
||||
case *string:
|
||||
return true
|
||||
|
@ -233,7 +240,7 @@ type missingHandlerInvokable struct {
|
|||
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})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -265,7 +272,7 @@ func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
|
|||
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)
|
||||
})
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue