parser: implement parseObjectKey function
This commit is contained in:
parent
17205f8484
commit
628bc89026
@ -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
|
||||||
|
@ -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"))
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user