zclsyntax: parsing of nested blocks within bodies
This commit is contained in:
parent
2c90adb9e1
commit
e953e1c18a
@ -22,7 +22,7 @@ func (p *parser) ParseBody(end TokenType) (*Body, zcl.Diagnostics) {
|
|||||||
blocks := Blocks{}
|
blocks := Blocks{}
|
||||||
var diags zcl.Diagnostics
|
var diags zcl.Diagnostics
|
||||||
|
|
||||||
startRange := p.NextRange()
|
startRange := p.PrevRange()
|
||||||
var endRange zcl.Range
|
var endRange zcl.Range
|
||||||
|
|
||||||
Token:
|
Token:
|
||||||
@ -86,7 +86,7 @@ Token:
|
|||||||
Subject: &bad.Range,
|
Subject: &bad.Range,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
endRange = p.NextRange() // arbitrary, but somewhere inside the body means better diagnostics
|
endRange = p.PrevRange() // arbitrary, but somewhere inside the body means better diagnostics
|
||||||
|
|
||||||
p.recover(end) // attempt to recover to the token after the end of this body
|
p.recover(end) // attempt to recover to the token after the end of this body
|
||||||
break Token
|
break Token
|
||||||
@ -107,9 +107,132 @@ Token:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) ParseBodyItem() (Node, zcl.Diagnostics) {
|
func (p *parser) ParseBodyItem() (Node, zcl.Diagnostics) {
|
||||||
|
ident := p.Read()
|
||||||
|
if ident.Type != TokenIdent {
|
||||||
|
p.recoverAfterBodyItem()
|
||||||
|
return nil, zcl.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Attribute or block definition required",
|
||||||
|
Detail: "An attribute or block definition is required here.",
|
||||||
|
Subject: &ident.Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next := p.Peek()
|
||||||
|
|
||||||
|
switch next.Type {
|
||||||
|
case TokenEqual:
|
||||||
|
return p.finishParsingBodyAttribute(ident)
|
||||||
|
case TokenOQuote, TokenOBrace:
|
||||||
|
return p.finishParsingBodyBlock(ident)
|
||||||
|
default:
|
||||||
|
p.recoverAfterBodyItem()
|
||||||
|
return nil, zcl.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Attribute or block definition required",
|
||||||
|
Detail: "An attribute or block definition is required here. To define an attribute, use the equals sign \"=\" to introduce the attribute value.",
|
||||||
|
Subject: &ident.Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *parser) finishParsingBodyAttribute(ident Token) (Node, zcl.Diagnostics) {
|
||||||
|
panic("attribute parsing not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) finishParsingBodyBlock(ident Token) (Node, zcl.Diagnostics) {
|
||||||
|
var blockType = string(ident.Bytes)
|
||||||
|
var diags zcl.Diagnostics
|
||||||
|
var labels []string
|
||||||
|
var labelRanges []zcl.Range
|
||||||
|
|
||||||
|
var oBrace Token
|
||||||
|
|
||||||
|
Token:
|
||||||
|
for {
|
||||||
|
tok := p.Peek()
|
||||||
|
|
||||||
|
switch tok.Type {
|
||||||
|
|
||||||
|
case TokenOBrace:
|
||||||
|
oBrace = p.Read()
|
||||||
|
break Token
|
||||||
|
|
||||||
|
case TokenOQuote:
|
||||||
|
label, labelRange, labelDiags := p.parseQuotedStringLiteral()
|
||||||
|
diags = append(diags, labelDiags...)
|
||||||
|
labels = append(labels, label)
|
||||||
|
labelRanges = append(labelRanges, labelRange)
|
||||||
|
|
||||||
|
default:
|
||||||
|
switch tok.Type {
|
||||||
|
case TokenEqual:
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Invalid block definition",
|
||||||
|
Detail: "The equals sign \"=\" indicates an attribute definition, and must not be used when defining a block.",
|
||||||
|
Subject: &tok.Range,
|
||||||
|
Context: zcl.RangeBetween(ident.Range, tok.Range).Ptr(),
|
||||||
|
})
|
||||||
|
case TokenNewline:
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Invalid block definition",
|
||||||
|
Detail: "A block definition must have block content delimited by \"{\" and \"}\", starting on the same line as the block header.",
|
||||||
|
Subject: &tok.Range,
|
||||||
|
Context: zcl.RangeBetween(ident.Range, tok.Range).Ptr(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
if !p.recovery {
|
||||||
|
diags = append(diags, &zcl.Diagnostic{
|
||||||
|
Severity: zcl.DiagError,
|
||||||
|
Summary: "Invalid block definition",
|
||||||
|
Detail: "Either a quoted string block label or an opening brace (\"{\") is expected here.",
|
||||||
|
Subject: &tok.Range,
|
||||||
|
Context: zcl.RangeBetween(ident.Range, tok.Range).Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.recoverAfterBodyItem()
|
||||||
|
|
||||||
|
return &Block{
|
||||||
|
Type: blockType,
|
||||||
|
Labels: labels,
|
||||||
|
Body: nil,
|
||||||
|
|
||||||
|
TypeRange: ident.Range,
|
||||||
|
LabelRanges: labelRanges,
|
||||||
|
OpenBraceRange: ident.Range, // placeholder
|
||||||
|
CloseBraceRange: ident.Range, // placeholder
|
||||||
|
}, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we fall out here, the peeker is pointed just after our opening
|
||||||
|
// brace, so we can begin our nested body parsing.
|
||||||
|
body, bodyDiags := p.ParseBody(TokenCBrace)
|
||||||
|
diags = append(diags, bodyDiags...)
|
||||||
|
cBraceRange := p.PrevRange()
|
||||||
|
|
||||||
|
return &Block{
|
||||||
|
Type: blockType,
|
||||||
|
Labels: labels,
|
||||||
|
Body: body,
|
||||||
|
|
||||||
|
TypeRange: ident.Range,
|
||||||
|
LabelRanges: labelRanges,
|
||||||
|
OpenBraceRange: oBrace.Range,
|
||||||
|
CloseBraceRange: cBraceRange,
|
||||||
|
}, diags
|
||||||
|
}
|
||||||
|
|
||||||
// parseQuotedStringLiteral is a helper for parsing quoted strings that
|
// parseQuotedStringLiteral is a helper for parsing quoted strings that
|
||||||
// aren't allowed to contain any interpolations, such as block labels.
|
// aren't allowed to contain any interpolations, such as block labels.
|
||||||
func (p *parser) parseQuotedStringLiteral() (string, zcl.Range, zcl.Diagnostics) {
|
func (p *parser) parseQuotedStringLiteral() (string, zcl.Range, zcl.Diagnostics) {
|
||||||
@ -205,7 +328,16 @@ func (p *parser) recover(end TokenType) {
|
|||||||
nest := 0
|
nest := 0
|
||||||
for {
|
for {
|
||||||
tok := p.Read()
|
tok := p.Read()
|
||||||
switch tok.Type {
|
ty := tok.Type
|
||||||
|
if end == TokenTemplateSeqEnd && ty == TokenTemplateControl {
|
||||||
|
// normalize so that our matching behavior can work, since
|
||||||
|
// TokenTemplateControl/TokenTemplateInterp are asymmetrical
|
||||||
|
// with TokenTemplateSeqEnd and thus we need to count both
|
||||||
|
// openers if that's the closer we're looking for.
|
||||||
|
ty = TokenTemplateInterp
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ty {
|
||||||
case start:
|
case start:
|
||||||
nest++
|
nest++
|
||||||
case end:
|
case end:
|
||||||
@ -218,6 +350,40 @@ func (p *parser) recover(end TokenType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recoverOver seeks forward in the token stream until it finds a block
|
||||||
|
// starting with TokenType "start", then finds the corresponding end token,
|
||||||
|
// leaving the peeker pointed at the token after that end token.
|
||||||
|
//
|
||||||
|
// The given token type _must_ be a bracketer. For example, if the given
|
||||||
|
// start token is TokenOBrace then the parser will be left at the _end_ of
|
||||||
|
// the next brace-delimited block encountered, or at EOF if no such block
|
||||||
|
// is found or it is unclosed.
|
||||||
|
func (p *parser) recoverOver(start TokenType) {
|
||||||
|
end := p.oppositeBracket(start)
|
||||||
|
|
||||||
|
// find the opening bracket first
|
||||||
|
Token:
|
||||||
|
for {
|
||||||
|
tok := p.Read()
|
||||||
|
switch tok.Type {
|
||||||
|
case start:
|
||||||
|
break Token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now use our existing recover function to locate the _end_ of the
|
||||||
|
// container we've found.
|
||||||
|
p.recover(end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) recoverAfterBodyItem() {
|
||||||
|
// TODO: Seek forward until we find a newline that isn't inside any
|
||||||
|
// bracketer, and return with the peeker placed after it so we're
|
||||||
|
// ready to try to parse another body item.
|
||||||
|
p.recovery = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// oppositeBracket finds the bracket that opposes the given bracketer, or
|
// oppositeBracket finds the bracket that opposes the given bracketer, or
|
||||||
// NilToken if the given token isn't a bracketer.
|
// NilToken if the given token isn't a bracketer.
|
||||||
//
|
//
|
||||||
@ -248,6 +414,15 @@ func (p *parser) oppositeBracket(ty TokenType) TokenType {
|
|||||||
case TokenCHeredoc:
|
case TokenCHeredoc:
|
||||||
return TokenOHeredoc
|
return TokenOHeredoc
|
||||||
|
|
||||||
|
case TokenTemplateControl:
|
||||||
|
return TokenTemplateSeqEnd
|
||||||
|
case TokenTemplateInterp:
|
||||||
|
return TokenTemplateSeqEnd
|
||||||
|
case TokenTemplateSeqEnd:
|
||||||
|
// This is ambigous, but we return Interp here because that's
|
||||||
|
// what's assumed by the "recover" method.
|
||||||
|
return TokenTemplateInterp
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return TokenNil
|
return TokenNil
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,109 @@ func TestParseConfig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`block {}`,
|
||||||
|
0,
|
||||||
|
&Body{
|
||||||
|
Attributes: Attributes{},
|
||||||
|
Blocks: Blocks{
|
||||||
|
&Block{
|
||||||
|
Type: "block",
|
||||||
|
Labels: nil,
|
||||||
|
Body: &Body{
|
||||||
|
Attributes: Attributes{},
|
||||||
|
Blocks: Blocks{},
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 7, Byte: 6},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
},
|
||||||
|
EndRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TypeRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||||
|
},
|
||||||
|
LabelRanges: nil,
|
||||||
|
OpenBraceRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 7, Byte: 6},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 8, Byte: 7},
|
||||||
|
},
|
||||||
|
CloseBraceRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 8, Byte: 7},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
},
|
||||||
|
EndRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 9, Byte: 8},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`block "foo" {}`,
|
||||||
|
0,
|
||||||
|
&Body{
|
||||||
|
Attributes: Attributes{},
|
||||||
|
Blocks: Blocks{
|
||||||
|
&Block{
|
||||||
|
Type: "block",
|
||||||
|
Labels: []string{"foo"},
|
||||||
|
Body: &Body{
|
||||||
|
Attributes: Attributes{},
|
||||||
|
Blocks: Blocks{},
|
||||||
|
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
},
|
||||||
|
EndRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TypeRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||||
|
},
|
||||||
|
LabelRanges: []zcl.Range{
|
||||||
|
{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 7, Byte: 6},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OpenBraceRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||||
|
},
|
||||||
|
CloseBraceRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SrcRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
},
|
||||||
|
EndRange: zcl.Range{
|
||||||
|
Start: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
End: zcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
prettyConfig := &pretty.Config{
|
prettyConfig := &pretty.Config{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user