2017-09-11 23:40:37 +00:00
|
|
|
package hcl
|
2017-05-22 15:05:19 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2017-05-28 00:35:44 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2017-05-22 15:05:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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) {
|
2017-06-05 14:55:33 +00:00
|
|
|
if !t.IsRelative() {
|
|
|
|
panic("can't use TraverseRel on an absolute traversal")
|
|
|
|
}
|
|
|
|
|
2017-05-22 15:05:19 +00:00
|
|
|
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) {
|
2017-06-05 14:55:33 +00:00
|
|
|
if t.IsRelative() {
|
|
|
|
panic("can't use TraverseAbs on a relative traversal")
|
|
|
|
}
|
|
|
|
|
|
|
|
split := t.SimpleSplit()
|
|
|
|
root := split.Abs[0].(TraverseRoot)
|
|
|
|
name := root.Name
|
|
|
|
|
2017-06-16 14:15:14 +00:00
|
|
|
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 {
|
2017-06-05 14:55:33 +00:00
|
|
|
return cty.DynamicVal, Diagnostics{
|
|
|
|
{
|
|
|
|
Severity: DiagError,
|
|
|
|
Summary: "Variables not allowed",
|
|
|
|
Detail: "Variables may not be used here.",
|
|
|
|
Subject: &root.SrcRange,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-14 15:54:12 +00:00
|
|
|
suggestions := make([]string, 0, len(ctx.Variables))
|
|
|
|
thisCtx = ctx
|
|
|
|
for thisCtx != nil {
|
|
|
|
for k := range thisCtx.Variables {
|
|
|
|
suggestions = append(suggestions, k)
|
2017-06-05 14:55:33 +00:00
|
|
|
}
|
2017-06-14 15:54:12 +00:00
|
|
|
thisCtx = thisCtx.parent
|
|
|
|
}
|
|
|
|
suggestion := nameSuggestion(name, suggestions)
|
|
|
|
if suggestion != "" {
|
|
|
|
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
2017-06-05 14:55:33 +00:00
|
|
|
}
|
|
|
|
|
2017-06-14 15:54:12 +00:00
|
|
|
return cty.DynamicVal, Diagnostics{
|
|
|
|
{
|
|
|
|
Severity: DiagError,
|
|
|
|
Summary: "Unknown variable",
|
|
|
|
Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion),
|
|
|
|
Subject: &root.SrcRange,
|
|
|
|
},
|
|
|
|
}
|
2017-05-22 15:05:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2017-06-05 14:55:33 +00:00
|
|
|
return false
|
2017-05-22 15:05:19 +00:00
|
|
|
}
|
2017-06-05 14:55:33 +00:00
|
|
|
return true
|
2017-05-22 15:05:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-01-14 07:01:11 +00:00
|
|
|
// 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())
|
|
|
|
}
|
|
|
|
|
2017-05-22 15:05:19 +00:00
|
|
|
// 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)
|
2018-01-14 07:01:11 +00:00
|
|
|
SourceRange() Range
|
2017-05-22 15:05:19 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-01-14 07:01:11 +00:00
|
|
|
func (tn TraverseRoot) SourceRange() Range {
|
|
|
|
return tn.SrcRange
|
|
|
|
}
|
|
|
|
|
2017-05-22 15:05:19 +00:00
|
|
|
// 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) {
|
2019-01-30 22:52:18 +00:00
|
|
|
return GetAttr(val, tn.Name, &tn.SrcRange)
|
2017-05-22 15:05:19 +00:00
|
|
|
}
|
2017-05-23 00:54:58 +00:00
|
|
|
|
2018-01-14 07:01:11 +00:00
|
|
|
func (tn TraverseAttr) SourceRange() Range {
|
|
|
|
return tn.SrcRange
|
|
|
|
}
|
|
|
|
|
2017-05-23 00:54:58 +00:00
|
|
|
// 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) {
|
2017-06-05 14:31:59 +00:00
|
|
|
return Index(val, tn.Key, &tn.SrcRange)
|
2017-05-23 00:54:58 +00:00
|
|
|
}
|
2017-05-23 01:48:06 +00:00
|
|
|
|
2018-01-14 07:01:11 +00:00
|
|
|
func (tn TraverseIndex) SourceRange() Range {
|
|
|
|
return tn.SrcRange
|
|
|
|
}
|
|
|
|
|
2017-05-23 01:48:06 +00:00
|
|
|
// 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")
|
|
|
|
}
|
2018-01-14 07:01:11 +00:00
|
|
|
|
|
|
|
func (tn TraverseSplat) SourceRange() Range {
|
|
|
|
return tn.SrcRange
|
|
|
|
}
|