6 "github.com/hashicorp/hcl2/hcl"
7 "github.com/hashicorp/hcl2/hcl/hclsyntax"
8 "github.com/zclconf/go-cty/cty"
11 type Expression struct {
17 func newExpression() *Expression {
20 absTraversals: newNodeSet(),
24 // NewExpressionLiteral constructs an an expression that represents the given
27 // Since an unknown value cannot be represented in source code, this function
28 // will panic if the given value is unknown or contains a nested unknown value.
29 // Use val.IsWhollyKnown before calling to be sure.
31 // HCL native syntax does not directly represent lists, maps, and sets, and
32 // instead relies on the automatic conversions to those collection types from
33 // either list or tuple constructor syntax. Therefore converting collection
34 // values to source code and re-reading them will lose type information, and
35 // the reader must provide a suitable type at decode time to recover the
37 func NewExpressionLiteral(val cty.Value) *Expression {
38 toks := TokensForValue(val)
39 expr := newExpression()
40 expr.children.AppendUnstructuredTokens(toks)
44 // NewExpressionAbsTraversal constructs an expression that represents the
45 // given traversal, which must be absolute or this function will panic.
46 func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression {
47 if traversal.IsRelative() {
48 panic("can't construct expression from relative traversal")
51 physT := newTraversal()
52 rootName := traversal.RootName()
53 steps := traversal[1:]
56 tn := newTraverseName()
57 tn.name = tn.children.Append(newIdentifier(&Token{
58 Type: hclsyntax.TokenIdent,
59 Bytes: []byte(rootName),
61 physT.steps.Add(physT.children.Append(tn))
64 for _, step := range steps {
65 switch ts := step.(type) {
66 case hcl.TraverseAttr:
67 tn := newTraverseName()
68 tn.children.AppendUnstructuredTokens(Tokens{
70 Type: hclsyntax.TokenDot,
74 tn.name = tn.children.Append(newIdentifier(&Token{
75 Type: hclsyntax.TokenIdent,
76 Bytes: []byte(ts.Name),
78 physT.steps.Add(physT.children.Append(tn))
79 case hcl.TraverseIndex:
80 ti := newTraverseIndex()
81 ti.children.AppendUnstructuredTokens(Tokens{
83 Type: hclsyntax.TokenOBrack,
87 indexExpr := NewExpressionLiteral(ts.Key)
88 ti.key = ti.children.Append(indexExpr)
89 ti.children.AppendUnstructuredTokens(Tokens{
91 Type: hclsyntax.TokenCBrack,
95 physT.steps.Add(physT.children.Append(ti))
99 expr := newExpression()
100 expr.absTraversals.Add(expr.children.Append(physT))
104 // Variables returns the absolute traversals that exist within the receiving
106 func (e *Expression) Variables() []*Traversal {
107 nodes := e.absTraversals.List()
108 ret := make([]*Traversal, len(nodes))
109 for i, node := range nodes {
110 ret[i] = node.content.(*Traversal)
115 // RenameVariablePrefix examines each of the absolute traversals in the
116 // receiving expression to see if they have the given sequence of names as
117 // a prefix prefix. If so, they are updated in place to have the given
118 // replacement names instead of that prefix.
120 // This can be used to implement symbol renaming. The calling application can
121 // visit all relevant expressions in its input and apply the same renaming
122 // to implement a global symbol rename.
124 // The search and replacement traversals must be the same length, or this
125 // method will panic. Only attribute access operations can be matched and
126 // replaced. Index steps never match the prefix.
127 func (e *Expression) RenameVariablePrefix(search, replacement []string) {
128 if len(search) != len(replacement) {
129 panic(fmt.Sprintf("search and replacement length mismatch (%d and %d)", len(search), len(replacement)))
132 for node := range e.absTraversals {
133 traversal := node.content.(*Traversal)
134 if len(traversal.steps) < len(search) {
135 // If it's shorter then it can't have our prefix
139 stepNodes := traversal.steps.List()
140 for i, name := range search {
141 step, isName := stepNodes[i].content.(*TraverseName)
143 continue Traversals // only name nodes can match
145 foundNameBytes := step.name.content.(*identifier).token.Bytes
146 if len(foundNameBytes) != len(name) {
149 if string(foundNameBytes) != name {
154 // If we get here then the prefix matched, so now we'll swap in
155 // the replacement strings.
156 for i, name := range replacement {
157 step := stepNodes[i].content.(*TraverseName)
158 token := step.name.content.(*identifier).token
159 token.Bytes = []byte(name)
164 // Traversal represents a sequence of variable, attribute, and/or index
166 type Traversal struct {
172 func newTraversal() *Traversal {
179 type TraverseName struct {
185 func newTraverseName() *TraverseName {
186 return &TraverseName{
191 type TraverseIndex struct {
197 func newTraverseIndex() *TraverseIndex {
198 return &TraverseIndex{