github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/ast_expression.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclsyntax" 11 "github.com/zclconf/go-cty/cty" 12 ) 13 14 type Expression struct { 15 inTree 16 17 absTraversals nodeSet 18 } 19 20 func newExpression() *Expression { 21 return &Expression{ 22 inTree: newInTree(), 23 absTraversals: newNodeSet(), 24 } 25 } 26 27 // NewExpressionRaw constructs an expression containing the given raw tokens. 28 // 29 // There is no automatic validation that the given tokens produce a valid 30 // expression. Callers of thus function must take care to produce invalid 31 // expression tokens. Where possible, use the higher-level functions 32 // NewExpressionLiteral or NewExpressionAbsTraversal instead. 33 // 34 // Because NewExpressionRaw does not interpret the given tokens in any way, 35 // an expression created by NewExpressionRaw will produce an empty result 36 // for calls to its method Variables, even if the given token sequence 37 // contains a subslice that would normally be interpreted as a traversal under 38 // parsing. 39 func NewExpressionRaw(tokens Tokens) *Expression { 40 expr := newExpression() 41 // We copy the tokens here in order to make sure that later mutations 42 // by the caller don't inadvertently cause our expression to become 43 // invalid. 44 copyTokens := make(Tokens, len(tokens)) 45 copy(copyTokens, tokens) 46 expr.children.AppendUnstructuredTokens(copyTokens) 47 return expr 48 } 49 50 // NewExpressionLiteral constructs an an expression that represents the given 51 // literal value. 52 // 53 // Since an unknown value cannot be represented in source code, this function 54 // will panic if the given value is unknown or contains a nested unknown value. 55 // Use val.IsWhollyKnown before calling to be sure. 56 // 57 // HCL native syntax does not directly represent lists, maps, and sets, and 58 // instead relies on the automatic conversions to those collection types from 59 // either list or tuple constructor syntax. Therefore converting collection 60 // values to source code and re-reading them will lose type information, and 61 // the reader must provide a suitable type at decode time to recover the 62 // original value. 63 func NewExpressionLiteral(val cty.Value) *Expression { 64 toks := TokensForValue(val) 65 expr := newExpression() 66 expr.children.AppendUnstructuredTokens(toks) 67 return expr 68 } 69 70 // NewExpressionAbsTraversal constructs an expression that represents the 71 // given traversal, which must be absolute or this function will panic. 72 func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression { 73 if traversal.IsRelative() { 74 panic("can't construct expression from relative traversal") 75 } 76 77 physT := newTraversal() 78 rootName := traversal.RootName() 79 steps := traversal[1:] 80 81 { 82 tn := newTraverseName() 83 tn.name = tn.children.Append(newIdentifier(&Token{ 84 Type: hclsyntax.TokenIdent, 85 Bytes: []byte(rootName), 86 })) 87 physT.steps.Add(physT.children.Append(tn)) 88 } 89 90 for _, step := range steps { 91 switch ts := step.(type) { 92 case hcl.TraverseAttr: 93 tn := newTraverseName() 94 tn.children.AppendUnstructuredTokens(Tokens{ 95 { 96 Type: hclsyntax.TokenDot, 97 Bytes: []byte{'.'}, 98 }, 99 }) 100 tn.name = tn.children.Append(newIdentifier(&Token{ 101 Type: hclsyntax.TokenIdent, 102 Bytes: []byte(ts.Name), 103 })) 104 physT.steps.Add(physT.children.Append(tn)) 105 case hcl.TraverseIndex: 106 ti := newTraverseIndex() 107 ti.children.AppendUnstructuredTokens(Tokens{ 108 { 109 Type: hclsyntax.TokenOBrack, 110 Bytes: []byte{'['}, 111 }, 112 }) 113 indexExpr := NewExpressionLiteral(ts.Key) 114 ti.key = ti.children.Append(indexExpr) 115 ti.children.AppendUnstructuredTokens(Tokens{ 116 { 117 Type: hclsyntax.TokenCBrack, 118 Bytes: []byte{']'}, 119 }, 120 }) 121 physT.steps.Add(physT.children.Append(ti)) 122 } 123 } 124 125 expr := newExpression() 126 expr.absTraversals.Add(expr.children.Append(physT)) 127 return expr 128 } 129 130 // Variables returns the absolute traversals that exist within the receiving 131 // expression. 132 func (e *Expression) Variables() []*Traversal { 133 nodes := e.absTraversals.List() 134 ret := make([]*Traversal, len(nodes)) 135 for i, node := range nodes { 136 ret[i] = node.content.(*Traversal) 137 } 138 return ret 139 } 140 141 // RenameVariablePrefix examines each of the absolute traversals in the 142 // receiving expression to see if they have the given sequence of names as 143 // a prefix prefix. If so, they are updated in place to have the given 144 // replacement names instead of that prefix. 145 // 146 // This can be used to implement symbol renaming. The calling application can 147 // visit all relevant expressions in its input and apply the same renaming 148 // to implement a global symbol rename. 149 // 150 // The search and replacement traversals must be the same length, or this 151 // method will panic. Only attribute access operations can be matched and 152 // replaced. Index steps never match the prefix. 153 func (e *Expression) RenameVariablePrefix(search, replacement []string) { 154 if len(search) != len(replacement) { 155 panic(fmt.Sprintf("search and replacement length mismatch (%d and %d)", len(search), len(replacement))) 156 } 157 Traversals: 158 for node := range e.absTraversals { 159 traversal := node.content.(*Traversal) 160 if len(traversal.steps) < len(search) { 161 // If it's shorter then it can't have our prefix 162 continue 163 } 164 165 stepNodes := traversal.steps.List() 166 for i, name := range search { 167 step, isName := stepNodes[i].content.(*TraverseName) 168 if !isName { 169 continue Traversals // only name nodes can match 170 } 171 foundNameBytes := step.name.content.(*identifier).token.Bytes 172 if len(foundNameBytes) != len(name) { 173 continue Traversals 174 } 175 if string(foundNameBytes) != name { 176 continue Traversals 177 } 178 } 179 180 // If we get here then the prefix matched, so now we'll swap in 181 // the replacement strings. 182 for i, name := range replacement { 183 step := stepNodes[i].content.(*TraverseName) 184 token := step.name.content.(*identifier).token 185 token.Bytes = []byte(name) 186 } 187 } 188 } 189 190 // Traversal represents a sequence of variable, attribute, and/or index 191 // operations. 192 type Traversal struct { 193 inTree 194 195 steps nodeSet 196 } 197 198 func newTraversal() *Traversal { 199 return &Traversal{ 200 inTree: newInTree(), 201 steps: newNodeSet(), 202 } 203 } 204 205 type TraverseName struct { 206 inTree 207 208 name *node 209 } 210 211 func newTraverseName() *TraverseName { 212 return &TraverseName{ 213 inTree: newInTree(), 214 } 215 } 216 217 type TraverseIndex struct { 218 inTree 219 220 key *node 221 } 222 223 func newTraverseIndex() *TraverseIndex { 224 return &TraverseIndex{ 225 inTree: newInTree(), 226 } 227 }