diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go new file mode 100644 index 0000000..62d89fb --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hclwrite/ast_expression.go | |||
@@ -0,0 +1,201 @@ | |||
1 | package hclwrite | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/hashicorp/hcl2/hcl" | ||
7 | "github.com/hashicorp/hcl2/hcl/hclsyntax" | ||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | ) | ||
10 | |||
11 | type Expression struct { | ||
12 | inTree | ||
13 | |||
14 | absTraversals nodeSet | ||
15 | } | ||
16 | |||
17 | func newExpression() *Expression { | ||
18 | return &Expression{ | ||
19 | inTree: newInTree(), | ||
20 | absTraversals: newNodeSet(), | ||
21 | } | ||
22 | } | ||
23 | |||
24 | // NewExpressionLiteral constructs an an expression that represents the given | ||
25 | // literal value. | ||
26 | // | ||
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. | ||
30 | // | ||
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 | ||
36 | // original value. | ||
37 | func NewExpressionLiteral(val cty.Value) *Expression { | ||
38 | toks := TokensForValue(val) | ||
39 | expr := newExpression() | ||
40 | expr.children.AppendUnstructuredTokens(toks) | ||
41 | return expr | ||
42 | } | ||
43 | |||
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") | ||
49 | } | ||
50 | |||
51 | physT := newTraversal() | ||
52 | rootName := traversal.RootName() | ||
53 | steps := traversal[1:] | ||
54 | |||
55 | { | ||
56 | tn := newTraverseName() | ||
57 | tn.name = tn.children.Append(newIdentifier(&Token{ | ||
58 | Type: hclsyntax.TokenIdent, | ||
59 | Bytes: []byte(rootName), | ||
60 | })) | ||
61 | physT.steps.Add(physT.children.Append(tn)) | ||
62 | } | ||
63 | |||
64 | for _, step := range steps { | ||
65 | switch ts := step.(type) { | ||
66 | case hcl.TraverseAttr: | ||
67 | tn := newTraverseName() | ||
68 | tn.children.AppendUnstructuredTokens(Tokens{ | ||
69 | { | ||
70 | Type: hclsyntax.TokenDot, | ||
71 | Bytes: []byte{'.'}, | ||
72 | }, | ||
73 | }) | ||
74 | tn.name = tn.children.Append(newIdentifier(&Token{ | ||
75 | Type: hclsyntax.TokenIdent, | ||
76 | Bytes: []byte(ts.Name), | ||
77 | })) | ||
78 | physT.steps.Add(physT.children.Append(tn)) | ||
79 | case hcl.TraverseIndex: | ||
80 | ti := newTraverseIndex() | ||
81 | ti.children.AppendUnstructuredTokens(Tokens{ | ||
82 | { | ||
83 | Type: hclsyntax.TokenOBrack, | ||
84 | Bytes: []byte{'['}, | ||
85 | }, | ||
86 | }) | ||
87 | indexExpr := NewExpressionLiteral(ts.Key) | ||
88 | ti.key = ti.children.Append(indexExpr) | ||
89 | ti.children.AppendUnstructuredTokens(Tokens{ | ||
90 | { | ||
91 | Type: hclsyntax.TokenCBrack, | ||
92 | Bytes: []byte{']'}, | ||
93 | }, | ||
94 | }) | ||
95 | physT.steps.Add(physT.children.Append(ti)) | ||
96 | } | ||
97 | } | ||
98 | |||
99 | expr := newExpression() | ||
100 | expr.absTraversals.Add(expr.children.Append(physT)) | ||
101 | return expr | ||
102 | } | ||
103 | |||
104 | // Variables returns the absolute traversals that exist within the receiving | ||
105 | // expression. | ||
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) | ||
111 | } | ||
112 | return ret | ||
113 | } | ||
114 | |||
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. | ||
119 | // | ||
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. | ||
123 | // | ||
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))) | ||
130 | } | ||
131 | Traversals: | ||
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 | ||
136 | continue | ||
137 | } | ||
138 | |||
139 | stepNodes := traversal.steps.List() | ||
140 | for i, name := range search { | ||
141 | step, isName := stepNodes[i].content.(*TraverseName) | ||
142 | if !isName { | ||
143 | continue Traversals // only name nodes can match | ||
144 | } | ||
145 | foundNameBytes := step.name.content.(*identifier).token.Bytes | ||
146 | if len(foundNameBytes) != len(name) { | ||
147 | continue Traversals | ||
148 | } | ||
149 | if string(foundNameBytes) != name { | ||
150 | continue Traversals | ||
151 | } | ||
152 | } | ||
153 | |||
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) | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | |||
164 | // Traversal represents a sequence of variable, attribute, and/or index | ||
165 | // operations. | ||
166 | type Traversal struct { | ||
167 | inTree | ||
168 | |||
169 | steps nodeSet | ||
170 | } | ||
171 | |||
172 | func newTraversal() *Traversal { | ||
173 | return &Traversal{ | ||
174 | inTree: newInTree(), | ||
175 | steps: newNodeSet(), | ||
176 | } | ||
177 | } | ||
178 | |||
179 | type TraverseName struct { | ||
180 | inTree | ||
181 | |||
182 | name *node | ||
183 | } | ||
184 | |||
185 | func newTraverseName() *TraverseName { | ||
186 | return &TraverseName{ | ||
187 | inTree: newInTree(), | ||
188 | } | ||
189 | } | ||
190 | |||
191 | type TraverseIndex struct { | ||
192 | inTree | ||
193 | |||
194 | key *node | ||
195 | } | ||
196 | |||
197 | func newTraverseIndex() *TraverseIndex { | ||
198 | return &TraverseIndex{ | ||
199 | inTree: newInTree(), | ||
200 | } | ||
201 | } | ||