github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/parser_traversal.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclsyntax
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  // ParseTraversalAbs parses an absolute traversal that is assumed to consume
    12  // all of the remaining tokens in the peeker. The usual parser recovery
    13  // behavior is not supported here because traversals are not expected to
    14  // be parsed as part of a larger program.
    15  func (p *parser) ParseTraversalAbs() (hcl.Traversal, hcl.Diagnostics) {
    16  	var ret hcl.Traversal
    17  	var diags hcl.Diagnostics
    18  
    19  	// Absolute traversal must always begin with a variable name
    20  	varTok := p.Read()
    21  	if varTok.Type != TokenIdent {
    22  		diags = append(diags, &hcl.Diagnostic{
    23  			Severity: hcl.DiagError,
    24  			Summary:  "Variable name required",
    25  			Detail:   "Must begin with a variable name.",
    26  			Subject:  &varTok.Range,
    27  		})
    28  		return ret, diags
    29  	}
    30  
    31  	varName := string(varTok.Bytes)
    32  	ret = append(ret, hcl.TraverseRoot{
    33  		Name:     varName,
    34  		SrcRange: varTok.Range,
    35  	})
    36  
    37  	for {
    38  		next := p.Peek()
    39  
    40  		if next.Type == TokenEOF {
    41  			return ret, diags
    42  		}
    43  
    44  		switch next.Type {
    45  		case TokenDot:
    46  			// Attribute access
    47  			dot := p.Read() // eat dot
    48  			nameTok := p.Read()
    49  			if nameTok.Type != TokenIdent {
    50  				if nameTok.Type == TokenStar {
    51  					diags = append(diags, &hcl.Diagnostic{
    52  						Severity: hcl.DiagError,
    53  						Summary:  "Attribute name required",
    54  						Detail:   "Splat expressions (.*) may not be used here.",
    55  						Subject:  &nameTok.Range,
    56  						Context:  hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(),
    57  					})
    58  				} else {
    59  					diags = append(diags, &hcl.Diagnostic{
    60  						Severity: hcl.DiagError,
    61  						Summary:  "Attribute name required",
    62  						Detail:   "Dot must be followed by attribute name.",
    63  						Subject:  &nameTok.Range,
    64  						Context:  hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(),
    65  					})
    66  				}
    67  				return ret, diags
    68  			}
    69  
    70  			attrName := string(nameTok.Bytes)
    71  			ret = append(ret, hcl.TraverseAttr{
    72  				Name:     attrName,
    73  				SrcRange: hcl.RangeBetween(dot.Range, nameTok.Range),
    74  			})
    75  		case TokenOBrack:
    76  			// Index
    77  			open := p.Read() // eat open bracket
    78  			next := p.Peek()
    79  
    80  			switch next.Type {
    81  			case TokenNumberLit:
    82  				tok := p.Read() // eat number
    83  				numVal, numDiags := p.numberLitValue(tok)
    84  				diags = append(diags, numDiags...)
    85  
    86  				close := p.Read()
    87  				if close.Type != TokenCBrack {
    88  					diags = append(diags, &hcl.Diagnostic{
    89  						Severity: hcl.DiagError,
    90  						Summary:  "Unclosed index brackets",
    91  						Detail:   "Index key must be followed by a closing bracket.",
    92  						Subject:  &close.Range,
    93  						Context:  hcl.RangeBetween(open.Range, close.Range).Ptr(),
    94  					})
    95  				}
    96  
    97  				ret = append(ret, hcl.TraverseIndex{
    98  					Key:      numVal,
    99  					SrcRange: hcl.RangeBetween(open.Range, close.Range),
   100  				})
   101  
   102  				if diags.HasErrors() {
   103  					return ret, diags
   104  				}
   105  
   106  			case TokenOQuote:
   107  				str, _, strDiags := p.parseQuotedStringLiteral()
   108  				diags = append(diags, strDiags...)
   109  
   110  				close := p.Read()
   111  				if close.Type != TokenCBrack {
   112  					diags = append(diags, &hcl.Diagnostic{
   113  						Severity: hcl.DiagError,
   114  						Summary:  "Unclosed index brackets",
   115  						Detail:   "Index key must be followed by a closing bracket.",
   116  						Subject:  &close.Range,
   117  						Context:  hcl.RangeBetween(open.Range, close.Range).Ptr(),
   118  					})
   119  				}
   120  
   121  				ret = append(ret, hcl.TraverseIndex{
   122  					Key:      cty.StringVal(str),
   123  					SrcRange: hcl.RangeBetween(open.Range, close.Range),
   124  				})
   125  
   126  				if diags.HasErrors() {
   127  					return ret, diags
   128  				}
   129  
   130  			default:
   131  				if next.Type == TokenStar {
   132  					diags = append(diags, &hcl.Diagnostic{
   133  						Severity: hcl.DiagError,
   134  						Summary:  "Attribute name required",
   135  						Detail:   "Splat expressions ([*]) may not be used here.",
   136  						Subject:  &next.Range,
   137  						Context:  hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
   138  					})
   139  				} else {
   140  					diags = append(diags, &hcl.Diagnostic{
   141  						Severity: hcl.DiagError,
   142  						Summary:  "Index value required",
   143  						Detail:   "Index brackets must contain either a literal number or a literal string.",
   144  						Subject:  &next.Range,
   145  						Context:  hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
   146  					})
   147  				}
   148  				return ret, diags
   149  			}
   150  
   151  		default:
   152  			diags = append(diags, &hcl.Diagnostic{
   153  				Severity: hcl.DiagError,
   154  				Summary:  "Invalid character",
   155  				Detail:   "Expected an attribute access or an index operator.",
   156  				Subject:  &next.Range,
   157  				Context:  hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
   158  			})
   159  			return ret, diags
   160  		}
   161  	}
   162  }