hcl/parser/parser.go

369 lines
8.3 KiB
Go
Raw Normal View History

2015-10-25 15:14:16 +00:00
// Package parser implements a parser for HCL (HashiCorp Configuration
// Language)
package parser
2015-10-07 22:38:39 +00:00
2015-10-11 23:27:43 +00:00
import (
2015-10-12 19:53:40 +00:00
"errors"
2015-10-11 23:27:43 +00:00
"fmt"
2015-10-16 20:12:26 +00:00
"github.com/fatih/hcl/ast"
2015-10-11 23:27:43 +00:00
"github.com/fatih/hcl/scanner"
2015-10-16 20:12:26 +00:00
"github.com/fatih/hcl/token"
2015-10-11 23:27:43 +00:00
)
2015-10-07 22:38:39 +00:00
type Parser struct {
sc *scanner.Scanner
// Last read token
tok token.Token
comments []*ast.CommentGroup
leadComment *ast.CommentGroup // last lead comment
lineComment *ast.CommentGroup // last line comment
2015-10-11 23:27:43 +00:00
enableTrace bool
indent int
n int // buffer size (max = 1)
2015-10-07 22:38:39 +00:00
}
func newParser(src []byte) *Parser {
2015-10-07 22:38:39 +00:00
return &Parser{
sc: scanner.New(src),
}
}
// Parse returns the fully parsed source and returns the abstract syntax tree.
2015-10-30 19:51:35 +00:00
func Parse(src []byte) (*ast.File, error) {
p := newParser(src)
return p.Parse()
}
var errEofToken = errors.New("EOF token found")
// Parse returns the fully parsed source and returns the abstract syntax tree.
2015-10-30 19:51:35 +00:00
func (p *Parser) Parse() (*ast.File, error) {
f := &ast.File{}
var err error
f.Node, err = p.objectList()
if err != nil {
return nil, err
}
f.Comments = p.comments
return f, nil
2015-10-18 22:30:14 +00:00
}
2015-10-26 18:37:17 +00:00
func (p *Parser) objectList() (*ast.ObjectList, error) {
2015-10-18 22:30:14 +00:00
defer un(trace(p, "ParseObjectList"))
node := &ast.ObjectList{}
2015-10-18 22:30:14 +00:00
for {
n, err := p.objectItem()
2015-10-18 22:30:14 +00:00
if err == errEofToken {
break // we are finished
}
// we don't return a nil node, because might want to use already
// collected items.
2015-10-18 22:30:14 +00:00
if err != nil {
return node, err
}
node.Add(n)
2015-10-07 22:38:39 +00:00
}
2015-10-12 07:37:37 +00:00
return node, nil
}
func (p *Parser) consumeComment() (comment *ast.Comment, endline int) {
endline = p.tok.Pos.Line
2015-10-18 22:30:14 +00:00
// count the endline if it's multiline comment, ie starting with /*
if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' {
// don't use range here - no need to decode Unicode code points
for i := 0; i < len(p.tok.Text); i++ {
if p.tok.Text[i] == '\n' {
endline++
}
}
}
2015-10-18 22:30:14 +00:00
comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text}
p.tok = p.sc.Scan()
return
}
func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
var list []*ast.Comment
endline = p.tok.Pos.Line
for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n {
var comment *ast.Comment
comment, endline = p.consumeComment()
list = append(list, comment)
2015-10-18 22:30:14 +00:00
}
// add comment group to the comments list
comments = &ast.CommentGroup{List: list}
p.comments = append(p.comments, comments)
return
2015-10-18 22:30:14 +00:00
}
2015-10-26 18:37:17 +00:00
// objectItem parses a single object item
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
defer un(trace(p, "ParseObjectItem"))
2015-10-11 23:27:43 +00:00
2015-10-26 18:37:17 +00:00
keys, err := p.objectKey()
if err != nil {
return nil, err
}
2015-10-26 22:26:51 +00:00
o := &ast.ObjectItem{
Keys: keys,
}
2015-10-16 11:44:11 +00:00
2015-10-26 22:26:51 +00:00
if p.leadComment != nil {
o.LeadComment = p.leadComment
p.leadComment = nil
}
2015-10-26 22:26:51 +00:00
switch p.tok.Type {
case token.ASSIGN:
o.Assign = p.tok.Pos
2015-10-26 18:37:17 +00:00
o.Val, err = p.object()
2015-10-16 11:44:11 +00:00
if err != nil {
return nil, err
}
2015-10-16 20:12:26 +00:00
case token.LBRACE:
2015-10-26 18:37:17 +00:00
o.Val, err = p.objectType()
if err != nil {
return nil, err
}
2015-10-26 22:26:51 +00:00
}
2015-10-26 22:26:51 +00:00
// do a look-ahead for line comment
p.scan()
if o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
o.LineComment = p.lineComment
p.lineComment = nil
2015-10-16 11:16:12 +00:00
}
2015-10-26 22:26:51 +00:00
p.unscan()
return o, nil
2015-10-16 19:57:56 +00:00
}
2015-10-26 18:37:17 +00:00
// objectKey parses an object key and returns a ObjectKey AST
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
keyCount := 0
2015-10-18 22:30:14 +00:00
keys := make([]*ast.ObjectKey, 0)
2015-10-16 11:16:12 +00:00
for {
tok := p.scan()
switch tok.Type {
2015-10-18 22:30:14 +00:00
case token.EOF:
return nil, errEofToken
2015-10-16 20:12:26 +00:00
case token.ASSIGN:
2015-10-16 11:44:11 +00:00
// assignment or object only, but not nested objects. this is not
// allowed: `foo bar = {}`
if keyCount > 1 {
2015-10-18 22:30:14 +00:00
return nil, fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type)
2015-10-16 11:16:12 +00:00
}
if keyCount == 0 {
return nil, errors.New("no keys found!!!")
}
2015-10-16 11:16:12 +00:00
return keys, nil
2015-10-16 20:12:26 +00:00
case token.LBRACE:
2015-10-16 11:16:12 +00:00
// object
return keys, nil
2015-10-16 20:12:26 +00:00
case token.IDENT, token.STRING:
keyCount++
2015-10-18 22:30:14 +00:00
keys = append(keys, &ast.ObjectKey{Token: p.tok})
case token.ILLEGAL:
fmt.Println("illegal")
2015-10-16 11:16:12 +00:00
default:
2015-10-18 22:30:14 +00:00
return nil, fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type)
2015-10-16 11:16:12 +00:00
}
}
}
2015-10-26 18:37:17 +00:00
// object parses any type of object, such as number, bool, string, object or
2015-10-18 22:30:14 +00:00
// list.
2015-10-26 18:37:17 +00:00
func (p *Parser) object() (ast.Node, error) {
2015-10-18 22:30:14 +00:00
defer un(trace(p, "ParseType"))
tok := p.scan()
switch tok.Type {
case token.NUMBER, token.FLOAT, token.BOOL, token.STRING:
2015-10-26 18:37:17 +00:00
return p.literalType()
2015-10-18 22:30:14 +00:00
case token.LBRACE:
2015-10-26 18:37:17 +00:00
return p.objectType()
2015-10-18 22:30:14 +00:00
case token.LBRACK:
2015-10-26 18:37:17 +00:00
return p.listType()
2015-10-18 22:30:14 +00:00
case token.COMMENT:
// implement comment
case token.EOF:
return nil, errEofToken
}
return nil, fmt.Errorf("Unknown token: %+v", tok)
}
// objectType parses an object type and returns a ObjectType AST
2015-10-26 18:37:17 +00:00
func (p *Parser) objectType() (*ast.ObjectType, error) {
2015-10-16 22:14:40 +00:00
defer un(trace(p, "ParseObjectType"))
// we assume that the currently scanned token is a LBRACE
o := &ast.ObjectType{
Lbrace: p.tok.Pos,
}
2015-10-26 18:37:17 +00:00
l, err := p.objectList()
2015-10-16 22:14:40 +00:00
// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
// not a RBRACE, it's an syntax error and we just return it.
if err != nil && p.tok.Type != token.RBRACE {
return nil, err
}
o.List = l
o.Rbrace = p.tok.Pos // advanced via parseObjectList
return o, nil
}
2015-10-26 18:37:17 +00:00
// listType parses a list type and returns a ListType AST
func (p *Parser) listType() (*ast.ListType, error) {
2015-10-16 21:00:05 +00:00
defer un(trace(p, "ParseListType"))
2015-10-16 22:14:40 +00:00
// we assume that the currently scanned token is a LBRACK
2015-10-16 21:00:05 +00:00
l := &ast.ListType{
Lbrack: p.tok.Pos,
}
for {
tok := p.scan()
switch tok.Type {
case token.NUMBER, token.FLOAT, token.STRING:
2015-10-26 18:37:17 +00:00
node, err := p.literalType()
2015-10-16 21:00:05 +00:00
if err != nil {
return nil, err
}
2015-10-16 21:00:05 +00:00
l.Add(node)
case token.COMMA:
// get next list item or we are at the end
continue
case token.BOOL:
// TODO(arslan) should we support? not supported by HCL yet
case token.LBRACK:
2015-10-16 22:14:40 +00:00
// TODO(arslan) should we support nested lists? Even though it's
2015-10-18 20:19:56 +00:00
// written in README of HCL, it's not a part of the grammar
// (not defined in parse.y)
2015-10-16 21:00:05 +00:00
case token.RBRACK:
// finished
l.Rbrack = p.tok.Pos
return l, nil
default:
return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type)
}
}
}
2015-10-26 18:37:17 +00:00
// literalType parses a literal type and returns a LiteralType AST
func (p *Parser) literalType() (*ast.LiteralType, error) {
defer un(trace(p, "ParseLiteral"))
2015-10-12 20:44:53 +00:00
2015-10-16 20:12:26 +00:00
return &ast.LiteralType{
Token: p.tok,
}, nil
2015-10-12 20:44:53 +00:00
}
// scan returns the next token from the underlying scanner. If a token has
// been unscanned then read that instead. In the process, it collects any
// comment groups encountered, and remembers the last lead and line comments.
2015-10-16 20:12:26 +00:00
func (p *Parser) scan() token.Token {
// If we have a token on the buffer, then return it.
if p.n != 0 {
p.n = 0
return p.tok
}
// Otherwise read the next token from the scanner and Save it to the buffer
// in case we unscan later.
prev := p.tok
p.tok = p.sc.Scan()
if p.tok.Type == token.COMMENT {
var comment *ast.CommentGroup
var endline int
// fmt.Printf("p.tok.Pos.Line = %+v prev: %d \n", p.tok.Pos.Line, prev.Pos.Line)
if p.tok.Pos.Line == prev.Pos.Line {
// The comment is on same line as the previous token; it
// cannot be a lead comment but may be a line comment.
comment, endline = p.consumeCommentGroup(0)
if p.tok.Pos.Line != endline {
// The next token is on a different line, thus
// the last comment group is a line comment.
p.lineComment = comment
}
}
// consume successor comments, if any
endline = -1
for p.tok.Type == token.COMMENT {
comment, endline = p.consumeCommentGroup(1)
}
if endline+1 == p.tok.Pos.Line {
// The next token is following on the line immediately after the
// comment group, thus the last comment group is a lead comment.
p.leadComment = comment
}
}
return p.tok
2015-10-07 22:38:39 +00:00
}
// unscan pushes the previously read token back onto the buffer.
func (p *Parser) unscan() {
p.n = 1
}
2015-10-11 23:27:43 +00:00
// ----------------------------------------------------------------------------
// Parsing support
func (p *Parser) printTrace(a ...interface{}) {
if !p.enableTrace {
return
}
const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
const n = len(dots)
fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)
i := 2 * p.indent
for i > n {
fmt.Print(dots)
i -= n
}
// i <= n
fmt.Print(dots[0:i])
fmt.Println(a...)
}
func trace(p *Parser, msg string) *Parser {
p.printTrace(msg, "(")
p.indent++
return p
}
// Usage pattern: defer un(trace(p, "..."))
func un(p *Parser) {
p.indent--
p.printTrace(")")
}