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  }