parser: implement parseObjectKey function

This commit is contained in:
Fatih Arslan 2015-10-16 02:00:02 +03:00
parent 17205f8484
commit 628bc89026
3 changed files with 113 additions and 17 deletions

View File

@ -34,7 +34,7 @@ func (o *ObjectList) Pos() scanner.Pos {
// ObjectItem represents a HCL Object Item. An item is represented with a key // ObjectItem represents a HCL Object Item. An item is represented with a key
// (or keys). It can be an assignment or an object (both normal and nested) // (or keys). It can be an assignment or an object (both normal and nested)
type ObjectItem struct { type ObjectItem struct {
// keys is only one lenght long if it's of type assignment. If it's a // keys is only one length long if it's of type assignment. If it's a
// nested object it can be larger than one. In that case "assign" is // nested object it can be larger than one. In that case "assign" is
// invalid as there is no assignments for a nested object. // invalid as there is no assignments for a nested object.
keys []*ObjectKey keys []*ObjectKey
@ -43,7 +43,7 @@ type ObjectItem struct {
assign scanner.Pos assign scanner.Pos
// val is the item itself. It can be an object,list, number, bool or a // val is the item itself. It can be an object,list, number, bool or a
// string. If key lenght is larger than one, val can be only of type // string. If key length is larger than one, val can be only of type
// Object. // Object.
val Node val Node
} }
@ -61,7 +61,9 @@ func (o *ObjectKey) Pos() scanner.Pos {
return o.token.Pos return o.token.Pos
} }
func (o *ObjectKey) IsValid() bool { // isValid() returns true if the underlying identifier satisfies one of the
// valid types (IDENT or STRING)
func (o *ObjectKey) isValid() bool {
switch o.token.Type { switch o.token.Type {
case scanner.IDENT, scanner.STRING: case scanner.IDENT, scanner.STRING:
return true return true

View File

@ -51,6 +51,18 @@ func (p *Parser) Parse() (Node, error) {
func (p *Parser) parseObjectItem() (*ObjectItem, error) { func (p *Parser) parseObjectItem() (*ObjectItem, error) {
defer un(trace(p, "ParseObjectItem")) defer un(trace(p, "ParseObjectItem"))
keys, err := p.parseObjectKey()
if err != nil {
return nil, err
}
switch len(keys) {
case 1:
// assignment or object
default:
// nested object
}
tok := p.scan() tok := p.scan()
fmt.Println(tok) // debug fmt.Println(tok) // debug
@ -70,6 +82,47 @@ func (p *Parser) parseObjectItem() (*ObjectItem, error) {
return nil, fmt.Errorf("not yet implemented: %s", tok.Type) return nil, fmt.Errorf("not yet implemented: %s", tok.Type)
} }
// parseObjectKey parses an object key and returns a ObjectKey AST
func (p *Parser) parseObjectKey() ([]*ObjectKey, error) {
tok := p.scan()
switch tok.Type {
case scanner.IDENT, scanner.STRING:
// add first found token
keys := []*ObjectKey{&ObjectKey{tok}}
nestedObj := false
// now we have three casses
// 1. assignment: KEY = NODE
// 2. object: KEY { }
// 2. nested object: KEY KEY2 ... KEYN {}
for {
tok := p.scan()
switch tok.Type {
case scanner.ASSIGN:
// assignment or object, but not nested objects
if nestedObj {
return nil, fmt.Errorf("nested object expected: LBRACE got: %s", tok.Type)
}
return keys, nil
case scanner.LBRACE:
// object
return keys, nil
case scanner.IDENT, scanner.STRING:
// nested object
nestedObj = true
keys = append(keys, &ObjectKey{
token: tok,
})
default:
return nil, fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", tok.Type)
}
}
default:
return nil, fmt.Errorf("expected: IDENT | STRING got: %s", tok.Type)
}
}
// parseLiteralType parses a literal type and returns a LiteralType AST // parseLiteralType parses a literal type and returns a LiteralType AST
func (p *Parser) parseLiteralType() (*LiteralType, error) { func (p *Parser) parseLiteralType() (*LiteralType, error) {
defer un(trace(p, "ParseLiteral")) defer un(trace(p, "ParseLiteral"))

View File

@ -2,28 +2,69 @@ package parser
import ( import (
"fmt" "fmt"
"path/filepath"
"reflect"
"runtime"
"testing" "testing"
"github.com/fatih/hcl/scanner"
) )
func TestAssignStatement(t *testing.T) { func TestObjectKey(t *testing.T) {
src := `ami = "${var.foo}"` keys := []struct {
p := New([]byte(src)) exp []scanner.TokenType
p.enableTrace = true src string
n, err := p.Parse() }{
if err != nil { {[]scanner.TokenType{scanner.IDENT}, `foo {}`},
t.Fatal(err) {[]scanner.TokenType{scanner.IDENT}, `foo = {}`},
{[]scanner.TokenType{scanner.IDENT}, `foo = "${var.bar}`},
{[]scanner.TokenType{scanner.STRING}, `"foo" {}`},
{[]scanner.TokenType{scanner.STRING}, `"foo" = {}`},
{[]scanner.TokenType{scanner.STRING}, `"foo" = "${var.bar}`},
{[]scanner.TokenType{scanner.IDENT, scanner.IDENT}, `foo bar {}`},
{[]scanner.TokenType{scanner.IDENT, scanner.STRING}, `foo "bar" {}`},
{[]scanner.TokenType{scanner.STRING, scanner.IDENT}, `"foo" bar {}`},
{[]scanner.TokenType{scanner.IDENT, scanner.IDENT, scanner.IDENT}, `foo bar baz {}`},
} }
if n.Pos().Line != 1 { for _, k := range keys {
t.Errorf("AssignStatement position is wrong\n\twant: '%d'\n\tgot : '%d'", 1, n.Pos().Line) p := New([]byte(k.src))
keys, err := p.parseObjectKey()
if err != nil {
t.Fatal(err)
}
tokens := []scanner.TokenType{}
for _, o := range keys {
tokens = append(tokens, o.token.Type)
}
equals(t, k.exp, tokens)
} }
n1, ok := n.(*ObjectList) errKeys := []struct {
if !ok { src string
t.Fatal("First Node should be of type Source") }{
{`foo 12 {}`},
{`foo bar = {}`},
{`foo []`},
{`12 {}`},
} }
for _, ns := range n1.nodes { for _, k := range errKeys {
fmt.Printf("ns = %+v\n", ns) p := New([]byte(k.src))
_, err := p.parseObjectKey()
if err == nil {
t.Errorf("case '%s' should give an error", k.src)
}
}
}
// equals fails the test if exp is not equal to act.
func equals(tb testing.TB, exp, act interface{}) {
if !reflect.DeepEqual(exp, act) {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
tb.FailNow()
} }
} }