2017-05-30 02:28:10 +00:00
|
|
|
package zclsyntax
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/zclconf/go-zcl/zcl"
|
|
|
|
)
|
|
|
|
|
|
|
|
type parser struct {
|
|
|
|
*peeker
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) ParseBody(end TokenType) (*Body, zcl.Diagnostics) {
|
|
|
|
attrs := Attributes{}
|
|
|
|
blocks := Blocks{}
|
|
|
|
var diags zcl.Diagnostics
|
|
|
|
|
|
|
|
startRange := p.NextRange()
|
|
|
|
var endRange zcl.Range
|
|
|
|
|
|
|
|
Token:
|
|
|
|
for {
|
|
|
|
next := p.Peek()
|
|
|
|
if next.Type == end {
|
|
|
|
endRange = p.NextRange()
|
|
|
|
p.Read()
|
|
|
|
break Token
|
|
|
|
}
|
|
|
|
|
|
|
|
switch next.Type {
|
|
|
|
case TokenNewline:
|
|
|
|
p.Read()
|
|
|
|
continue
|
2017-05-30 02:58:45 +00:00
|
|
|
case TokenIdent:
|
2017-05-30 02:28:10 +00:00
|
|
|
item, itemDiags := p.ParseBodyItem()
|
|
|
|
diags = append(diags, itemDiags...)
|
|
|
|
switch titem := item.(type) {
|
|
|
|
case *Block:
|
|
|
|
blocks = append(blocks, titem)
|
|
|
|
case *Attribute:
|
|
|
|
if existing, exists := attrs[titem.Name]; exists {
|
|
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
|
|
Severity: zcl.DiagError,
|
|
|
|
Summary: "Attribute redefined",
|
|
|
|
Detail: fmt.Sprintf(
|
|
|
|
"The attribute %q was already defined at %s. Each attribute may be defined only once.",
|
|
|
|
titem.Name, existing.NameRange.String(),
|
|
|
|
),
|
|
|
|
Subject: &titem.NameRange,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
attrs[titem.Name] = titem
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// This should never happen for valid input, but may if a
|
|
|
|
// syntax error was detected in ParseBodyItem that prevented
|
|
|
|
// it from even producing a partially-broken item. In that
|
|
|
|
// case, it would've left at least one error in the diagnostics
|
|
|
|
// slice we already dealt with above.
|
|
|
|
//
|
|
|
|
// We'll assume ParseBodyItem attempted recovery to leave
|
|
|
|
// us in a reasonable position to try parsing the next item.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
bad := p.Read()
|
2017-05-30 02:58:45 +00:00
|
|
|
if bad.Type == TokenOQuote {
|
|
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
|
|
Severity: zcl.DiagError,
|
|
|
|
Summary: "Invalid attribute name",
|
|
|
|
Detail: "Attribute names must not be quoted.",
|
|
|
|
Subject: &bad.Range,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
diags = append(diags, &zcl.Diagnostic{
|
|
|
|
Severity: zcl.DiagError,
|
|
|
|
Summary: "Attribute or block definition required",
|
|
|
|
Detail: "An attribute or block definition is required here.",
|
|
|
|
Subject: &bad.Range,
|
|
|
|
})
|
|
|
|
}
|
2017-05-30 02:28:10 +00:00
|
|
|
endRange = p.NextRange() // 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Body{
|
|
|
|
Attributes: attrs,
|
|
|
|
Blocks: blocks,
|
|
|
|
|
|
|
|
SrcRange: zcl.RangeBetween(startRange, endRange),
|
|
|
|
EndRange: zcl.Range{
|
|
|
|
Filename: endRange.Filename,
|
|
|
|
Start: endRange.End,
|
|
|
|
End: endRange.End,
|
|
|
|
},
|
|
|
|
}, diags
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) ParseBodyItem() (Node, zcl.Diagnostics) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// recover seeks forward in the token stream until it finds TokenType "end",
|
|
|
|
// then returns with the peeker pointed at the following token.
|
|
|
|
//
|
|
|
|
// If the given token type is a bracketer, this function will additionally
|
|
|
|
// count nested instances of the brackets to try to leave the peeker at
|
|
|
|
// the end of the _current_ instance of that bracketer, skipping over any
|
|
|
|
// nested instances. This is a best-effort operation and may have
|
|
|
|
// unpredictable results on input with bad bracketer nesting.
|
|
|
|
func (p *parser) recover(end TokenType) {
|
|
|
|
start := p.oppositeBracket(end)
|
|
|
|
|
|
|
|
nest := 0
|
|
|
|
for {
|
|
|
|
tok := p.Read()
|
|
|
|
switch tok.Type {
|
|
|
|
case start:
|
|
|
|
nest++
|
|
|
|
case end:
|
|
|
|
if nest < 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
nest--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// oppositeBracket finds the bracket that opposes the given bracketer, or
|
|
|
|
// NilToken if the given token isn't a bracketer.
|
|
|
|
//
|
|
|
|
// "Bracketer", for the sake of this function, is one end of a matching
|
|
|
|
// open/close set of tokens that establish a bracketing context.
|
|
|
|
func (p *parser) oppositeBracket(ty TokenType) TokenType {
|
|
|
|
switch ty {
|
|
|
|
|
|
|
|
case TokenOBrace:
|
|
|
|
return TokenCBrace
|
|
|
|
case TokenOBrack:
|
|
|
|
return TokenCBrack
|
|
|
|
case TokenOParen:
|
|
|
|
return TokenCParen
|
|
|
|
case TokenOQuote:
|
|
|
|
return TokenCQuote
|
|
|
|
case TokenOHeredoc:
|
|
|
|
return TokenCHeredoc
|
|
|
|
|
|
|
|
case TokenCBrace:
|
|
|
|
return TokenOBrace
|
|
|
|
case TokenCBrack:
|
|
|
|
return TokenOBrack
|
|
|
|
case TokenCParen:
|
|
|
|
return TokenOParen
|
|
|
|
case TokenCQuote:
|
|
|
|
return TokenOQuote
|
|
|
|
case TokenCHeredoc:
|
|
|
|
return TokenOHeredoc
|
|
|
|
|
|
|
|
default:
|
|
|
|
return TokenNil
|
|
|
|
}
|
|
|
|
}
|