This commit is contained in:
parent
33cf23b221
commit
23f730fb2f
0
.beads/issues.jsonl
Normal file
0
.beads/issues.jsonl
Normal file
20
ucl/ast.go
20
ucl/ast.go
|
|
@ -63,8 +63,8 @@ func (ai *astIdentNames) String() string {
|
|||
}
|
||||
|
||||
type astElementPair struct {
|
||||
Left astDot `parser:"@@"`
|
||||
Right *astDot `parser:"( COLON @@ )? NL?"`
|
||||
Left astArgOrJustDot `parser:"@@"`
|
||||
Right *astArgOrJustDot `parser:"( COLON @@ )? NL?"`
|
||||
}
|
||||
|
||||
type astListOrHash struct {
|
||||
|
|
@ -97,16 +97,26 @@ type astDotSuffix struct {
|
|||
Pipeline *astPipeline `parser:"| LP @@ RP"`
|
||||
}
|
||||
|
||||
type astDot struct {
|
||||
type astArgOrJustDot struct {
|
||||
Arg *astArgDotSuffix `parser:"@@"`
|
||||
Dot *astJustDotSuffix `parser:"| @@"`
|
||||
}
|
||||
|
||||
type astArgDotSuffix struct {
|
||||
Pos lexer.Position
|
||||
Arg astCmdArg `parser:"@@"`
|
||||
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
||||
}
|
||||
|
||||
type astJustDotSuffix struct {
|
||||
Pos lexer.Position
|
||||
DotSuffix []astDotSuffix `parser:" DOT ( @@ ( DOT @@ )* )?"`
|
||||
}
|
||||
|
||||
type astCmd struct {
|
||||
Pos lexer.Position
|
||||
Name astDot `parser:"@@"`
|
||||
InvokeArgs []astDot `parser:"@@*"`
|
||||
Name astArgOrJustDot `parser:"@@"`
|
||||
InvokeArgs []astArgOrJustDot `parser:"@@*"`
|
||||
}
|
||||
|
||||
type astPipeline struct {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package builtins
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"ucl.lmika.dev/ucl"
|
||||
)
|
||||
|
||||
|
|
@ -11,6 +13,8 @@ func Time() ucl.Module {
|
|||
Name: "time",
|
||||
Builtins: map[string]ucl.BuiltinHandler{
|
||||
"from-unix": timeFromUnix,
|
||||
"to-unix": timeToUnix,
|
||||
"now": timeNow,
|
||||
"sleep": timeSleep,
|
||||
},
|
||||
}
|
||||
|
|
@ -26,6 +30,23 @@ func timeFromUnix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|||
return time.Unix(int64(ux), 0).UTC(), nil
|
||||
}
|
||||
|
||||
func timeToUnix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
tval, err := getTimeArg(&args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return int(tval.Unix()), nil
|
||||
}
|
||||
|
||||
func timeNow(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
if err := args.Bind(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return time.Now().UTC(), nil
|
||||
}
|
||||
|
||||
func timeSleep(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||
var secs int
|
||||
|
||||
|
|
@ -40,3 +61,17 @@ func timeSleep(ctx context.Context, args ucl.CallArgs) (any, error) {
|
|||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func getTimeArg(args *ucl.CallArgs) (time.Time, error) {
|
||||
var t any
|
||||
if err := args.Bind(&t); err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
tval, ok := t.(time.Time)
|
||||
if !ok {
|
||||
return time.Time{}, errors.New("expected time.Time")
|
||||
}
|
||||
|
||||
return tval, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,60 @@ func TestTime_FromUnix(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTime_Now(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
eval string
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "now returns time", eval: `time:now`, wantErr: false},
|
||||
{desc: "now with to-unix and from-unix roundtrip", eval: `time:to-unix (time:now) | time:from-unix`, wantErr: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.Time()),
|
||||
)
|
||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime_ToUnix(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
eval string
|
||||
want any
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "to-unix from epoch", eval: `time:to-unix (time:from-unix 0)`, want: 0},
|
||||
{desc: "to-unix from specific time", eval: `time:to-unix (time:from-unix 1234567890)`, want: 1234567890},
|
||||
{desc: "roundtrip", eval: `time:to-unix (time:from-unix 999999999)`, want: 999999999},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
inst := ucl.New(
|
||||
ucl.WithModule(builtins.Time()),
|
||||
)
|
||||
res, err := inst.EvalString(context.Background(), tt.eval)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTime_Sleep(t *testing.T) {
|
||||
t.Run("should terminate on cancelled context", func(t *testing.T) {
|
||||
st := time.Now()
|
||||
|
|
|
|||
164
ucl/eval.go
164
ucl/eval.go
|
|
@ -103,10 +103,7 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
|
|||
}
|
||||
|
||||
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()
|
||||
|
||||
if name, ok := e.isArgOrDotAnIdent(ast.Name); ok {
|
||||
// Regular command
|
||||
if cmd := ec.lookupInvokable(name); cmd != nil {
|
||||
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
||||
|
|
@ -117,8 +114,8 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object,
|
|||
} else {
|
||||
return nil, errors.New("unknown command: " + name)
|
||||
}
|
||||
case len(ast.InvokeArgs) > 0:
|
||||
nameElem, err := e.evalDot(ctx, ec, ast.Name)
|
||||
} else if len(ast.InvokeArgs) > 0 {
|
||||
nameElem, err := e.evalArgOrDot(ctx, ec, ast.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -131,7 +128,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object,
|
|||
return e.evalInvokable(ctx, ec, currentPipe, ast, inv)
|
||||
}
|
||||
|
||||
nameElem, err := e.evalDot(ctx, ec, ast.Name)
|
||||
nameElem, err := e.evalArgOrDot(ctx, ec, ast.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -142,7 +139,25 @@ func (e evaluator) assignCmd(ctx context.Context, ec *evalCtx, ast *astCmd, toVa
|
|||
if len(ast.InvokeArgs) != 0 {
|
||||
return nil, errors.New("cannot assign to multiple values")
|
||||
}
|
||||
return e.assignDot(ctx, ec, ast.Name, toVal)
|
||||
return e.assignArgOrDot(ctx, ec, ast.Name, toVal)
|
||||
}
|
||||
|
||||
func (e evaluator) isArgOrDotAnIdent(argOrDot astArgOrJustDot) (string, bool) {
|
||||
nonDotArg := argOrDot.Arg
|
||||
if nonDotArg == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if len(nonDotArg.DotSuffix) != 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
ident := nonDotArg.Arg.Ident
|
||||
if ident == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return ident.String(), true
|
||||
}
|
||||
|
||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) {
|
||||
|
|
@ -157,16 +172,16 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O
|
|||
argsPtr.Append(currentPipe)
|
||||
}
|
||||
for _, arg := range ast.InvokeArgs {
|
||||
if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' {
|
||||
if ident, ok := e.isArgOrDotAnIdent(arg); ok && ident[0] == '-' {
|
||||
// Arg switch
|
||||
if kwargs == nil {
|
||||
kwargs = make(map[string]*ListObject)
|
||||
}
|
||||
|
||||
argsPtr = &ListObject{}
|
||||
kwargs[ident.String()[1:]] = argsPtr
|
||||
kwargs[ident[1:]] = argsPtr
|
||||
} else {
|
||||
ae, err := e.evalDot(ctx, ec, arg)
|
||||
ae, err := e.evalArgOrDot(ctx, ec, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -188,7 +203,55 @@ 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) evalArgOrDot(ctx context.Context, ec *evalCtx, n astArgOrJustDot) (Object, error) {
|
||||
if n.Arg != nil {
|
||||
return e.evalArgWithDot(ctx, ec, *n.Arg)
|
||||
} else if n.Dot != nil {
|
||||
return e.evalJustDot(ctx, ec, *n.Dot)
|
||||
}
|
||||
return nil, errors.New("unhandled arg or dot type")
|
||||
}
|
||||
|
||||
func (e evaluator) evalPseudoVar(ctx context.Context, ec *evalCtx, name string) (Object, error) {
|
||||
if v, ok := ec.getPseudoVar(name); ok {
|
||||
return v.get(ctx, name)
|
||||
}
|
||||
|
||||
if mph := e.inst.missingPseudoVarHandler; mph != nil {
|
||||
return mph.get(ctx, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown pseudo-variable: '%v'", name)
|
||||
}
|
||||
|
||||
func (e evaluator) evalJustDot(ctx context.Context, ec *evalCtx, n astJustDotSuffix) (Object, error) {
|
||||
res, err := e.evalPseudoVar(ctx, ec, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(n.DotSuffix) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
for _, dot := range n.DotSuffix {
|
||||
var idx Object
|
||||
if dot.KeyIdent != nil {
|
||||
idx = StringObject(dot.KeyIdent.String())
|
||||
} else {
|
||||
idx, err = e.evalPipeline(ctx, ec, dot.Pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res, err = indexLookup(ctx, res, idx, n.Pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (e evaluator) evalArgWithDot(ctx context.Context, ec *evalCtx, n astArgDotSuffix) (Object, error) {
|
||||
res, err := e.evalArg(ctx, ec, n.Arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -215,7 +278,16 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object,
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (e evaluator) assignDot(ctx context.Context, ec *evalCtx, n astDot, toVal Object) (Object, error) {
|
||||
func (e evaluator) assignArgOrDot(ctx context.Context, ec *evalCtx, n astArgOrJustDot, toVal Object) (Object, error) {
|
||||
if n.Arg != nil {
|
||||
return e.assignArgWithDot(ctx, ec, *n.Arg, toVal)
|
||||
} else if n.Dot != nil {
|
||||
return e.assignJustDot(ctx, ec, *n.Dot, toVal)
|
||||
}
|
||||
return nil, errors.New("unhandled arg or dot type")
|
||||
}
|
||||
|
||||
func (e evaluator) assignArgWithDot(ctx context.Context, ec *evalCtx, n astArgDotSuffix, toVal Object) (Object, error) {
|
||||
if len(n.DotSuffix) == 0 {
|
||||
return e.assignArg(ctx, ec, n.Arg, toVal)
|
||||
}
|
||||
|
|
@ -251,6 +323,56 @@ func (e evaluator) assignDot(ctx context.Context, ec *evalCtx, n astDot, toVal O
|
|||
return val, nil
|
||||
}
|
||||
|
||||
func (e evaluator) assignJustDot(ctx context.Context, ec *evalCtx, n astJustDotSuffix, toVal Object) (Object, error) {
|
||||
if len(n.DotSuffix) == 0 {
|
||||
pvar, ok := ec.getPseudoVar(".")
|
||||
if ok {
|
||||
if err := pvar.set(ctx, ".", toVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toVal, nil
|
||||
}
|
||||
|
||||
if pvar := e.inst.missingPseudoVarHandler; pvar != nil {
|
||||
if err := pvar.set(ctx, ".", toVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toVal, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown pseudo-variable: .")
|
||||
}
|
||||
|
||||
val, err := e.evalPseudoVar(ctx, ec, ".")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, dot := range n.DotSuffix {
|
||||
isLast := i == len(n.DotSuffix)-1
|
||||
|
||||
var idx Object
|
||||
if dot.KeyIdent != nil {
|
||||
idx = StringObject(dot.KeyIdent.String())
|
||||
} else {
|
||||
idx, err = e.evalPipeline(ctx, ec, dot.Pipeline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if isLast {
|
||||
val, err = indexAssign(ctx, val, idx, toVal, n.Pos)
|
||||
} else {
|
||||
val, err = indexLookup(ctx, val, idx, n.Pos)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (e evaluator) evalArgForDotAssign(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) {
|
||||
// Special case for dot assigns of 'a.b = c' where a is actually a var deref (i.e. $a)
|
||||
// which is unnecessary for assignments. Likewise, having '$a.b = c' should be dissallowed
|
||||
|
|
@ -279,15 +401,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
|
|||
}
|
||||
return nil, nil
|
||||
case n.PseudoVar != nil:
|
||||
if v, ok := ec.getPseudoVar(*n.PseudoVar); ok {
|
||||
return v.get(ctx, *n.PseudoVar)
|
||||
}
|
||||
|
||||
if mph := e.inst.missingPseudoVarHandler; mph != nil {
|
||||
return mph.get(ctx, *n.PseudoVar)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown pseudo-variable: " + *n.PseudoVar)
|
||||
return e.evalPseudoVar(ctx, ec, *n.PseudoVar)
|
||||
case n.MaybeSub != nil:
|
||||
sub := n.MaybeSub.Sub
|
||||
if sub == nil {
|
||||
|
|
@ -351,12 +465,12 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
|||
return nil, errors.New("miss-match of lists and hash")
|
||||
}
|
||||
|
||||
n, err := e.evalDot(ctx, ec, el.Left)
|
||||
n, err := e.evalArgOrDot(ctx, ec, el.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := e.evalDot(ctx, ec, *el.Right)
|
||||
v, err := e.evalArgOrDot(ctx, ec, *el.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -371,7 +485,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
|||
if el.Right != nil {
|
||||
return nil, errors.New("miss-match of lists and hash")
|
||||
}
|
||||
v, err := e.evalDot(ctx, ec, el.Left)
|
||||
v, err := e.evalArgOrDot(ctx, ec, el.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -307,6 +307,50 @@ func TestInst_SetPseudoVar(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInst_ParseDot(t *testing.T) {
|
||||
strPVal := &stringPseudoVar{str: "this is dot"}
|
||||
slicePVal := &anyPseudoVar{val: []string{"item1", "item2"}}
|
||||
mapPVal := &anyPseudoVar{val: map[string]string{"key1": "value1", "key2": "value2"}}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
pvalHandler ucl.PseudoVarHandler
|
||||
expr string
|
||||
wantRes any
|
||||
wantBarVar string
|
||||
wantErr bool
|
||||
}{
|
||||
{desc: "read dot 1", pvalHandler: strPVal, expr: `.`, wantRes: "this is dot"},
|
||||
{desc: "read dot 2", pvalHandler: strPVal, expr: `toUpper .`, wantRes: "THIS IS DOT"},
|
||||
{desc: "read dot 3", pvalHandler: strPVal, expr: `. | toUpper`, wantRes: "THIS IS DOT"},
|
||||
{desc: "read dot 4", pvalHandler: slicePVal, expr: `.(0)`, wantRes: "item1"},
|
||||
{desc: "read dot 5", pvalHandler: slicePVal, expr: `.(1)`, wantRes: "item2"},
|
||||
{desc: "read dot 6", pvalHandler: slicePVal, expr: `. | len`, wantRes: 2},
|
||||
{desc: "read dot 7", pvalHandler: mapPVal, expr: `.key1`, wantRes: "value1"},
|
||||
{desc: "read dot 8", pvalHandler: mapPVal, expr: `.key2`, wantRes: "value2"},
|
||||
|
||||
// Always keep last as this will modify the pvals
|
||||
{desc: "write dot 1", pvalHandler: strPVal, expr: `. = "hello" ; .`, wantRes: "hello"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
inst := ucl.New(ucl.WithTestBuiltin())
|
||||
inst.SetPseudoVar(".", tt.pvalHandler)
|
||||
|
||||
res, err := inst.EvalString(t.Context(), tt.expr)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantRes, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var parseComments1 = `
|
||||
proc lookup { |file|
|
||||
foreach { |toks|
|
||||
|
|
@ -359,6 +403,19 @@ func (s *stringPseudoVar) Set(ctx context.Context, v any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type anyPseudoVar struct {
|
||||
val any
|
||||
}
|
||||
|
||||
func (s *anyPseudoVar) Get(ctx context.Context) (any, error) {
|
||||
return s.val, nil
|
||||
}
|
||||
|
||||
func (s *anyPseudoVar) Set(ctx context.Context, v any) error {
|
||||
s.val = v
|
||||
return nil
|
||||
}
|
||||
|
||||
type missingPseudoVarType struct{}
|
||||
|
||||
func (missingPseudoVarType) Get(ctx context.Context, name string) (any, error) {
|
||||
|
|
|
|||
81
ucl/objs.go
81
ucl/objs.go
|
|
@ -308,6 +308,11 @@ func fromGoReflectValue(resVal reflect.Value) (Object, error) {
|
|||
switch resVal.Kind() {
|
||||
case reflect.Slice:
|
||||
return listableProxyObject{v: resVal, orig: resVal}, nil
|
||||
case reflect.Map:
|
||||
if resVal.Type().Key().Kind() == reflect.String {
|
||||
return mapProxyObject{v: resVal, orig: resVal}, nil
|
||||
}
|
||||
return nil, errors.New("map keys must be strings")
|
||||
case reflect.Struct:
|
||||
return newStructProxyObject(resVal, resVal), nil
|
||||
case reflect.Pointer:
|
||||
|
|
@ -346,11 +351,16 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo
|
|||
return false
|
||||
}
|
||||
|
||||
if len(ma.ast.InvokeArgs[ma.argShift+n].DotSuffix) != 0 {
|
||||
arg := ma.ast.InvokeArgs[ma.argShift+n].Arg
|
||||
if arg == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
lit := ma.ast.InvokeArgs[ma.argShift+n].Arg.Ident
|
||||
if len(arg.DotSuffix) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
lit := arg.Arg.Ident
|
||||
if lit == nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -363,11 +373,16 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
if len(ma.ast.InvokeArgs[ma.argShift].DotSuffix) != 0 {
|
||||
arg := ma.ast.InvokeArgs[ma.argShift].Arg
|
||||
if arg == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
lit := ma.ast.InvokeArgs[ma.argShift].Arg.Ident
|
||||
if len(arg.DotSuffix) != 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
lit := arg.Arg.Ident
|
||||
if lit != nil {
|
||||
ma.argShift += 1
|
||||
return lit.String(), true
|
||||
|
|
@ -380,7 +395,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) {
|
|||
return nil, errors.New("not enough arguments") // FIX
|
||||
}
|
||||
|
||||
return ma.eval.evalDot(ctx, ma.ec, ma.ast.InvokeArgs[ma.argShift+n])
|
||||
return ma.eval.evalArgOrDot(ctx, ma.ec, ma.ast.InvokeArgs[ma.argShift+n])
|
||||
}
|
||||
|
||||
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushScope bool) (Object, error) {
|
||||
|
|
@ -670,6 +685,62 @@ func (s structProxyObject) Each(fn func(k string, v Object) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type mapProxyObject struct {
|
||||
v reflect.Value
|
||||
orig reflect.Value
|
||||
}
|
||||
|
||||
func newMapProxyObject(v reflect.Value, orig reflect.Value) structProxyObject {
|
||||
return structProxyObject{
|
||||
v: v,
|
||||
orig: orig,
|
||||
}
|
||||
}
|
||||
|
||||
func (s mapProxyObject) String() string {
|
||||
return fmt.Sprintf("mapProxyObject{%v}", s.v.Type())
|
||||
}
|
||||
|
||||
func (p mapProxyObject) Truthy() bool {
|
||||
return p.v.Len() > 0
|
||||
}
|
||||
|
||||
func (p mapProxyObject) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p mapProxyObject) Value(k string) Object {
|
||||
val := p.v.MapIndex(reflect.ValueOf(k))
|
||||
if !val.IsValid() || val.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
e, err := fromGoValue(val.Interface())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (s mapProxyObject) Each(fn func(k string, v Object) error) error {
|
||||
for _, k := range s.v.MapKeys() {
|
||||
val := k.MapIndex(reflect.ValueOf(k))
|
||||
if !val.IsValid() || val.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := fromGoValue(val.Interface())
|
||||
if err != nil {
|
||||
v = nil
|
||||
}
|
||||
|
||||
if err := fn(k.String(), v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OpaqueObject struct {
|
||||
v any
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue