package hclsyntax import ( "github.com/hashicorp/hcl/v2" "github.com/zclconf/go-cty/cty" ) // ParseTraversalAbs parses an absolute traversal that is assumed to consume // all of the remaining tokens in the peeker. The usual parser recovery // behavior is not supported here because traversals are not expected to // be parsed as part of a larger program. func (p *parser) ParseTraversalAbs() (hcl.Traversal, hcl.Diagnostics) { var ret hcl.Traversal var diags hcl.Diagnostics // Absolute traversal must always begin with a variable name varTok := p.Read() if varTok.Type != TokenIdent { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Variable name required", Detail: "Must begin with a variable name.", Subject: &varTok.Range, }) return ret, diags } varName := string(varTok.Bytes) ret = append(ret, hcl.TraverseRoot{ Name: varName, SrcRange: varTok.Range, }) for { next := p.Peek() if next.Type == TokenEOF { return ret, diags } switch next.Type { case TokenDot: // Attribute access dot := p.Read() // eat dot nameTok := p.Read() if nameTok.Type != TokenIdent { if nameTok.Type == TokenStar { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Attribute name required", Detail: "Splat expressions (.*) may not be used here.", Subject: &nameTok.Range, Context: hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(), }) } else { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Attribute name required", Detail: "Dot must be followed by attribute name.", Subject: &nameTok.Range, Context: hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(), }) } return ret, diags } attrName := string(nameTok.Bytes) ret = append(ret, hcl.TraverseAttr{ Name: attrName, SrcRange: hcl.RangeBetween(dot.Range, nameTok.Range), }) case TokenOBrack: // Index open := p.Read() // eat open bracket next := p.Peek() switch next.Type { case TokenNumberLit: tok := p.Read() // eat number numVal, numDiags := p.numberLitValue(tok) diags = append(diags, numDiags...) close := p.Read() if close.Type != TokenCBrack { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unclosed index brackets", Detail: "Index key must be followed by a closing bracket.", Subject: &close.Range, Context: hcl.RangeBetween(open.Range, close.Range).Ptr(), }) } ret = append(ret, hcl.TraverseIndex{ Key: numVal, SrcRange: hcl.RangeBetween(open.Range, close.Range), }) if diags.HasErrors() { return ret, diags } case TokenOQuote: str, _, strDiags := p.parseQuotedStringLiteral() diags = append(diags, strDiags...) close := p.Read() if close.Type != TokenCBrack { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unclosed index brackets", Detail: "Index key must be followed by a closing bracket.", Subject: &close.Range, Context: hcl.RangeBetween(open.Range, close.Range).Ptr(), }) } ret = append(ret, hcl.TraverseIndex{ Key: cty.StringVal(str), SrcRange: hcl.RangeBetween(open.Range, close.Range), }) if diags.HasErrors() { return ret, diags } default: if next.Type == TokenStar { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Attribute name required", Detail: "Splat expressions ([*]) may not be used here.", Subject: &next.Range, Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(), }) } else { diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Index value required", Detail: "Index brackets must contain either a literal number or a literal string.", Subject: &next.Range, Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(), }) } return ret, diags } default: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid character", Detail: "Expected an attribute access or an index operator.", Subject: &next.Range, Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(), }) return ret, diags } } }