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
// (or keys). It can be an assignment or an object (both normal and nested)
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
// invalid as there is no assignments for a nested object.
keys []*ObjectKey
@ -43,7 +43,7 @@ type ObjectItem struct {
assign scanner.Pos
// 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.
val Node
}
@ -61,7 +61,9 @@ func (o *ObjectKey) Pos() scanner.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 {
case scanner.IDENT, scanner.STRING:
return true

View File

@ -51,6 +51,18 @@ func (p *Parser) Parse() (Node, error) {
func (p *Parser) parseObjectItem() (*ObjectItem, error) {
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()
fmt.Println(tok) // debug
@ -70,6 +82,47 @@ func (p *Parser) parseObjectItem() (*ObjectItem, error) {
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
func (p *Parser) parseLiteralType() (*LiteralType, error) {
defer un(trace(p, "ParseLiteral"))

View File

@ -2,28 +2,69 @@ package parser
import (
"fmt"
"path/filepath"
"reflect"
"runtime"
"testing"
"github.com/fatih/hcl/scanner"
)
func TestAssignStatement(t *testing.T) {
src := `ami = "${var.foo}"`
p := New([]byte(src))
p.enableTrace = true
n, err := p.Parse()
if err != nil {
t.Fatal(err)
func TestObjectKey(t *testing.T) {
keys := []struct {
exp []scanner.TokenType
src string
}{
{[]scanner.TokenType{scanner.IDENT}, `foo {}`},
{[]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 {
t.Errorf("AssignStatement position is wrong\n\twant: '%d'\n\tgot : '%d'", 1, n.Pos().Line)
for _, k := range keys {
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)
if !ok {
t.Fatal("First Node should be of type Source")
errKeys := []struct {
src string
}{
{`foo 12 {}`},
{`foo bar = {}`},
{`foo []`},
{`12 {}`},
}
for _, ns := range n1.nodes {
fmt.Printf("ns = %+v\n", ns)
for _, k := range errKeys {
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()
}
}