]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package hcl |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | ||
6 | "github.com/zclconf/go-cty/cty" | |
7 | ) | |
8 | ||
9 | // A Traversal is a description of traversing through a value through a | |
10 | // series of operations such as attribute lookup, index lookup, etc. | |
11 | // | |
12 | // It is used to look up values in scopes, for example. | |
13 | // | |
14 | // The traversal operations are implementations of interface Traverser. | |
15 | // This is a closed set of implementations, so the interface cannot be | |
16 | // implemented from outside this package. | |
17 | // | |
18 | // A traversal can be absolute (its first value is a symbol name) or relative | |
19 | // (starts from an existing value). | |
20 | type Traversal []Traverser | |
21 | ||
22 | // TraversalJoin appends a relative traversal to an absolute traversal to | |
23 | // produce a new absolute traversal. | |
24 | func TraversalJoin(abs Traversal, rel Traversal) Traversal { | |
25 | if abs.IsRelative() { | |
26 | panic("first argument to TraversalJoin must be absolute") | |
27 | } | |
28 | if !rel.IsRelative() { | |
29 | panic("second argument to TraversalJoin must be relative") | |
30 | } | |
31 | ||
32 | ret := make(Traversal, len(abs)+len(rel)) | |
33 | copy(ret, abs) | |
34 | copy(ret[len(abs):], rel) | |
35 | return ret | |
36 | } | |
37 | ||
38 | // TraverseRel applies the receiving traversal to the given value, returning | |
39 | // the resulting value. This is supported only for relative traversals, | |
40 | // and will panic if applied to an absolute traversal. | |
41 | func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { | |
42 | if !t.IsRelative() { | |
43 | panic("can't use TraverseRel on an absolute traversal") | |
44 | } | |
45 | ||
46 | current := val | |
47 | var diags Diagnostics | |
48 | for _, tr := range t { | |
49 | var newDiags Diagnostics | |
50 | current, newDiags = tr.TraversalStep(current) | |
51 | diags = append(diags, newDiags...) | |
52 | if newDiags.HasErrors() { | |
53 | return cty.DynamicVal, diags | |
54 | } | |
55 | } | |
56 | return current, diags | |
57 | } | |
58 | ||
59 | // TraverseAbs applies the receiving traversal to the given eval context, | |
60 | // returning the resulting value. This is supported only for absolute | |
61 | // traversals, and will panic if applied to a relative traversal. | |
62 | func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { | |
63 | if t.IsRelative() { | |
64 | panic("can't use TraverseAbs on a relative traversal") | |
65 | } | |
66 | ||
67 | split := t.SimpleSplit() | |
68 | root := split.Abs[0].(TraverseRoot) | |
69 | name := root.Name | |
70 | ||
71 | thisCtx := ctx | |
72 | hasNonNil := false | |
73 | for thisCtx != nil { | |
74 | if thisCtx.Variables == nil { | |
75 | thisCtx = thisCtx.parent | |
76 | continue | |
77 | } | |
78 | hasNonNil = true | |
79 | val, exists := thisCtx.Variables[name] | |
80 | if exists { | |
81 | return split.Rel.TraverseRel(val) | |
82 | } | |
83 | thisCtx = thisCtx.parent | |
84 | } | |
85 | ||
86 | if !hasNonNil { | |
87 | return cty.DynamicVal, Diagnostics{ | |
88 | { | |
89 | Severity: DiagError, | |
90 | Summary: "Variables not allowed", | |
91 | Detail: "Variables may not be used here.", | |
92 | Subject: &root.SrcRange, | |
93 | }, | |
94 | } | |
95 | } | |
96 | ||
97 | suggestions := make([]string, 0, len(ctx.Variables)) | |
98 | thisCtx = ctx | |
99 | for thisCtx != nil { | |
100 | for k := range thisCtx.Variables { | |
101 | suggestions = append(suggestions, k) | |
102 | } | |
103 | thisCtx = thisCtx.parent | |
104 | } | |
105 | suggestion := nameSuggestion(name, suggestions) | |
106 | if suggestion != "" { | |
107 | suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) | |
108 | } | |
109 | ||
110 | return cty.DynamicVal, Diagnostics{ | |
111 | { | |
112 | Severity: DiagError, | |
113 | Summary: "Unknown variable", | |
114 | Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion), | |
115 | Subject: &root.SrcRange, | |
116 | }, | |
117 | } | |
118 | } | |
119 | ||
120 | // IsRelative returns true if the receiver is a relative traversal, or false | |
121 | // otherwise. | |
122 | func (t Traversal) IsRelative() bool { | |
123 | if len(t) == 0 { | |
124 | return true | |
125 | } | |
126 | if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot { | |
127 | return false | |
128 | } | |
129 | return true | |
130 | } | |
131 | ||
132 | // SimpleSplit returns a TraversalSplit where the name lookup is the absolute | |
133 | // part and the remainder is the relative part. Supported only for | |
134 | // absolute traversals, and will panic if applied to a relative traversal. | |
135 | // | |
136 | // This can be used by applications that have a relatively-simple variable | |
137 | // namespace where only the top-level is directly populated in the scope, with | |
138 | // everything else handled by relative lookups from those initial values. | |
139 | func (t Traversal) SimpleSplit() TraversalSplit { | |
140 | if t.IsRelative() { | |
141 | panic("can't use SimpleSplit on a relative traversal") | |
142 | } | |
143 | return TraversalSplit{ | |
144 | Abs: t[0:1], | |
145 | Rel: t[1:], | |
146 | } | |
147 | } | |
148 | ||
149 | // RootName returns the root name for a absolute traversal. Will panic if | |
150 | // called on a relative traversal. | |
151 | func (t Traversal) RootName() string { | |
152 | if t.IsRelative() { | |
153 | panic("can't use RootName on a relative traversal") | |
154 | ||
155 | } | |
156 | return t[0].(TraverseRoot).Name | |
157 | } | |
158 | ||
159 | // SourceRange returns the source range for the traversal. | |
160 | func (t Traversal) SourceRange() Range { | |
161 | if len(t) == 0 { | |
162 | // Nothing useful to return here, but we'll return something | |
163 | // that's correctly-typed at least. | |
164 | return Range{} | |
165 | } | |
166 | ||
167 | return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange()) | |
168 | } | |
169 | ||
170 | // TraversalSplit represents a pair of traversals, the first of which is | |
171 | // an absolute traversal and the second of which is relative to the first. | |
172 | // | |
173 | // This is used by calling applications that only populate prefixes of the | |
174 | // traversals in the scope, with Abs representing the part coming from the | |
175 | // scope and Rel representing the remaining steps once that part is | |
176 | // retrieved. | |
177 | type TraversalSplit struct { | |
178 | Abs Traversal | |
179 | Rel Traversal | |
180 | } | |
181 | ||
182 | // TraverseAbs traverses from a scope to the value resulting from the | |
183 | // absolute traversal. | |
184 | func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { | |
185 | return t.Abs.TraverseAbs(ctx) | |
186 | } | |
187 | ||
188 | // TraverseRel traverses from a given value, assumed to be the result of | |
189 | // TraverseAbs on some scope, to a final result for the entire split traversal. | |
190 | func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { | |
191 | return t.Rel.TraverseRel(val) | |
192 | } | |
193 | ||
194 | // Traverse is a convenience function to apply TraverseAbs followed by | |
195 | // TraverseRel. | |
196 | func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) { | |
197 | v1, diags := t.TraverseAbs(ctx) | |
198 | if diags.HasErrors() { | |
199 | return cty.DynamicVal, diags | |
200 | } | |
201 | v2, newDiags := t.TraverseRel(v1) | |
202 | diags = append(diags, newDiags...) | |
203 | return v2, diags | |
204 | } | |
205 | ||
206 | // Join concatenates together the Abs and Rel parts to produce a single | |
207 | // absolute traversal. | |
208 | func (t TraversalSplit) Join() Traversal { | |
209 | return TraversalJoin(t.Abs, t.Rel) | |
210 | } | |
211 | ||
212 | // RootName returns the root name for the absolute part of the split. | |
213 | func (t TraversalSplit) RootName() string { | |
214 | return t.Abs.RootName() | |
215 | } | |
216 | ||
217 | // A Traverser is a step within a Traversal. | |
218 | type Traverser interface { | |
219 | TraversalStep(cty.Value) (cty.Value, Diagnostics) | |
220 | SourceRange() Range | |
221 | isTraverserSigil() isTraverser | |
222 | } | |
223 | ||
224 | // Embed this in a struct to declare it as a Traverser | |
225 | type isTraverser struct { | |
226 | } | |
227 | ||
228 | func (tr isTraverser) isTraverserSigil() isTraverser { | |
229 | return isTraverser{} | |
230 | } | |
231 | ||
232 | // TraverseRoot looks up a root name in a scope. It is used as the first step | |
233 | // of an absolute Traversal, and cannot itself be traversed directly. | |
234 | type TraverseRoot struct { | |
235 | isTraverser | |
236 | Name string | |
237 | SrcRange Range | |
238 | } | |
239 | ||
240 | // TraversalStep on a TraverseName immediately panics, because absolute | |
241 | // traversals cannot be directly traversed. | |
242 | func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) { | |
243 | panic("Cannot traverse an absolute traversal") | |
244 | } | |
245 | ||
246 | func (tn TraverseRoot) SourceRange() Range { | |
247 | return tn.SrcRange | |
248 | } | |
249 | ||
250 | // TraverseAttr looks up an attribute in its initial value. | |
251 | type TraverseAttr struct { | |
252 | isTraverser | |
253 | Name string | |
254 | SrcRange Range | |
255 | } | |
256 | ||
257 | func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | |
107c1cdb | 258 | return GetAttr(val, tn.Name, &tn.SrcRange) |
15c0b25d AP |
259 | } |
260 | ||
261 | func (tn TraverseAttr) SourceRange() Range { | |
262 | return tn.SrcRange | |
263 | } | |
264 | ||
265 | // TraverseIndex applies the index operation to its initial value. | |
266 | type TraverseIndex struct { | |
267 | isTraverser | |
268 | Key cty.Value | |
269 | SrcRange Range | |
270 | } | |
271 | ||
272 | func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | |
273 | return Index(val, tn.Key, &tn.SrcRange) | |
274 | } | |
275 | ||
276 | func (tn TraverseIndex) SourceRange() Range { | |
277 | return tn.SrcRange | |
278 | } | |
279 | ||
280 | // TraverseSplat applies the splat operation to its initial value. | |
281 | type TraverseSplat struct { | |
282 | isTraverser | |
283 | Each Traversal | |
284 | SrcRange Range | |
285 | } | |
286 | ||
287 | func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { | |
288 | panic("TraverseSplat not yet implemented") | |
289 | } | |
290 | ||
291 | func (tn TraverseSplat) SourceRange() Range { | |
292 | return tn.SrcRange | |
293 | } |