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 {
|
type astElementPair struct {
|
||||||
Left astDot `parser:"@@"`
|
Left astArgOrJustDot `parser:"@@"`
|
||||||
Right *astDot `parser:"( COLON @@ )? NL?"`
|
Right *astArgOrJustDot `parser:"( COLON @@ )? NL?"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type astListOrHash struct {
|
type astListOrHash struct {
|
||||||
|
|
@ -97,16 +97,26 @@ type astDotSuffix struct {
|
||||||
Pipeline *astPipeline `parser:"| LP @@ RP"`
|
Pipeline *astPipeline `parser:"| LP @@ RP"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type astDot struct {
|
type astArgOrJustDot struct {
|
||||||
|
Arg *astArgDotSuffix `parser:"@@"`
|
||||||
|
Dot *astJustDotSuffix `parser:"| @@"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type astArgDotSuffix struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
Arg astCmdArg `parser:"@@"`
|
Arg astCmdArg `parser:"@@"`
|
||||||
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type astJustDotSuffix struct {
|
||||||
|
Pos lexer.Position
|
||||||
|
DotSuffix []astDotSuffix `parser:" DOT ( @@ ( DOT @@ )* )?"`
|
||||||
|
}
|
||||||
|
|
||||||
type astCmd struct {
|
type astCmd struct {
|
||||||
Pos lexer.Position
|
Pos lexer.Position
|
||||||
Name astDot `parser:"@@"`
|
Name astArgOrJustDot `parser:"@@"`
|
||||||
InvokeArgs []astDot `parser:"@@*"`
|
InvokeArgs []astArgOrJustDot `parser:"@@*"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type astPipeline struct {
|
type astPipeline struct {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package builtins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,6 +13,8 @@ func Time() ucl.Module {
|
||||||
Name: "time",
|
Name: "time",
|
||||||
Builtins: map[string]ucl.BuiltinHandler{
|
Builtins: map[string]ucl.BuiltinHandler{
|
||||||
"from-unix": timeFromUnix,
|
"from-unix": timeFromUnix,
|
||||||
|
"to-unix": timeToUnix,
|
||||||
|
"now": timeNow,
|
||||||
"sleep": timeSleep,
|
"sleep": timeSleep,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +30,23 @@ func timeFromUnix(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return time.Unix(int64(ux), 0).UTC(), nil
|
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) {
|
func timeSleep(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
var secs int
|
var secs int
|
||||||
|
|
||||||
|
|
@ -40,3 +61,17 @@ func timeSleep(ctx context.Context, args ucl.CallArgs) (any, error) {
|
||||||
return nil, ctx.Err()
|
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) {
|
func TestTime_Sleep(t *testing.T) {
|
||||||
t.Run("should terminate on cancelled context", func(t *testing.T) {
|
t.Run("should terminate on cancelled context", func(t *testing.T) {
|
||||||
st := time.Now()
|
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) {
|
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd) (Object, error) {
|
||||||
switch {
|
if name, ok := e.isArgOrDotAnIdent(ast.Name); ok {
|
||||||
case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
|
|
||||||
name := ast.Name.Arg.Ident.String()
|
|
||||||
|
|
||||||
// Regular command
|
// Regular command
|
||||||
if cmd := ec.lookupInvokable(name); cmd != nil {
|
if cmd := ec.lookupInvokable(name); cmd != nil {
|
||||||
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
return e.evalInvokable(ctx, ec, currentPipe, ast, cmd)
|
||||||
|
|
@ -117,8 +114,8 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object,
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("unknown command: " + name)
|
return nil, errors.New("unknown command: " + name)
|
||||||
}
|
}
|
||||||
case len(ast.InvokeArgs) > 0:
|
} else if len(ast.InvokeArgs) > 0 {
|
||||||
nameElem, err := e.evalDot(ctx, ec, ast.Name)
|
nameElem, err := e.evalArgOrDot(ctx, ec, ast.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +139,25 @@ func (e evaluator) assignCmd(ctx context.Context, ec *evalCtx, ast *astCmd, toVa
|
||||||
if len(ast.InvokeArgs) != 0 {
|
if len(ast.InvokeArgs) != 0 {
|
||||||
return nil, errors.New("cannot assign to multiple values")
|
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) {
|
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)
|
argsPtr.Append(currentPipe)
|
||||||
}
|
}
|
||||||
for _, arg := range ast.InvokeArgs {
|
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
|
// Arg switch
|
||||||
if kwargs == nil {
|
if kwargs == nil {
|
||||||
kwargs = make(map[string]*ListObject)
|
kwargs = make(map[string]*ListObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
argsPtr = &ListObject{}
|
argsPtr = &ListObject{}
|
||||||
kwargs[ident.String()[1:]] = argsPtr
|
kwargs[ident[1:]] = argsPtr
|
||||||
} else {
|
} else {
|
||||||
ae, err := e.evalDot(ctx, ec, arg)
|
ae, err := e.evalArgOrDot(ctx, ec, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
res, err := e.evalArg(ctx, ec, n.Arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -215,7 +278,16 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object,
|
||||||
return res, nil
|
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 {
|
if len(n.DotSuffix) == 0 {
|
||||||
return e.assignArg(ctx, ec, n.Arg, toVal)
|
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
|
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) {
|
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)
|
// 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
|
// 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
|
return nil, nil
|
||||||
case n.PseudoVar != nil:
|
case n.PseudoVar != nil:
|
||||||
if v, ok := ec.getPseudoVar(*n.PseudoVar); ok {
|
return e.evalPseudoVar(ctx, ec, *n.PseudoVar)
|
||||||
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)
|
|
||||||
case n.MaybeSub != nil:
|
case n.MaybeSub != nil:
|
||||||
sub := n.MaybeSub.Sub
|
sub := n.MaybeSub.Sub
|
||||||
if sub == nil {
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := e.evalDot(ctx, ec, *el.Right)
|
v, err := e.evalArgOrDot(ctx, ec, *el.Right)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -371,7 +485,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
||||||
if el.Right != nil {
|
if el.Right != nil {
|
||||||
return nil, errors.New("miss-match of lists and hash")
|
return nil, errors.New("miss-match of lists and hash")
|
||||||
}
|
}
|
||||||
v, err := e.evalDot(ctx, ec, el.Left)
|
v, err := e.evalArgOrDot(ctx, ec, el.Left)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 = `
|
var parseComments1 = `
|
||||||
proc lookup { |file|
|
proc lookup { |file|
|
||||||
foreach { |toks|
|
foreach { |toks|
|
||||||
|
|
@ -359,6 +403,19 @@ func (s *stringPseudoVar) Set(ctx context.Context, v any) error {
|
||||||
return nil
|
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{}
|
type missingPseudoVarType struct{}
|
||||||
|
|
||||||
func (missingPseudoVarType) Get(ctx context.Context, name string) (any, error) {
|
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() {
|
switch resVal.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
return listableProxyObject{v: resVal, orig: resVal}, nil
|
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:
|
case reflect.Struct:
|
||||||
return newStructProxyObject(resVal, resVal), nil
|
return newStructProxyObject(resVal, resVal), nil
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
|
|
@ -346,11 +351,16 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo
|
||||||
return false
|
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
|
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 {
|
if lit == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -363,11 +373,16 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ma.ast.InvokeArgs[ma.argShift].DotSuffix) != 0 {
|
arg := ma.ast.InvokeArgs[ma.argShift].Arg
|
||||||
|
if arg == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
lit := ma.ast.InvokeArgs[ma.argShift].Arg.Ident
|
if len(arg.DotSuffix) != 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
lit := arg.Arg.Ident
|
||||||
if lit != nil {
|
if lit != nil {
|
||||||
ma.argShift += 1
|
ma.argShift += 1
|
||||||
return lit.String(), true
|
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 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) {
|
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
|
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 {
|
type OpaqueObject struct {
|
||||||
v any
|
v any
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue