zclsyntax: parsing of nested blocks within bodies

This commit is contained in:
Martin Atkins 2017-05-30 07:52:22 -07:00
parent 2c90adb9e1
commit e953e1c18a
2 changed files with 281 additions and 3 deletions

View File

@ -22,7 +22,7 @@ func (p *parser) ParseBody(end TokenType) (*Body, zcl.Diagnostics) {
blocks := Blocks{}
var diags zcl.Diagnostics
startRange := p.NextRange()
startRange := p.PrevRange()
var endRange zcl.Range
Token:
@ -86,7 +86,7 @@ Token:
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
break Token
@ -107,9 +107,132 @@ Token:
}
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
}
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
// aren't allowed to contain any interpolations, such as block labels.
func (p *parser) parseQuotedStringLiteral() (string, zcl.Range, zcl.Diagnostics) {
@ -205,7 +328,16 @@ func (p *parser) recover(end TokenType) {
nest := 0
for {
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:
nest++
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
// NilToken if the given token isn't a bracketer.
//
@ -248,6 +414,15 @@ func (p *parser) oppositeBracket(ty TokenType) TokenType {
case TokenCHeredoc:
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:
return TokenNil
}

View File

@ -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{