diff --git a/lang/interfaces/ast.go b/lang/interfaces/ast.go index be104b8e..2d4d33aa 100644 --- a/lang/interfaces/ast.go +++ b/lang/interfaces/ast.go @@ -23,15 +23,11 @@ import ( "github.com/purpleidea/mgmt/pgraph" ) -const ( - // Debug enables debugging for some commonly used debug information. - Debug = false -) - // Stmt represents a statement node in the language. A stmt could be a resource, // a `bind` statement, or even an `if` statement. (Different from an `if` // expression.) type Stmt interface { + Init(*Data) error // initialize the populated node and validate Interpolate() (Stmt, error) // return expanded form of AST as a new AST SetScope(*Scope) error // set the scope here and propagate it downwards Unify() ([]Invariant, error) // TODO: is this named correctly? @@ -45,6 +41,7 @@ type Stmt interface { // these can be stored as pointers in our graph data structure. type Expr interface { pgraph.Vertex // must implement this since we store these in our graphs + Init(*Data) error // initialize the populated node and validate Interpolate() (Expr, error) // return expanded form of AST as a new AST SetScope(*Scope) error // set the scope here and propagate it downwards SetType(*types.Type) error // sets the type definitively, errors if incompatible @@ -56,6 +53,15 @@ type Expr interface { Value() (types.Value, error) } +// Data provides some data to the node that could be useful during its lifetime. +type Data struct { + // Debug represents if we're running in debug mode or not. + Debug bool + + // Logf is a logger which should be used. + Logf func(format string, v ...interface{}) +} + // Scope represents a mapping between a variables identifier and the // corresponding expression it is bound to. Local scopes in this language exist // and are formed by nesting within if statements. Child scopes can shadow diff --git a/lang/interpolate.go b/lang/interpolate.go index 494dd444..32aa923d 100644 --- a/lang/interpolate.go +++ b/lang/interpolate.go @@ -19,7 +19,6 @@ package lang // TODO: move this into a sub package of lang/$name? import ( "fmt" - "log" "github.com/purpleidea/mgmt/lang/interfaces" @@ -36,11 +35,21 @@ type Pos struct { Filename string // optional source filename, if known } +// InterpolateInfo contains some information passed around during interpolation. +// TODO: rename to Info if this is moved to its own package. +type InterpolateInfo struct { + // Debug represents if we're running in debug mode or not. + Debug bool + + // Logf is a logger which should be used. + Logf func(format string, v ...interface{}) +} + // InterpolateStr interpolates a string and returns the representative AST. This // particular implementation uses the hashicorp hil library and syntax to do so. -func InterpolateStr(str string, pos *Pos) (interfaces.Expr, error) { - if interfaces.Debug { - log.Printf("%s: interpolate: %s", Name, str) +func InterpolateStr(str string, pos *Pos, info *InterpolateInfo) (interfaces.Expr, error) { + if info.Debug { + info.Logf("interpolating: %s", str) } var line, column int = -1, -1 var filename string @@ -59,39 +68,50 @@ func InterpolateStr(str string, pos *Pos) (interfaces.Expr, error) { if err != nil { return nil, errwrap.Wrapf(err, "can't parse string interpolation: `%s`", str) } - if interfaces.Debug { - log.Printf("%s: interpolate: tree: %+v", Name, tree) + if info.Debug { + info.Logf("tree: %+v", tree) } - result, err := hilTransform(tree) + transformInfo := &InterpolateInfo{ + Debug: info.Debug, + Logf: func(format string, v ...interface{}) { + info.Logf("transform: "+format, v...) + }, + } + result, err := hilTransform(tree, transformInfo) if err != nil { return nil, errwrap.Wrapf(err, "error running AST map: `%s`", str) } - if interfaces.Debug { - log.Printf("%s: interpolate: transform: %+v", Name, result) + if info.Debug { + info.Logf("transform: %+v", result) } - return result, err + + // make sure to run the Init on the new expression + return result, errwrap.Wrapf(result.Init(&interfaces.Data{ + Debug: info.Debug, + Logf: info.Logf, + }), "init failed") } // hilTransform returns the AST equivalent of the hil AST. -func hilTransform(root hilast.Node) (interfaces.Expr, error) { +func hilTransform(root hilast.Node, info *InterpolateInfo) (interfaces.Expr, error) { switch node := root.(type) { case *hilast.Output: // common root node - if interfaces.Debug { - log.Printf("%s: interpolate: transform: got output type: %+v", Name, node) + if info.Debug { + info.Logf("got output type: %+v", node) } if len(node.Exprs) == 0 { return nil, fmt.Errorf("no expressions found") } if len(node.Exprs) == 1 { - return hilTransform(node.Exprs[0]) + return hilTransform(node.Exprs[0], info) } // assumes len > 1 args := []interfaces.Expr{} for _, n := range node.Exprs { - expr, err := hilTransform(n) + expr, err := hilTransform(n, info) if err != nil { return nil, errwrap.Wrapf(err, "root failed") } @@ -107,12 +127,12 @@ func hilTransform(root hilast.Node) (interfaces.Expr, error) { return result, nil case *hilast.Call: - if interfaces.Debug { - log.Printf("%s: interpolate: transform: got function type: %+v", Name, node) + if info.Debug { + info.Logf("got function type: %+v", node) } args := []interfaces.Expr{} for _, n := range node.Args { - arg, err := hilTransform(n) + arg, err := hilTransform(n, info) if err != nil { return nil, fmt.Errorf("call failed: %+v", err) } @@ -125,8 +145,8 @@ func hilTransform(root hilast.Node) (interfaces.Expr, error) { }, nil case *hilast.LiteralNode: // string, int, etc... - if interfaces.Debug { - log.Printf("%s: interpolate: transform: got literal type: %+v", Name, node) + if info.Debug { + info.Logf("got literal type: %+v", node) } switch node.Typex { @@ -160,8 +180,8 @@ func hilTransform(root hilast.Node) (interfaces.Expr, error) { } case *hilast.VariableAccess: // variable lookup - if interfaces.Debug { - log.Printf("%s: interpolate: transform: got variable access type: %+v", Name, node) + if info.Debug { + info.Logf("got variable access type: %+v", node) } return &ExprVar{ Name: node.Name, diff --git a/lang/interpolate_test.go b/lang/interpolate_test.go index 534c9728..9dc0cbe7 100644 --- a/lang/interpolate_test.go +++ b/lang/interpolate_test.go @@ -140,6 +140,19 @@ func TestInterpolate0(t *testing.T) { } t.Logf("test #%d: AST: %+v", index, ast) + data := &interfaces.Data{ + Debug: true, + Logf: func(format string, v ...interface{}) { + t.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: could not init and validate AST: %+v", index, err) + return + } + iast, err := ast.Interpolate() if !fail && err != nil { t.Errorf("test #%d: FAIL", index) @@ -152,15 +165,26 @@ func TestInterpolate0(t *testing.T) { return } - if !reflect.DeepEqual(iast, exp) { - t.Errorf("test #%d: AST did not match expected", index) - // TODO: consider making our own recursive print function - t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) - t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) - if diff := pretty.Compare(iast, exp); diff != "" { // bonus - t.Logf("test #%d: diff:\n%s", index, diff) + // init exp so that the match will look identical... + if !fail { + if err := exp.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: match init failed with: %+v", index, err) + return + } + } + + if !reflect.DeepEqual(iast, exp) { + // double check because DeepEqual is different since the logf exists + diff := pretty.Compare(iast, exp) + if diff != "" { // bonus + t.Errorf("test #%d: AST did not match expected", index) + // TODO: consider making our own recursive print function + t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) + t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) + t.Logf("test #%d: diff:\n%s", index, diff) + return } - return } }) } @@ -346,6 +370,19 @@ func TestInterpolateBasicStmt(t *testing.T) { t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) { ast, fail, exp := tc.ast, tc.fail, tc.exp + data := &interfaces.Data{ + Debug: true, + Logf: func(format string, v ...interface{}) { + t.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: could not init and validate AST: %+v", index, err) + return + } + iast, err := ast.Interpolate() if !fail && err != nil { t.Errorf("test #%d: FAIL", index) @@ -358,15 +395,26 @@ func TestInterpolateBasicStmt(t *testing.T) { return } - if !reflect.DeepEqual(iast, exp) { - t.Errorf("test #%d: AST did not match expected", index) - // TODO: consider making our own recursive print function - t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) - t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) - if diff := pretty.Compare(iast, exp); diff != "" { // bonus - t.Logf("test #%d: diff:\n%s", index, diff) + // init exp so that the match will look identical... + if !fail { + if err := exp.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: match init failed with: %+v", index, err) + return + } + } + + if !reflect.DeepEqual(iast, exp) { + // double check because DeepEqual is different since the logf exists + diff := pretty.Compare(iast, exp) + if diff != "" { // bonus + t.Errorf("test #%d: AST did not match expected", index) + // TODO: consider making our own recursive print function + t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) + t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) + t.Logf("test #%d: diff:\n%s", index, diff) + return } - return } }) } @@ -637,6 +685,19 @@ func TestInterpolateBasicExpr(t *testing.T) { t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) { ast, fail, exp := tc.ast, tc.fail, tc.exp + data := &interfaces.Data{ + Debug: true, + Logf: func(format string, v ...interface{}) { + t.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: could not init and validate AST: %+v", index, err) + return + } + iast, err := ast.Interpolate() if !fail && err != nil { t.Errorf("test #%d: FAIL", index) @@ -649,15 +710,26 @@ func TestInterpolateBasicExpr(t *testing.T) { return } - if !reflect.DeepEqual(iast, exp) { - t.Errorf("test #%d: AST did not match expected", index) - // TODO: consider making our own recursive print function - t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) - t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) - if diff := pretty.Compare(iast, exp); diff != "" { // bonus - t.Logf("test #%d: diff:\n%s", index, diff) + // init exp so that the match will look identical... + if !fail { + if err := exp.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: match init failed with: %+v", index, err) + return + } + } + + if !reflect.DeepEqual(iast, exp) { + // double check because DeepEqual is different since the logf exists + diff := pretty.Compare(iast, exp) + if diff != "" { // bonus + t.Errorf("test #%d: AST did not match expected", index) + // TODO: consider making our own recursive print function + t.Logf("test #%d: actual: \n%s", index, spew.Sdump(iast)) + t.Logf("test #%d: expected: \n%s", index, spew.Sdump(exp)) + t.Logf("test #%d: diff:\n%s", index, diff) + return } - return } }) } diff --git a/lang/interpret_test.go b/lang/interpret_test.go index 4929ea62..c21bb0f3 100644 --- a/lang/interpret_test.go +++ b/lang/interpret_test.go @@ -387,6 +387,19 @@ func TestAstFunc0(t *testing.T) { } t.Logf("test #%d: AST: %+v", index, ast) + data := &interfaces.Data{ + Debug: true, + Logf: func(format string, v ...interface{}) { + t.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: could not init and validate AST: %+v", index, err) + return + } + iast, err := ast.Interpolate() if err != nil { t.Errorf("test #%d: FAIL", index) @@ -600,6 +613,19 @@ func TestAstInterpret0(t *testing.T) { } t.Logf("test #%d: AST: %+v", index, ast) + data := &interfaces.Data{ + Debug: true, + Logf: func(format string, v ...interface{}) { + t.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + t.Errorf("test #%d: FAIL", index) + t.Errorf("test #%d: could not init and validate AST: %+v", index, err) + return + } + // these tests only work in certain cases, since this does not // perform type unification, run the function graph engine, and // only gives you limited results... don't expect normal code to diff --git a/lang/lang.go b/lang/lang.go index 10430135..500ff5c9 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -88,11 +88,19 @@ func (obj *Lang) Init() error { obj.Logf("behold, the AST: %+v", ast) } - // TODO: should we validate the structure of the AST? - // TODO: should we do this *after* interpolate, or trust it to behave? - //if err := ast.Validate(); err != nil { - // return errwrap.Wrapf(err, "could not validate AST") - //} + obj.Logf("init...") + // init and validate the structure of the AST + data := &interfaces.Data{ + Debug: obj.Debug, + Logf: func(format string, v ...interface{}) { + // TODO: is this a sane prefix to use here? + obj.Logf("ast: "+format, v...) + }, + } + // some of this might happen *after* interpolate in SetScope or Unify... + if err := ast.Init(data); err != nil { + return errwrap.Wrapf(err, "could not init and validate AST") + } obj.Logf("interpolating...") // interpolate strings and other expansionable nodes in AST diff --git a/lang/structs.go b/lang/structs.go index be00d590..15919882 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -19,7 +19,6 @@ package lang // TODO: move this into a sub package of lang/$name? import ( "fmt" - "log" "reflect" "strings" @@ -61,6 +60,12 @@ type StmtBind struct { Value interfaces.Expr } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtBind) Init(data *interfaces.Data) error { + return obj.Value.Init(data) +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -120,11 +125,28 @@ func (obj *StmtBind) Output() (*interfaces.Output, error) { // Res's in the Output function. Alternatively, it could be a map[name]struct{}, // or even a map[[]name]struct{}. type StmtRes struct { + data *interfaces.Data + Kind string // kind of resource, eg: pkg, file, svc, etc... Name interfaces.Expr // unique name for the res of this kind Contents []StmtResContents // list of fields/edges in parsed order } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtRes) Init(data *interfaces.Data) error { + obj.data = data + if err := obj.Name.Init(data); err != nil { + return err + } + for _, x := range obj.Contents { + if err := x.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -144,6 +166,7 @@ func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) { } return &StmtRes{ + data: obj.data, Kind: obj.Kind, Name: name, Contents: contents, @@ -322,8 +345,8 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) { } // all our int's are src kind == reflect.Int64 in our language! - if interfaces.Debug { - log.Printf("field `%s`: type(%+v), expected(%+v)", x.Field, typ, tkk) + if obj.data.Debug { + obj.data.Logf("field `%s`: type(%+v), expected(%+v)", x.Field, typ, tkk) } // overflow check @@ -353,15 +376,15 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) { // finally build a new value to set tt := tf.Type kk := tt.Kind() - if interfaces.Debug { - log.Printf("field `%s`: start(%v)->kind(%v)", x.Field, tt, kk) + if obj.data.Debug { + obj.data.Logf("field `%s`: start(%v)->kind(%v)", x.Field, tt, kk) } //fmt.Printf("start: %v || %+v\n", tt, kk) for kk == reflect.Ptr { tt = tt.Elem() // un-nest one pointer kk = tt.Kind() - if interfaces.Debug { - log.Printf("field `%s`:\tloop(%v)->kind(%v)", x.Field, tt, kk) + if obj.data.Debug { + obj.data.Logf("field `%s`:\tloop(%v)->kind(%v)", x.Field, tt, kk) } // wrap in ptr by one level valof := reflect.ValueOf(value.Interface()) @@ -498,6 +521,7 @@ func (obj *StmtRes) edges() ([]*interfaces.Edge, error) { // StmtResContents is the interface that is met by the resource contents. Look // closely for while it is similar to the Stmt interface, it is quite different. type StmtResContents interface { + Init(*interfaces.Data) error Interpolate() (StmtResContents, error) // different! SetScope(*interfaces.Scope) error Unify(kind string) ([]interfaces.Invariant, error) // different! @@ -512,6 +536,17 @@ type StmtResField struct { Condition interfaces.Expr // the value will be used if nil or true } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtResField) Init(data *interfaces.Data) error { + if obj.Condition != nil { + if err := obj.Condition.Init(data); err != nil { + return err + } + } + return obj.Value.Init(data) +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -647,6 +682,17 @@ type StmtResEdge struct { Condition interfaces.Expr // the value will be used if nil or true } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtResEdge) Init(data *interfaces.Data) error { + if obj.Condition != nil { + if err := obj.Condition.Init(data); err != nil { + return err + } + } + return obj.EdgeHalf.Init(data) +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -764,6 +810,17 @@ type StmtEdge struct { Notify bool // specifies that this edge sends a notification as well } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtEdge) Init(data *interfaces.Data) error { + for _, x := range obj.EdgeHalfList { + if err := x.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -908,6 +965,12 @@ type StmtEdgeHalf struct { SendRecv string // name of field to send/recv from, empty to ignore } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtEdgeHalf) Init(data *interfaces.Data) error { + return obj.Name.Init(data) +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -983,6 +1046,25 @@ type StmtIf struct { ElseBranch interfaces.Stmt // optional } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtIf) Init(data *interfaces.Data) error { + if err := obj.Condition.Init(data); err != nil { + return err + } + if obj.ThenBranch != nil { + if err := obj.ThenBranch.Init(data); err != nil { + return err + } + } + if obj.ElseBranch != nil { + if err := obj.ElseBranch.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1149,9 +1231,23 @@ func (obj *StmtIf) Output() (*interfaces.Output, error) { // the bind statement's are correctly applied in this scope, and irrespective of // their order of definition. type StmtProg struct { + data *interfaces.Data + Prog []interfaces.Stmt } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtProg) Init(data *interfaces.Data) error { + obj.data = data + for _, x := range obj.Prog { + if err := x.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1165,6 +1261,7 @@ func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) { prog = append(prog, interpolated) } return &StmtProg{ + data: obj.data, Prog: prog, }, nil } @@ -1321,6 +1418,12 @@ type StmtClass struct { Body interfaces.Stmt // probably a *StmtProg } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtClass) Init(data *interfaces.Data) error { + return obj.Body.Init(data) +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1391,6 +1494,19 @@ type StmtInclude struct { Args []interfaces.Expr } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtInclude) Init(data *interfaces.Data) error { + if obj.Args != nil { + for _, x := range obj.Args { + if err := x.Init(data); err != nil { + return err + } + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1580,6 +1696,12 @@ type StmtComment struct { Value string } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtComment) Init(*interfaces.Data) error { + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1628,6 +1750,10 @@ type ExprBool struct { // String returns a short representation of this expression. func (obj *ExprBool) String() string { return fmt.Sprintf("bool(%t)", obj.V) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprBool) Init(*interfaces.Data) error { return nil } + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1707,12 +1833,21 @@ func (obj *ExprBool) Value() (types.Value, error) { // ExprStr is a representation of a string. type ExprStr struct { + data *interfaces.Data + V string // value of this string } // String returns a short representation of this expression. func (obj *ExprStr) String() string { return fmt.Sprintf("str(%s)", obj.V) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprStr) Init(data *interfaces.Data) error { + obj.data = data + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1727,13 +1862,20 @@ func (obj *ExprStr) Interpolate() (interfaces.Expr, error) { //Line: -1, // TODO //Filename: "", // optional source filename, if known } - result, err := InterpolateStr(obj.V, pos) + info := &InterpolateInfo{ + Debug: obj.data.Debug, + Logf: func(format string, v ...interface{}) { + obj.data.Logf("interpolate: "+format, v...) + }, + } + result, err := InterpolateStr(obj.V, pos, info) if err != nil { return nil, err } if result == nil { return &ExprStr{ - V: obj.V, + data: obj.data, + V: obj.V, }, nil } // we got something, overwrite the existing static str @@ -1815,6 +1957,10 @@ type ExprInt struct { // String returns a short representation of this expression. func (obj *ExprInt) String() string { return fmt.Sprintf("int(%d)", obj.V) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprInt) Init(*interfaces.Data) error { return nil } + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1902,6 +2048,10 @@ func (obj *ExprFloat) String() string { return fmt.Sprintf("float(%g)", obj.V) // TODO: %f instead? } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprFloat) Init(*interfaces.Data) error { return nil } + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -1996,6 +2146,17 @@ func (obj *ExprList) String() string { return fmt.Sprintf("list(%s)", strings.Join(s, ", ")) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprList) Init(data *interfaces.Data) error { + for _, x := range obj.Elements { + if err := x.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -2241,6 +2402,20 @@ func (obj *ExprMap) String() string { return fmt.Sprintf("map(%s)", strings.Join(s, ", ")) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprMap) Init(data *interfaces.Data) error { + for _, x := range obj.KVs { + if err := x.Key.Init(data); err != nil { + return err + } + if err := x.Val.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -2581,6 +2756,17 @@ func (obj *ExprStruct) String() string { return fmt.Sprintf("struct(%s)", strings.Join(s, "; ")) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprStruct) Init(data *interfaces.Data) error { + for _, x := range obj.Fields { + if err := x.Value.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -2839,6 +3025,10 @@ type ExprFunc struct { // we have a better printable function value and put that here instead. func (obj *ExprFunc) String() string { return fmt.Sprintf("func(???)") } // TODO: print nicely +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprFunc) Init(*interfaces.Data) error { return nil } + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -2999,6 +3189,17 @@ func (obj *ExprCall) buildFunc() (interfaces.Func, error) { return fn, nil } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprCall) Init(data *interfaces.Data) error { + for _, x := range obj.Args { + if err := x.Init(data); err != nil { + return err + } + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -3374,6 +3575,10 @@ type ExprVar struct { // String returns a short representation of this expression. func (obj *ExprVar) String() string { return fmt.Sprintf("var(%s)", obj.Name) } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprVar) Init(*interfaces.Data) error { return nil } + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. @@ -3598,6 +3803,21 @@ func (obj *ExprIf) String() string { return fmt.Sprintf("if(%s)", obj.Condition.String()) // TODO: improve this } +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprIf) Init(data *interfaces.Data) error { + if err := obj.Condition.Init(data); err != nil { + return err + } + if err := obj.ThenBranch.Init(data); err != nil { + return err + } + if err := obj.ElseBranch.Init(data); err != nil { + return err + } + return nil +} + // Interpolate returns a new node (aka a copy) once it has been expanded. This // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents.