diff --git a/ucl/ast.go b/ucl/ast.go index 250587d..4814288 100644 --- a/ucl/ast.go +++ b/ucl/ast.go @@ -103,9 +103,10 @@ type astDot struct { } type astCmd struct { - Pos lexer.Position - Name astDot `parser:"@@"` - Args []astDot `parser:"@@*"` + Pos lexer.Position + Name astDot `parser:"@@"` + Assign *astDot `parser:"( EQ @@"` + InvokeArgs []astDot `parser:" | @@+ )?"` } type astPipeline struct { @@ -141,6 +142,7 @@ var scanner = lexer.MustStateful(lexer.Rules{ {"RC", `\}`, nil}, {"NL", `[;\n][; \n\t]*`, nil}, {"PIPE", `\|`, nil}, + {"EQ", `=`, nil}, {"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil}, }, "String": { diff --git a/ucl/eval.go b/ucl/eval.go index ab9f26a..4f6894f 100644 --- a/ucl/eval.go +++ b/ucl/eval.go @@ -72,6 +72,14 @@ 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.Assign != nil: + // Assignment + assignVal, err := e.evalDot(ctx, ec, *ast.Assign) + if err != nil { + return nil, err + } + + return e.assignDot(ctx, ec, ast.Assign, assignVal) case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0: name := ast.Name.Arg.Ident.String() @@ -85,7 +93,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object, } else { return nil, errors.New("unknown command: " + name) } - case len(ast.Args) > 0: + case len(ast.InvokeArgs) > 0: nameElem, err := e.evalDot(ctx, ec, ast.Name) if err != nil { return nil, err @@ -117,7 +125,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O if currentPipe != nil { argsPtr.Append(currentPipe) } - for _, arg := range ast.Args { + for _, arg := range ast.InvokeArgs { if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' { // Arg switch if kwargs == nil { @@ -176,6 +184,14 @@ 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) { + if len(n.DotSuffix) == 0 { + return e.assignArg(ctx, ec, n.Arg, toVal) + } + + panic("TODO") +} + func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) { switch { case n.Literal != nil: @@ -211,6 +227,40 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec return nil, errors.New("unhandled arg type") } +func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) { + switch { + case n.Literal != nil: + // We may use this for variable setting? + return nil, errors.New("cannot assign to a literal") + case n.Var != nil: + ec.setOrDefineVar(*n.Var, toVal) + return toVal, nil + case n.PseudoVar != nil: + pvar, ok := ec.getPseudoVar(*n.PseudoVar) + if ok { + if err := pvar.set(ctx, *n.PseudoVar, toVal); err != nil { + return nil, err + } + return toVal, nil + } + + if pvar := e.inst.missingPseudoVarHandler; pvar != nil { + if err := pvar.set(ctx, *n.PseudoVar, toVal); err != nil { + return nil, err + } + return toVal, nil + } + return nil, errors.New("unknown pseudo-variable: " + *n.Var) + case n.MaybeSub != nil: + return nil, errors.New("cannot assign to a subexpression") + case n.ListOrHash != nil: + return nil, errors.New("cannot assign to a list or hash") + case n.Block != nil: + return nil, errors.New("cannot assign to a block") + } + return nil, errors.New("unhandled arg type") +} + func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) { if loh.EmptyList { return &ListObject{}, nil diff --git a/ucl/objs.go b/ucl/objs.go index e2bfda9..c17e246 100644 --- a/ucl/objs.go +++ b/ucl/objs.go @@ -279,7 +279,7 @@ type macroArgs struct { } func (ma macroArgs) nargs() int { - return len(ma.ast.Args[ma.argShift:]) + return len(ma.ast.InvokeArgs[ma.argShift:]) } func (ma *macroArgs) shift(n int) { @@ -287,15 +287,15 @@ func (ma *macroArgs) shift(n int) { } func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bool { - if n >= len(ma.ast.Args[ma.argShift:]) { + if n >= len(ma.ast.InvokeArgs[ma.argShift:]) { return false } - if len(ma.ast.Args[ma.argShift+n].DotSuffix) != 0 { + if len(ma.ast.InvokeArgs[ma.argShift+n].DotSuffix) != 0 { return false } - lit := ma.ast.Args[ma.argShift+n].Arg.Ident + lit := ma.ast.InvokeArgs[ma.argShift+n].Arg.Ident if lit == nil { return false } @@ -304,15 +304,15 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo } func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) { - if ma.argShift >= len(ma.ast.Args) { + if ma.argShift >= len(ma.ast.InvokeArgs) { return "", false } - if len(ma.ast.Args[ma.argShift].DotSuffix) != 0 { + if len(ma.ast.InvokeArgs[ma.argShift].DotSuffix) != 0 { return "", false } - lit := ma.ast.Args[ma.argShift].Arg.Ident + lit := ma.ast.InvokeArgs[ma.argShift].Arg.Ident if lit != nil { ma.argShift += 1 return lit.String(), true @@ -321,11 +321,11 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) { } func (ma macroArgs) evalArg(ctx context.Context, n int) (Object, error) { - if n >= len(ma.ast.Args[ma.argShift:]) { + if n >= len(ma.ast.InvokeArgs[ma.argShift:]) { return nil, errors.New("not enough arguments") // FIX } - return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n]) + return ma.eval.evalDot(ctx, ma.ec, ma.ast.InvokeArgs[ma.argShift+n]) } func (ma macroArgs) evalBlock(ctx context.Context, n int, args []Object, pushScope bool) (Object, error) {