123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- package parser
-
- import (
- "errors"
- "fmt"
-
- "github.com/hashicorp/hcl/hcl/ast"
- hcltoken "github.com/hashicorp/hcl/hcl/token"
- "github.com/hashicorp/hcl/json/scanner"
- "github.com/hashicorp/hcl/json/token"
- )
-
- type Parser struct {
- sc *scanner.Scanner
-
- // Last read token
- tok token.Token
- commaPrev token.Token
-
- enableTrace bool
- indent int
- n int // buffer size (max = 1)
- }
-
- func newParser(src []byte) *Parser {
- return &Parser{
- sc: scanner.New(src),
- }
- }
-
- // Parse returns the fully parsed source and returns the abstract syntax tree.
- 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.
- func (p *Parser) Parse() (*ast.File, error) {
- f := &ast.File{}
- var err, scerr error
- p.sc.Error = func(pos token.Pos, msg string) {
- scerr = fmt.Errorf("%s: %s", pos, msg)
- }
-
- // The root must be an object in JSON
- object, err := p.object()
- if scerr != nil {
- return nil, scerr
- }
- if err != nil {
- return nil, err
- }
-
- // We make our final node an object list so it is more HCL compatible
- f.Node = object.List
-
- // Flatten it, which finds patterns and turns them into more HCL-like
- // AST trees.
- flattenObjects(f.Node)
-
- return f, nil
- }
-
- func (p *Parser) objectList() (*ast.ObjectList, error) {
- defer un(trace(p, "ParseObjectList"))
- node := &ast.ObjectList{}
-
- for {
- n, err := p.objectItem()
- if err == errEofToken {
- break // we are finished
- }
-
- // we don't return a nil node, because might want to use already
- // collected items.
- if err != nil {
- return node, err
- }
-
- node.Add(n)
-
- // Check for a followup comma. If it isn't a comma, then we're done
- if tok := p.scan(); tok.Type != token.COMMA {
- break
- }
- }
-
- return node, nil
- }
-
- // objectItem parses a single object item
- func (p *Parser) objectItem() (*ast.ObjectItem, error) {
- defer un(trace(p, "ParseObjectItem"))
-
- keys, err := p.objectKey()
- if err != nil {
- return nil, err
- }
-
- o := &ast.ObjectItem{
- Keys: keys,
- }
-
- switch p.tok.Type {
- case token.COLON:
- pos := p.tok.Pos
- o.Assign = hcltoken.Pos{
- Filename: pos.Filename,
- Offset: pos.Offset,
- Line: pos.Line,
- Column: pos.Column,
- }
-
- o.Val, err = p.objectValue()
- if err != nil {
- return nil, err
- }
- }
-
- return o, nil
- }
-
- // objectKey parses an object key and returns a ObjectKey AST
- func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
- keyCount := 0
- keys := make([]*ast.ObjectKey, 0)
-
- for {
- tok := p.scan()
- switch tok.Type {
- case token.EOF:
- return nil, errEofToken
- case token.STRING:
- keyCount++
- keys = append(keys, &ast.ObjectKey{
- Token: p.tok.HCLToken(),
- })
- case token.COLON:
- // If we have a zero keycount it means that we never got
- // an object key, i.e. `{ :`. This is a syntax error.
- if keyCount == 0 {
- return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type)
- }
-
- // Done
- return keys, nil
- case token.ILLEGAL:
- return nil, errors.New("illegal")
- default:
- return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type)
- }
- }
- }
-
- // object parses any type of object, such as number, bool, string, object or
- // list.
- func (p *Parser) objectValue() (ast.Node, error) {
- defer un(trace(p, "ParseObjectValue"))
- tok := p.scan()
-
- switch tok.Type {
- case token.NUMBER, token.FLOAT, token.BOOL, token.NULL, token.STRING:
- return p.literalType()
- case token.LBRACE:
- return p.objectType()
- case token.LBRACK:
- return p.listType()
- case token.EOF:
- return nil, errEofToken
- }
-
- return nil, fmt.Errorf("Expected object value, got unknown token: %+v", tok)
- }
-
- // object parses any type of object, such as number, bool, string, object or
- // list.
- func (p *Parser) object() (*ast.ObjectType, error) {
- defer un(trace(p, "ParseType"))
- tok := p.scan()
-
- switch tok.Type {
- case token.LBRACE:
- return p.objectType()
- case token.EOF:
- return nil, errEofToken
- }
-
- return nil, fmt.Errorf("Expected object, got unknown token: %+v", tok)
- }
-
- // objectType parses an object type and returns a ObjectType AST
- func (p *Parser) objectType() (*ast.ObjectType, error) {
- defer un(trace(p, "ParseObjectType"))
-
- // we assume that the currently scanned token is a LBRACE
- o := &ast.ObjectType{}
-
- l, err := p.objectList()
-
- // 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
- return o, nil
- }
-
- // listType parses a list type and returns a ListType AST
- func (p *Parser) listType() (*ast.ListType, error) {
- defer un(trace(p, "ParseListType"))
-
- // we assume that the currently scanned token is a LBRACK
- l := &ast.ListType{}
-
- for {
- tok := p.scan()
- switch tok.Type {
- case token.NUMBER, token.FLOAT, token.STRING:
- node, err := p.literalType()
- if err != nil {
- return nil, err
- }
-
- l.Add(node)
- case token.COMMA:
- continue
- case token.LBRACE:
- node, err := p.objectType()
- if err != nil {
- return nil, err
- }
-
- l.Add(node)
- case token.BOOL:
- // TODO(arslan) should we support? not supported by HCL yet
- case token.LBRACK:
- // TODO(arslan) should we support nested lists? Even though it's
- // written in README of HCL, it's not a part of the grammar
- // (not defined in parse.y)
- case token.RBRACK:
- // finished
- return l, nil
- default:
- return nil, fmt.Errorf("unexpected token while parsing list: %s", tok.Type)
- }
-
- }
- }
-
- // literalType parses a literal type and returns a LiteralType AST
- func (p *Parser) literalType() (*ast.LiteralType, error) {
- defer un(trace(p, "ParseLiteral"))
-
- return &ast.LiteralType{
- Token: p.tok.HCLToken(),
- }, nil
- }
-
- // scan returns the next token from the underlying scanner. If a token has
- // been unscanned then read that instead.
- 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
- }
-
- p.tok = p.sc.Scan()
- return p.tok
- }
-
- // unscan pushes the previously read token back onto the buffer.
- func (p *Parser) unscan() {
- p.n = 1
- }
-
- // ----------------------------------------------------------------------------
- // 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(")")
- }
|