| package hcl |
| |
| import ( |
| "fmt" |
| |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // A Traversal is a description of traversing through a value through a |
| // series of operations such as attribute lookup, index lookup, etc. |
| // |
| // It is used to look up values in scopes, for example. |
| // |
| // The traversal operations are implementations of interface Traverser. |
| // This is a closed set of implementations, so the interface cannot be |
| // implemented from outside this package. |
| // |
| // A traversal can be absolute (its first value is a symbol name) or relative |
| // (starts from an existing value). |
| type Traversal []Traverser |
| |
| // TraversalJoin appends a relative traversal to an absolute traversal to |
| // produce a new absolute traversal. |
| func TraversalJoin(abs Traversal, rel Traversal) Traversal { |
| if abs.IsRelative() { |
| panic("first argument to TraversalJoin must be absolute") |
| } |
| if !rel.IsRelative() { |
| panic("second argument to TraversalJoin must be relative") |
| } |
| |
| ret := make(Traversal, len(abs)+len(rel)) |
| copy(ret, abs) |
| copy(ret[len(abs):], rel) |
| return ret |
| } |
| |
| // TraverseRel applies the receiving traversal to the given value, returning |
| // the resulting value. This is supported only for relative traversals, |
| // and will panic if applied to an absolute traversal. |
| func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { |
| if !t.IsRelative() { |
| panic("can't use TraverseRel on an absolute traversal") |
| } |
| |
| current := val |
| var diags Diagnostics |
| for _, tr := range t { |
| var newDiags Diagnostics |
| current, newDiags = tr.TraversalStep(current) |
| diags = append(diags, newDiags...) |
| if newDiags.HasErrors() { |
| return cty.DynamicVal, diags |
| } |
| } |
| return current, diags |
| } |
| |
| // TraverseAbs applies the receiving traversal to the given eval context, |
| // returning the resulting value. This is supported only for absolute |
| // traversals, and will panic if applied to a relative traversal. |
| func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { |
| if t.IsRelative() { |
| panic("can't use TraverseAbs on a relative traversal") |
| } |
| |
| split := t.SimpleSplit() |
| root := split.Abs[0].(TraverseRoot) |
| name := root.Name |
| |
| thisCtx := ctx |
| hasNonNil := false |
| for thisCtx != nil { |
| if thisCtx.Variables == nil { |
| thisCtx = thisCtx.parent |
| continue |
| } |
| hasNonNil = true |
| val, exists := thisCtx.Variables[name] |
| if exists { |
| return split.Rel.TraverseRel(val) |
| } |
| thisCtx = thisCtx.parent |
| } |
| |
| if !hasNonNil { |
| return cty.DynamicVal, Diagnostics{ |
| { |
| Severity: DiagError, |
| Summary: "Variables not allowed", |
| Detail: "Variables may not be used here.", |
| Subject: &root.SrcRange, |
| }, |
| } |
| } |
| |
| suggestions := make([]string, 0, len(ctx.Variables)) |
| thisCtx = ctx |
| for thisCtx != nil { |
| for k := range thisCtx.Variables { |
| suggestions = append(suggestions, k) |
| } |
| thisCtx = thisCtx.parent |
| } |
| suggestion := nameSuggestion(name, suggestions) |
| if suggestion != "" { |
| suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) |
| } |
| |
| return cty.DynamicVal, Diagnostics{ |
| { |
| Severity: DiagError, |
| Summary: "Unknown variable", |
| Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion), |
| Subject: &root.SrcRange, |
| }, |
| } |
| } |
| |
| // IsRelative returns true if the receiver is a relative traversal, or false |
| // otherwise. |
| func (t Traversal) IsRelative() bool { |
| if len(t) == 0 { |
| return true |
| } |
| if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot { |
| return false |
| } |
| return true |
| } |
| |
| // SimpleSplit returns a TraversalSplit where the name lookup is the absolute |
| // part and the remainder is the relative part. Supported only for |
| // absolute traversals, and will panic if applied to a relative traversal. |
| // |
| // This can be used by applications that have a relatively-simple variable |
| // namespace where only the top-level is directly populated in the scope, with |
| // everything else handled by relative lookups from those initial values. |
| func (t Traversal) SimpleSplit() TraversalSplit { |
| if t.IsRelative() { |
| panic("can't use SimpleSplit on a relative traversal") |
| } |
| return TraversalSplit{ |
| Abs: t[0:1], |
| Rel: t[1:], |
| } |
| } |
| |
| // RootName returns the root name for a absolute traversal. Will panic if |
| // called on a relative traversal. |
| func (t Traversal) RootName() string { |
| if t.IsRelative() { |
| panic("can't use RootName on a relative traversal") |
| |
| } |
| return t[0].(TraverseRoot).Name |
| } |
| |
| // SourceRange returns the source range for the traversal. |
| func (t Traversal) SourceRange() Range { |
| if len(t) == 0 { |
| // Nothing useful to return here, but we'll return something |
| // that's correctly-typed at least. |
| return Range{} |
| } |
| |
| return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange()) |
| } |
| |
| // TraversalSplit represents a pair of traversals, the first of which is |
| // an absolute traversal and the second of which is relative to the first. |
| // |
| // This is used by calling applications that only populate prefixes of the |
| // traversals in the scope, with Abs representing the part coming from the |
| // scope and Rel representing the remaining steps once that part is |
| // retrieved. |
| type TraversalSplit struct { |
| Abs Traversal |
| Rel Traversal |
| } |
| |
| // TraverseAbs traverses from a scope to the value resulting from the |
| // absolute traversal. |
| func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) { |
| return t.Abs.TraverseAbs(ctx) |
| } |
| |
| // TraverseRel traverses from a given value, assumed to be the result of |
| // TraverseAbs on some scope, to a final result for the entire split traversal. |
| func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) { |
| return t.Rel.TraverseRel(val) |
| } |
| |
| // Traverse is a convenience function to apply TraverseAbs followed by |
| // TraverseRel. |
| func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) { |
| v1, diags := t.TraverseAbs(ctx) |
| if diags.HasErrors() { |
| return cty.DynamicVal, diags |
| } |
| v2, newDiags := t.TraverseRel(v1) |
| diags = append(diags, newDiags...) |
| return v2, diags |
| } |
| |
| // Join concatenates together the Abs and Rel parts to produce a single |
| // absolute traversal. |
| func (t TraversalSplit) Join() Traversal { |
| return TraversalJoin(t.Abs, t.Rel) |
| } |
| |
| // RootName returns the root name for the absolute part of the split. |
| func (t TraversalSplit) RootName() string { |
| return t.Abs.RootName() |
| } |
| |
| // A Traverser is a step within a Traversal. |
| type Traverser interface { |
| TraversalStep(cty.Value) (cty.Value, Diagnostics) |
| SourceRange() Range |
| isTraverserSigil() isTraverser |
| } |
| |
| // Embed this in a struct to declare it as a Traverser |
| type isTraverser struct { |
| } |
| |
| func (tr isTraverser) isTraverserSigil() isTraverser { |
| return isTraverser{} |
| } |
| |
| // TraverseRoot looks up a root name in a scope. It is used as the first step |
| // of an absolute Traversal, and cannot itself be traversed directly. |
| type TraverseRoot struct { |
| isTraverser |
| Name string |
| SrcRange Range |
| } |
| |
| // TraversalStep on a TraverseName immediately panics, because absolute |
| // traversals cannot be directly traversed. |
| func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) { |
| panic("Cannot traverse an absolute traversal") |
| } |
| |
| func (tn TraverseRoot) SourceRange() Range { |
| return tn.SrcRange |
| } |
| |
| // TraverseAttr looks up an attribute in its initial value. |
| type TraverseAttr struct { |
| isTraverser |
| Name string |
| SrcRange Range |
| } |
| |
| func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { |
| return GetAttr(val, tn.Name, &tn.SrcRange) |
| } |
| |
| func (tn TraverseAttr) SourceRange() Range { |
| return tn.SrcRange |
| } |
| |
| // TraverseIndex applies the index operation to its initial value. |
| type TraverseIndex struct { |
| isTraverser |
| Key cty.Value |
| SrcRange Range |
| } |
| |
| func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { |
| return Index(val, tn.Key, &tn.SrcRange) |
| } |
| |
| func (tn TraverseIndex) SourceRange() Range { |
| return tn.SrcRange |
| } |
| |
| // TraverseSplat applies the splat operation to its initial value. |
| type TraverseSplat struct { |
| isTraverser |
| Each Traversal |
| SrcRange Range |
| } |
| |
| func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) { |
| panic("TraverseSplat not yet implemented") |
| } |
| |
| func (tn TraverseSplat) SourceRange() Range { |
| return tn.SrcRange |
| } |