diff --git a/option.go b/option.go index fbda399..694cbb1 100644 --- a/option.go +++ b/option.go @@ -155,42 +155,66 @@ type NamedLiteral struct { Name string } -// parseAggregate reads options written using aggregate syntax +// parseAggregate reads options written using aggregate syntax. +// tLEFTCURLY { has been consumed func (o *Option) parseAggregate(p *Parser) error { - o.AggregatedConstants = []*NamedLiteral{} + constants, err := parseAggregateConstants(p, o) + o.AggregatedConstants = constants + return err +} + +func parseAggregateConstants(p *Parser, container interface{}) (list []*NamedLiteral, err error) { for { pos, tok, lit := p.next() if tRIGHTSQUARE == tok { p.nextPut(pos, tok, lit) // caller has checked for open square ; will consume rightsquare, rightcurly and semicolon - return nil + return } if tRIGHTCURLY == tok { - return nil + return } if tSEMICOLON == tok { p.nextPut(pos, tok, lit) // allow for inline comment parsing - return nil + return } if tCOMMA == tok { - if len(o.AggregatedConstants) == 0 { - return p.unexpected(lit, "non-empty option aggregate key", o) + if len(list) == 0 { + err = p.unexpected(lit, "non-empty option aggregate key", container) + return } continue } if tIDENT != tok { - return p.unexpected(lit, "option aggregate key", o) + err = p.unexpected(lit, "option aggregate key", container) + return } key := lit pos, tok, lit = p.next() + if tLEFTCURLY == tok { + nested, fault := parseAggregateConstants(p, container) + if fault != nil { + err = fault + return + } + // flatten the constants + for _, each := range nested { + flatten := &NamedLiteral{ + Name: key + "." + each.Name, + Literal: each.Literal} + list = append(list, flatten) + } + continue + } if tCOLON != tok { - return p.unexpected(lit, "option aggregate key colon :", o) + err = p.unexpected(lit, "option aggregate key colon :", container) + return } l := new(Literal) l.Position = pos - if err := l.parse(p); err != nil { - return err + if err = l.parse(p); err != nil { + return } - o.AggregatedConstants = append(o.AggregatedConstants, &NamedLiteral{Name: key, Literal: l}) + list = append(list, &NamedLiteral{Name: key, Literal: l}) } } diff --git a/option_test.go b/option_test.go index a5e91f9..33ed125 100644 --- a/option_test.go +++ b/option_test.go @@ -23,7 +23,9 @@ package proto -import "testing" +import ( + "testing" +) func TestOptionCases(t *testing.T) { for i, each := range []struct { @@ -136,7 +138,7 @@ option Help = "me"; // inline` } } -func TestIssue8(t *testing.T) { +func TestAggregateSyntax(t *testing.T) { proto := ` // usage: message Bar { @@ -241,3 +243,54 @@ func TestFieldCustomOptions(t *testing.T) { t.Errorf("got [%v] want [%v]", got, want) } } + +// issue #50 +func TestNestedAggregateConstants(t *testing.T) { + src := `syntax = "proto3"; + + package baz; + + option (foo.bar) = { + woot: 100 + foo { + hello: 200 + hello2: 300 + bar { + hello3: 400 + } + } + };` + p := newParserOn(src) + proto, err := p.Parse() + if err != nil { + t.Error(err) + } + option := proto.Elements[2].(*Option) + if got, want := option.Name, "(foo.bar)"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := len(option.AggregatedConstants), 4; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[0].Name, "woot"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[1].Name, "foo.hello"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[2].Name, "foo.hello2"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[3].Name, "foo.bar.hello3"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[1].Literal.SourceRepresentation(), "200"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[2].Literal.SourceRepresentation(), "300"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := option.AggregatedConstants[3].Literal.SourceRepresentation(), "400"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } +}