From 628bc890269030a73e0ff9b77dd3302fb9084956 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Fri, 16 Oct 2015 02:00:02 +0300 Subject: [PATCH] parser: implement parseObjectKey function --- parser/ast.go | 8 +++-- parser/parser.go | 53 +++++++++++++++++++++++++++++++++ parser/parser_test.go | 69 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/parser/ast.go b/parser/ast.go index d09432f..98b640f 100644 --- a/parser/ast.go +++ b/parser/ast.go @@ -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 diff --git a/parser/parser.go b/parser/parser.go index 1ae1432..2192829 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -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")) diff --git a/parser/parser_test.go b/parser/parser_test.go index 4760e71..5a16f89 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -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() } }