package parser
import (
"fmt"
"strings"
"github.com/grafana/agent/pkg/river/ast"
"github.com/grafana/agent/pkg/river/diag"
"github.com/grafana/agent/pkg/river/scanner"
"github.com/grafana/agent/pkg/river/token"
)
type parser struct {
file *token.File
diags diag.Diagnostics
scanner *scanner.Scanner
comments []ast.CommentGroup
pos token.Pos
tok token.Token
lit string
lastError token.Position
}
func newParser(filename string, src []byte) *parser {
file := token.NewFile(filename)
p := &parser{
file: file,
}
p.scanner = scanner.New(file, src, func(pos token.Pos, msg string) {
p.diags.Add(diag.Diagnostic{
Severity: diag.SeverityLevelError,
StartPos: file.PositionFor(pos),
Message: msg,
})
}, scanner.IncludeComments)
p.next()
return p
}
func (p *parser) next() {
p.next0()
for p.tok == token.COMMENT {
p.consumeCommentGroup()
}
}
func (p *parser) next0() { p.pos, p.tok, p.lit = p.scanner.Scan() }
func (p *parser) consumeCommentGroup() {
var list []*ast.Comment
endline := p.pos.Position().Line
for p.tok == token.COMMENT && p.pos.Position().Line <= endline+1 {
var comment *ast.Comment
comment, endline = p.consumeComment()
list = append(list, comment)
}
p.comments = append(p.comments, ast.CommentGroup(list))
}
func (p *parser) consumeComment() (comment *ast.Comment, endline int) {
endline = p.pos.Position().Line
if p.lit[1] == '*' {
for i := 0; i < len(p.lit); i++ {
if p.lit[i] == '\n' {
endline++
}
}
}
comment = &ast.Comment{StartPos: p.pos, Text: p.lit}
p.next0()
return
}
func (p *parser) advance(to token.Token) {
for p.tok != token.EOF {
if p.tok == to {
return
}
p.next()
}
}
func (p *parser) advanceAny(to map[token.Token]struct{}) {
for p.tok != token.EOF {
if _, inSet := to[p.tok]; inSet {
return
}
p.next()
}
}
func (p *parser) expect(t token.Token) (pos token.Pos, tok token.Token, lit string) {
pos, tok, lit = p.pos, p.tok, p.lit
if tok != t {
p.addErrorf("expected %s, got %s", t, p.tok)
}
p.next()
return
}
func (p *parser) addErrorf(format string, args ...interface{}) {
pos := p.file.PositionFor(p.pos)
if p.lastError.Line == pos.Line {
return
}
p.lastError = pos
p.diags.Add(diag.Diagnostic{
Severity: diag.SeverityLevelError,
StartPos: pos,
Message: fmt.Sprintf(format, args...),
})
}
func (p *parser) ParseFile() *ast.File {
body := p.parseBody(token.EOF)
return &ast.File{
Name: p.file.Name(),
Body: body,
Comments: p.comments,
}
}
func (p *parser) parseBody(until token.Token) ast.Body {
var body ast.Body
for p.tok != until && p.tok != token.EOF {
stmt := p.parseStatement()
if stmt != nil {
body = append(body, stmt)
}
if p.tok == until {
break
}
if p.tok != token.TERMINATOR {
p.addErrorf("expected %s, got %s", token.TERMINATOR, p.tok)
p.consumeStatement()
}
p.next()
}
return body
}
func (p *parser) consumeStatement() {
var curlyPairs, brackPairs, parenPairs int
for p.tok != token.EOF {
switch p.tok {
case token.LCURLY:
curlyPairs++
case token.RCURLY:
curlyPairs--
case token.LBRACK:
brackPairs++
case token.RBRACK:
brackPairs--
case token.LPAREN:
parenPairs++
case token.RPAREN:
parenPairs--
}
if p.tok == token.TERMINATOR {
if curlyPairs <= 0 && brackPairs <= 0 && parenPairs <= 0 {
return
}
}
p.next()
}
}
func (p *parser) parseStatement() ast.Stmt {
blockName := p.parseBlockName()
if blockName == nil {
p.advance(token.IDENT)
return nil
}
switch p.tok {
case token.ASSIGN:
p.next()
if len(blockName.Fragments) != 1 {
attrName := strings.Join(blockName.Fragments, ".")
p.diags.Add(diag.Diagnostic{
Severity: diag.SeverityLevelError,
StartPos: blockName.Start.Position(),
EndPos: blockName.Start.Add(len(attrName) - 1).Position(),
Message: `attribute names may only consist of a single identifier with no "."`,
})
} else if blockName.LabelPos != token.NoPos {
p.diags.Add(diag.Diagnostic{
Severity: diag.SeverityLevelError,
StartPos: blockName.LabelPos.Position(),
EndPos: blockName.LabelPos.Add(len(blockName.Label) + 1).Position(),
Message: `attribute names may not have labels`,
})
}
return &ast.AttributeStmt{
Name: &ast.Ident{
Name: blockName.Fragments[0],
NamePos: blockName.Start,
},
Value: p.ParseExpression(),
}
case token.LCURLY:
block := &ast.BlockStmt{
Name: blockName.Fragments,
NamePos: blockName.Start,
Label: blockName.Label,
LabelPos: blockName.LabelPos,
}
block.LCurlyPos, _, _ = p.expect(token.LCURLY)
block.Body = p.parseBody(token.RCURLY)
block.RCurlyPos, _, _ = p.expect(token.RCURLY)
return block
default:
if blockName.ValidAttribute() {
p.addErrorf("expected attribute assignment or block body, got %s", p.tok)
} else {
p.addErrorf("expected block body, got %s", p.tok)
}
p.advance(token.IDENT)
return nil
}
}
func (p *parser) parseBlockName() *blockName {
if p.tok != token.IDENT {
p.addErrorf("expected identifier, got %s", p.tok)
return nil
}
var bn blockName
bn.Fragments = append(bn.Fragments, p.lit)
bn.Start = p.pos
p.next()
for p.tok == token.DOT {
p.next()
if p.tok != token.IDENT {
p.addErrorf("expected identifier, got %s", p.tok)
}
bn.Fragments = append(bn.Fragments, p.lit)
p.next()
}
if p.tok != token.ASSIGN && p.tok != token.LCURLY {
if p.tok == token.STRING {
if len(p.lit) > 2 {
bn.Label = p.lit[1 : len(p.lit)-1]
if !isValidIdentifier(bn.Label) {
p.addErrorf("expected block label to be a valid identifier")
}
}
bn.LabelPos = p.pos
} else {
p.addErrorf("expected block label, got %s", p.tok)
}
p.next()
}
return &bn
}
type blockName struct {
Fragments []string
Label string
Start token.Pos
LabelPos token.Pos
}
func (n blockName) ValidAttribute() bool {
return len(n.Fragments) == 1 && n.Label == ""
}
func (p *parser) ParseExpression() ast.Expr {
return p.parseBinOp(1)
}
func (p *parser) parseBinOp(inPrec int) ast.Expr {
lhs := p.parsePowExpr()
for {
tok, pos, prec := p.tok, p.pos, p.tok.BinaryPrecedence()
if prec < inPrec {
return lhs
}
p.next()
rhs := p.parseBinOp(prec + 1)
lhs = &ast.BinaryExpr{
Left: lhs,
Kind: tok,
KindPos: pos,
Right: rhs,
}
}
}
func (p *parser) parsePowExpr() ast.Expr {
lhs := p.parseUnaryExpr()
if p.tok == token.POW {
pos := p.pos
p.next()
return &ast.BinaryExpr{
Left: lhs,
Kind: token.POW,
KindPos: pos,
Right: p.parsePowExpr(),
}
}
return lhs
}
func (p *parser) parseUnaryExpr() ast.Expr {
if isUnaryOp(p.tok) {
op, pos := p.tok, p.pos
p.next()
return &ast.UnaryExpr{
Kind: op,
KindPos: pos,
Value: p.parseUnaryExpr(),
}
}
primary := p.parsePrimaryExpr()
NextOper:
for {
switch p.tok {
case token.DOT:
p.next()
namePos, _, name := p.expect(token.IDENT)
primary = &ast.AccessExpr{
Value: primary,
Name: &ast.Ident{
Name: name,
NamePos: namePos,
},
}
case token.LBRACK:
lBrack, _, _ := p.expect(token.LBRACK)
index := p.ParseExpression()
rBrack, _, _ := p.expect(token.RBRACK)
primary = &ast.IndexExpr{
Value: primary,
LBrackPos: lBrack,
Index: index,
RBrackPos: rBrack,
}
case token.LPAREN:
var args []ast.Expr
lParen, _, _ := p.expect(token.LPAREN)
if p.tok != token.RPAREN {
args = p.parseExpressionList(token.RPAREN)
}
rParen, _, _ := p.expect(token.RPAREN)
primary = &ast.CallExpr{
Value: primary,
LParenPos: lParen,
Args: args,
RParenPos: rParen,
}
case token.STRING, token.LCURLY:
if p.tok == token.STRING {
p.next()
}
if _, tok, _ := p.expect(token.LCURLY); tok != token.LCURLY {
p.consumeStatement()
return primary
}
p.parseBody(token.RCURLY)
end, tok, _ := p.expect(token.RCURLY)
if tok != token.RCURLY {
p.consumeStatement()
return primary
}
p.diags.Add(diag.Diagnostic{
Severity: diag.SeverityLevelError,
StartPos: ast.StartPos(primary).Position(),
EndPos: end.Position(),
Message: "cannot use a block as an expression",
})
default:
break NextOper
}
}
return primary
}
func isUnaryOp(tok token.Token) bool {
switch tok {
case token.NOT, token.SUB:
return true
default:
return false
}
}
func (p *parser) parsePrimaryExpr() ast.Expr {
switch p.tok {
case token.IDENT:
res := &ast.IdentifierExpr{
Ident: &ast.Ident{
Name: p.lit,
NamePos: p.pos,
},
}
p.next()
return res
case token.STRING, token.NUMBER, token.FLOAT, token.BOOL, token.NULL:
res := &ast.LiteralExpr{
Kind: p.tok,
Value: p.lit,
ValuePos: p.pos,
}
p.next()
return res
case token.LPAREN:
lParen, _, _ := p.expect(token.LPAREN)
expr := p.ParseExpression()
rParen, _, _ := p.expect(token.RPAREN)
return &ast.ParenExpr{
LParenPos: lParen,
Inner: expr,
RParenPos: rParen,
}
case token.LBRACK:
var res ast.ArrayExpr
res.LBrackPos, _, _ = p.expect(token.LBRACK)
if p.tok != token.RBRACK {
res.Elements = p.parseExpressionList(token.RBRACK)
}
res.RBrackPos, _, _ = p.expect(token.RBRACK)
return &res
case token.LCURLY:
var res ast.ObjectExpr
res.LCurlyPos, _, _ = p.expect(token.LCURLY)
if p.tok != token.RBRACK {
res.Fields = p.parseFieldList(token.RCURLY)
}
res.RCurlyPos, _, _ = p.expect(token.RCURLY)
return &res
}
p.addErrorf("expected expression, got %s", p.tok)
res := &ast.LiteralExpr{Kind: token.NULL, Value: "null", ValuePos: p.pos}
p.advanceAny(statementEnd)
return res
}
var statementEnd = map[token.Token]struct{}{
token.TERMINATOR: {},
token.RPAREN: {},
token.RCURLY: {},
token.RBRACK: {},
token.COMMA: {},
}
func (p *parser) parseExpressionList(until token.Token) []ast.Expr {
var exprs []ast.Expr
for p.tok != until && p.tok != token.EOF {
exprs = append(exprs, p.ParseExpression())
if p.tok == until {
break
}
if p.tok != token.COMMA {
p.addErrorf("missing ',' in expression list")
}
p.next()
}
return exprs
}
func (p *parser) parseFieldList(until token.Token) []*ast.ObjectField {
var fields []*ast.ObjectField
for p.tok != until && p.tok != token.EOF {
fields = append(fields, p.parseField())
if p.tok == until {
break
}
if p.tok != token.COMMA {
p.addErrorf("missing ',' in field list")
}
p.next()
}
return fields
}
func (p *parser) parseField() *ast.ObjectField {
var field ast.ObjectField
if p.tok == token.STRING || p.tok == token.IDENT {
field.Name = &ast.Ident{
Name: p.lit,
NamePos: p.pos,
}
if p.tok == token.STRING && len(p.lit) > 2 {
field.Name.Name = p.lit[1 : len(p.lit)-1]
field.Quoted = true
}
p.next()
} else {
p.addErrorf("expected field name (string or identifier), got %s", p.tok)
p.advance(token.ASSIGN)
}
p.expect(token.ASSIGN)
field.Value = p.ParseExpression()
return &field
}
func isValidIdentifier(in string) bool {
s := scanner.New(nil, []byte(in), nil, 0)
_, tok, lit := s.Scan()
return tok == token.IDENT && lit == in
}