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