From c8e235e47c14f57601acbb70cebf9026595dfc4a Mon Sep 17 00:00:00 2001 From: Robbert Van Ginkel Date: Mon, 2 Oct 2017 22:40:21 +0200 Subject: [PATCH] LineNumbers --- comment.go | 15 +++++++-------- comment_test.go | 14 ++++++++++---- enum.go | 27 ++++++++++++++++----------- enum_test.go | 11 ++++++++++- extensions.go | 1 + extensions_test.go | 5 ++++- field.go | 23 +++++++++++++---------- field_test.go | 3 +++ group.go | 17 +++++++++-------- group_test.go | 3 +++ import.go | 3 ++- message.go | 36 +++++++++++++++++++++++++----------- message_test.go | 14 +++++++++++++- oneof.go | 15 +++++++++------ oneof_test.go | 6 ++++++ option.go | 24 ++++++++++++++---------- option_test.go | 24 ++++++++++++++++++++++++ package.go | 3 ++- parser.go | 21 +++++++++++---------- parser_test.go | 8 ++++---- proto.go | 12 ++++++++++-- range_test.go | 6 +++--- reserved.go | 3 ++- reserved_test.go | 12 ++++++------ scanner.go | 46 ++++++++++++++++++++++++---------------------- service.go | 40 ++++++++++++++++++++++------------------ service_test.go | 12 +++++++++--- syntax.go | 3 ++- 28 files changed, 264 insertions(+), 143 deletions(-) diff --git a/comment.go b/comment.go index 873ff8f..269ce48 100644 --- a/comment.go +++ b/comment.go @@ -27,14 +27,14 @@ import "strings" // Comment holds a message. type Comment struct { - lineNumber int + LineNumber int Lines []string Cstyle bool // refers to /* ... */, C++ style is using // ExtraSlash bool } // newComment returns a comment. -func newComment(lit string) *Comment { +func newComment(line int, lit string) *Comment { nonEmpty := []string{} extraSlash := false lines := strings.Split(lit, "\n") @@ -51,7 +51,7 @@ func newComment(lit string) *Comment { nonEmpty = append(nonEmpty, lit) } } - return &Comment{Lines: nonEmpty, Cstyle: len(lines) > 1, ExtraSlash: extraSlash} + return &Comment{LineNumber: line, Lines: nonEmpty, Cstyle: len(lines) > 1, ExtraSlash: extraSlash} } // columns is part of columnsPrintable @@ -114,7 +114,7 @@ type commentInliner interface { func maybeScanInlineComment(p *Parser, c elementContainer) { currentLine := p.s.line // see if there is an inline Comment - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() esize := len(c.elements()) // seen comment and on same line and elements have been added if tCOMMENT == tok && p.s.line <= currentLine+1 && esize > 0 { @@ -122,7 +122,7 @@ func maybeScanInlineComment(p *Parser, c elementContainer) { last := c.elements()[esize-1] if inliner, ok := last.(commentInliner); ok { // TODO skip multiline? - inliner.inlineComment(newComment(lit)) + inliner.inlineComment(newComment(line, lit)) } } else { p.unscan() @@ -142,13 +142,12 @@ func takeLastComment(list []Visitee) (*Comment, []Visitee) { // mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line). func mergeOrReturnComment(elements []Visitee, lit string, lineNumber int) *Comment { - com := newComment(lit) - com.lineNumber = lineNumber + com := newComment(lineNumber, lit) // last element must be a comment to merge + // do not merge c-style comments + // last comment line was on previous line if esize := len(elements); esize > 0 { - if last, ok := elements[esize-1].(*Comment); ok && !last.Cstyle && lineNumber <= last.lineNumber+len(last.Lines) { // less than because last line of file could be inline comment + if last, ok := elements[esize-1].(*Comment); ok && !last.Cstyle && lineNumber <= last.LineNumber+len(last.Lines) { // less than because last line of file could be inline comment last.Merge(com) // mark as merged com = nil diff --git a/comment_test.go b/comment_test.go index c278d8f..1758c70 100644 --- a/comment_test.go +++ b/comment_test.go @@ -28,11 +28,11 @@ import ( ) func TestCreateComment(t *testing.T) { - c0 := newComment("") + c0 := newComment(0, "") if got, want := len(c0.Lines), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } - c1 := newComment(`hello + c1 := newComment(0, `hello world`) if got, want := len(c1.Lines), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) @@ -49,8 +49,8 @@ world`) } func TestTakeLastComment(t *testing.T) { - c0 := newComment("hi") - c1 := newComment("there") + c0 := newComment(0, "hi") + c1 := newComment(0, "there") _, l := takeLastComment([]Visitee{c0, c1}) if got, want := len(l), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) @@ -83,6 +83,9 @@ func TestParseCommentWithEmptyLinesAndTripleSlash(t *testing.T) { if got, want := def.Elements[0].(*Comment).Lines[4], " comment 4"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := def.Elements[0].(*Comment).LineNumber, 2; got != want { + t.Fatalf("got [%d] want [%d]", got, want) + } } func TestParseCommentWithTripleSlash(t *testing.T) { @@ -104,4 +107,7 @@ func TestParseCommentWithTripleSlash(t *testing.T) { if got, want := def.Elements[0].(*Comment).Lines[0], " comment 1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := def.Elements[0].(*Comment).LineNumber, 2; got != want { + t.Fatalf("got [%d] want [%d]", got, want) + } } diff --git a/enum.go b/enum.go index 0fb4e38..0952939 100644 --- a/enum.go +++ b/enum.go @@ -27,9 +27,10 @@ import "strconv" // Enum definition consists of a name and an enum body. type Enum struct { - Comment *Comment - Name string - Elements []Visitee + LineNumber int + Comment *Comment + Name string + Elements []Visitee } // Accept dispatches the call to the visitor. @@ -60,26 +61,27 @@ func (e *Enum) takeLastComment() (last *Comment) { } func (e *Enum) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "enum identifier", e) } } e.Name = lit - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTCURLY { return p.unexpected(lit, "enum opening {", e) } for { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - if com := mergeOrReturnComment(e.elements(), lit, p.s.line); com != nil { // not merged? + if com := mergeOrReturnComment(e.elements(), lit, line); com != nil { // not merged? e.Elements = append(e.Elements, com) } case tOPTION: v := new(Option) + v.LineNumber = line v.Comment = e.takeLastComment() err := v.parse(p) if err != nil { @@ -93,6 +95,7 @@ func (e *Enum) parse(p *Parser) error { default: p.unscan() f := new(EnumField) + f.LineNumber = line f.Comment = e.takeLastComment() err := f.parse(p) if err != nil { @@ -110,6 +113,7 @@ done: // EnumField is part of the body of an Enum. type EnumField struct { + LineNumber int Comment *Comment Name string Integer int @@ -146,14 +150,14 @@ func (f EnumField) columns() (cols []aligned) { } func (f *EnumField) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "enum field identifier", f) } } f.Name = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tEQUALS { return p.unexpected(lit, "enum field =", f) } @@ -162,16 +166,17 @@ func (f *EnumField) parse(p *Parser) error { return p.unexpected(lit, "enum field integer", f) } f.Integer = i - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok == tLEFTSQUARE { o := new(Option) + o.LineNumber = line o.IsEmbedded = true err := o.parse(p) if err != nil { return err } f.ValueOption = o - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tRIGHTSQUARE { return p.unexpected(lit, "option closing ]", f) } diff --git a/enum_test.go b/enum_test.go index a15c983..3f293a0 100644 --- a/enum_test.go +++ b/enum_test.go @@ -26,7 +26,7 @@ package proto import "testing" func TestEnum(t *testing.T) { - proto := ` + proto := ` // enum enum EnumAllowingAlias { option allow_alias = true; @@ -52,10 +52,16 @@ enum EnumAllowingAlias { if got, want := enums[0].Comment.Message(), " enum"; got != want { t.Errorf("got [%v] want [%v]", enums[0].Comment, want) } + if got, want := enums[0].LineNumber, 3; got != want { + t.Errorf("got [%d] want [%d]", got, want) + } ef1 := enums[0].Elements[1].(*EnumField) if got, want := ef1.Integer, 0; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := ef1.LineNumber, 5; got != want { + t.Errorf("got [%d] want [%d]", got, want) + } ef3 := enums[0].Elements[3].(*EnumField) if got, want := ef3.Integer, 2; got != want { t.Errorf("got [%v] want [%v]", got, want) @@ -66,4 +72,7 @@ enum EnumAllowingAlias { if got, want := ef3.ValueOption.Constant.Source, "hello world"; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := ef3.LineNumber, 7; got != want { + t.Errorf("got [%d] want [%d]", got, want) + } } diff --git a/extensions.go b/extensions.go index 6b1f635..566ac11 100644 --- a/extensions.go +++ b/extensions.go @@ -26,6 +26,7 @@ package proto // Extensions declare that a range of field numbers in a message are available for third-party extensions. // proto2 only type Extensions struct { + LineNumber int Comment *Comment Ranges []Range InlineComment *Comment diff --git a/extensions_test.go b/extensions_test.go index d499da4..70d0dbe 100644 --- a/extensions_test.go +++ b/extensions_test.go @@ -26,7 +26,7 @@ package proto import "testing" func TestExtensions(t *testing.T) { - proto := `message M { + proto := `message M { // extensions extensions 4, 20 to max; // max }` @@ -44,6 +44,9 @@ func TestExtensions(t *testing.T) { if got, want := len(f.Ranges), 2; got != want { t.Fatalf("got [%d] want [%d]", got, want) } + if got, want := f.LineNumber, 3; got != want { + t.Fatalf("got [%d] want [%d]", got, want) + } if got, want := f.Ranges[1].String(), "20 to max"; got != want { t.Errorf("got [%s] want [%s]", got, want) } diff --git a/field.go b/field.go index c3e03c8..6478fe4 100644 --- a/field.go +++ b/field.go @@ -27,6 +27,7 @@ import "strconv" // Field is an abstract message field. type Field struct { + LineNumber int Comment *Comment Name string Type string @@ -94,7 +95,8 @@ func (f *NormalField) columns() (cols []aligned) { // [ "repeated" | "optional" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" func (f *NormalField) parse(p *Parser) error { for { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() + f.LineNumber = line switch tok { case tREPEATED: f.Repeated = true @@ -116,14 +118,14 @@ done: // parseFieldAfterType expects: // fieldName "=" fieldNumber [ "[" fieldOptions "]" ] "; func parseFieldAfterType(f *Field, p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "field identifier", f) } } f.Name = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tEQUALS { return p.unexpected(lit, "field =", f) } @@ -133,7 +135,7 @@ func parseFieldAfterType(f *Field, p *Parser) error { } f.Sequence = i // see if there are options - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tLEFTSQUARE != tok { p.unscan() return nil @@ -141,6 +143,7 @@ func parseFieldAfterType(f *Field, p *Parser) error { // consume options for { o := new(Option) + o.LineNumber = line o.IsEmbedded = true err := o.parse(p) if err != nil { @@ -148,7 +151,7 @@ func parseFieldAfterType(f *Field, p *Parser) error { } f.Options = append(f.Options, o) - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tRIGHTSQUARE == tok { break } @@ -205,25 +208,25 @@ func (f *MapField) columns() (cols []aligned) { // keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | // "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" func (f *MapField) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + _, tok, lit := p.scanIgnoreWhitespace() if tLESS != tok { return p.unexpected(lit, "map keyType <", f) } - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tIDENT != tok { return p.unexpected(lit, "map identifier", f) } f.KeyType = lit - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tCOMMA != tok { return p.unexpected(lit, "map type separator ,", f) } - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tIDENT != tok { return p.unexpected(lit, "map valueType identifier", f) } f.Type = lit - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tGREATER != tok { return p.unexpected(lit, "mak valueType >", f) } diff --git a/field_test.go b/field_test.go index fa20622..cb5ae6a 100644 --- a/field_test.go +++ b/field_test.go @@ -60,6 +60,9 @@ func TestField(t *testing.T) { if got, want := f.Options[2].Constant.Source, "happy"; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := f.LineNumber, 1; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } } func TestFieldSimple(t *testing.T) { diff --git a/group.go b/group.go index 7a23f82..61abe08 100644 --- a/group.go +++ b/group.go @@ -26,11 +26,12 @@ package proto // Group represents a (proto2 only) group. // https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#group_field type Group struct { - Comment *Comment - Name string - Optional bool - Sequence int - Elements []Visitee + LineNumber int + Comment *Comment + Name string + Optional bool + Sequence int + Elements []Visitee } // Accept dispatches the call to the visitor. @@ -63,14 +64,14 @@ func (g *Group) takeLastComment() (last *Comment) { // parse expects: // groupName "=" fieldNumber { messageBody } func (g *Group) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + _, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "group name", g) } } g.Name = lit - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tok != tEQUALS { return p.unexpected(lit, "group =", g) } @@ -79,7 +80,7 @@ func (g *Group) parse(p *Parser) error { return p.unexpected(lit, "group sequence number", g) } g.Sequence = i - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTCURLY { return p.unexpected(lit, "group opening {", g) } diff --git a/group_test.go b/group_test.go index 61b7c2d..2e8ae2f 100644 --- a/group_test.go +++ b/group_test.go @@ -47,6 +47,9 @@ func TestGroup(t *testing.T) { if got, want := len(g.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := g.LineNumber, 3; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } if got, want := g.Comment != nil, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } diff --git a/import.go b/import.go index 86ff7a5..01f3ef0 100644 --- a/import.go +++ b/import.go @@ -27,6 +27,7 @@ import "fmt" // Import holds a filename to another .proto definition. type Import struct { + LineNumber int Comment *Comment Filename string Kind string // weak, public, @@ -34,7 +35,7 @@ type Import struct { } func (i *Import) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + _, tok, lit := p.scanIgnoreWhitespace() switch tok { case tWEAK: i.Kind = lit diff --git a/message.go b/message.go index b9aa00a..4cca1e5 100644 --- a/message.go +++ b/message.go @@ -25,10 +25,11 @@ package proto // Message consists of a message name and a message body. type Message struct { - Comment *Comment - Name string - IsExtend bool - Elements []Visitee + LineNumber int + Comment *Comment + Name string + IsExtend bool + Elements []Visitee } func (m *Message) groupName() string { @@ -40,14 +41,14 @@ func (m *Message) groupName() string { // parse expects ident { messageBody func (m *Message) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + _, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, m.groupName()+" identifier", m) } } m.Name = lit - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTCURLY { return p.unexpected(lit, m.groupName()+" opening {", m) } @@ -57,18 +58,20 @@ func (m *Message) parse(p *Parser) error { // parseMessageBody parses elements after {. It consumes the closing } func parseMessageBody(p *Parser, c elementContainer) error { var ( - tok token - lit string + line int + tok token + lit string ) for { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - if com := mergeOrReturnComment(c.elements(), lit, p.s.line); com != nil { // not merged? + if com := mergeOrReturnComment(c.elements(), lit, line); com != nil { // not merged? c.addElement(com) } case tENUM: e := new(Enum) + e.LineNumber = line e.Comment = c.takeLastComment() if err := e.parse(p); err != nil { return err @@ -76,6 +79,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(e) case tMESSAGE: msg := new(Message) + msg.LineNumber = line msg.Comment = c.takeLastComment() if err := msg.parse(p); err != nil { return err @@ -83,6 +87,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(msg) case tOPTION: o := new(Option) + o.LineNumber = line o.Comment = c.takeLastComment() if err := o.parse(p); err != nil { return err @@ -90,6 +95,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(o) case tONEOF: o := new(Oneof) + o.LineNumber = line o.Comment = c.takeLastComment() if err := o.parse(p); err != nil { return err @@ -97,6 +103,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(o) case tMAP: f := newMapField() + f.LineNumber = line f.Comment = c.takeLastComment() if err := f.parse(p); err != nil { return err @@ -104,6 +111,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(f) case tRESERVED: r := new(Reserved) + r.LineNumber = line r.Comment = c.takeLastComment() if err := r.parse(p); err != nil { return err @@ -113,9 +121,10 @@ func parseMessageBody(p *Parser, c elementContainer) error { case tOPTIONAL, tREPEATED, tREQUIRED: // look ahead prevTok := tok - tok, lit = p.scanIgnoreWhitespace() + _, tok, lit = p.scanIgnoreWhitespace() if tGROUP == tok { g := new(Group) + g.LineNumber = line g.Comment = c.takeLastComment() g.Optional = prevTok == tOPTIONAL if err := g.parse(p); err != nil { @@ -126,6 +135,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { // not a group, will be tFIELD p.unscan() f := newNormalField() + f.LineNumber = line f.Comment = c.takeLastComment() f.Optional = prevTok == tOPTIONAL f.Repeated = prevTok == tREPEATED @@ -137,6 +147,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { } case tGROUP: g := new(Group) + g.LineNumber = line g.Comment = c.takeLastComment() if err := g.parse(p); err != nil { return err @@ -144,6 +155,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(g) case tEXTENSIONS: e := new(Extensions) + e.LineNumber = line e.Comment = c.takeLastComment() if err := e.parse(p); err != nil { return err @@ -151,6 +163,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { c.addElement(e) case tEXTEND: e := new(Message) + e.LineNumber = line e.Comment = c.takeLastComment() e.IsExtend = true if err := e.parse(p); err != nil { @@ -167,6 +180,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { // tFIELD p.unscan() f := newNormalField() + f.LineNumber = line f.Comment = c.takeLastComment() if err := f.parse(p); err != nil { return err diff --git a/message_test.go b/message_test.go index d0afc45..d7c784e 100644 --- a/message_test.go +++ b/message_test.go @@ -32,7 +32,7 @@ func TestMessage(t *testing.T) { string id = 1; // size int64 size = 2; - + oneof foo { string name = 4; SubMessage sub_message = 9; @@ -56,4 +56,16 @@ func TestMessage(t *testing.T) { if got, want := len(m.Elements), 6; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := m.Elements[0].(*NormalField).LineNumber, 4; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := m.Elements[0].(*NormalField).Comment.LineNumber, 3; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := m.Elements[3].(*Message).LineNumber, 12; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := m.Elements[3].(*Message).Elements[0].(*NormalField).LineNumber, 13; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } } diff --git a/oneof.go b/oneof.go index fd2ad0c..eccb8f8 100644 --- a/oneof.go +++ b/oneof.go @@ -27,9 +27,10 @@ import "strconv" // Oneof is a field alternate. type Oneof struct { - Comment *Comment - Name string - Elements []Visitee + LineNumber int + Comment *Comment + Name string + Elements []Visitee } // addElement is part of elementContainer @@ -52,19 +53,19 @@ func (o *Oneof) takeLastComment() (last *Comment) { // parse expects: // oneofName "{" { oneofField | emptyStatement } "}" func (o *Oneof) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "oneof identifier", o) } } o.Name = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTCURLY { return p.unexpected(lit, "oneof opening {", o) } for { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: if com := mergeOrReturnComment(o.elements(), lit, p.s.line); com != nil { // not merged? @@ -72,6 +73,7 @@ func (o *Oneof) parse(p *Parser) error { } case tIDENT: f := newOneOfField() + f.LineNumber = line f.Comment, o.Elements = takeLastComment(o.elements()) f.Type = lit if err := parseFieldAfterType(f.Field, p); err != nil { @@ -80,6 +82,7 @@ func (o *Oneof) parse(p *Parser) error { o.Elements = append(o.Elements, f) case tGROUP: g := new(Group) + g.LineNumber = line g.Comment, o.Elements = takeLastComment(o.elements()) if err := g.parse(p); err != nil { return err diff --git a/oneof_test.go b/oneof_test.go index 3912ef5..3b1209b 100644 --- a/oneof_test.go +++ b/oneof_test.go @@ -48,6 +48,9 @@ func TestOneof(t *testing.T) { if got, want := first.Comment.Message(), " just a name"; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := first.LineNumber, 3; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } second := o.Elements[1].(*OneOfField) if got, want := second.Name, "sub_message"; got != want { t.Errorf("got [%v] want [%v]", got, want) @@ -58,4 +61,7 @@ func TestOneof(t *testing.T) { if got, want := second.Sequence, 9; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := second.LineNumber, 4; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } } diff --git a/option.go b/option.go index cf065c4..2f22dcb 100644 --- a/option.go +++ b/option.go @@ -28,6 +28,7 @@ import "bytes" // Option is a protoc compiler option type Option struct { + LineNumber int Comment *Comment Name string Constant Literal @@ -69,15 +70,15 @@ func (o *Option) keyValuePair(embedded bool) (cols []aligned) { // parse reads an Option body // ( ident | "(" fullIdent ")" ) { "." ident } "=" constant ";" func (o *Option) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tLEFTPAREN == tok { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "option full identifier", o) } } - tok, _ = p.scanIgnoreWhitespace() + line, tok, _ = p.scanIgnoreWhitespace() if tok != tRIGHTPAREN { return p.unexpected(lit, "full identifier closing )", o) } @@ -91,15 +92,15 @@ func (o *Option) parse(p *Parser) error { } o.Name = lit } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tDOT == tok { // extend identifier - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tIDENT { return p.unexpected(lit, "option postfix identifier", o) } o.Name = fmt.Sprintf("%s.%s", o.Name, lit) - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() } if tEQUALS != tok { return p.unexpected(lit, "option constant =", o) @@ -111,6 +112,7 @@ func (o *Option) parse(p *Parser) error { } // non aggregate l := new(Literal) + l.LineNumber = line if err := l.parse(p); err != nil { return err } @@ -135,8 +137,9 @@ func (o *Option) Doc() *Comment { // Literal represents intLit,floatLit,strLit or boolLit type Literal struct { - Source string - IsString bool + LineNumber int + Source string + IsString bool } // String returns the source (if quoted then use double quote). @@ -168,7 +171,7 @@ type NamedLiteral struct { func (o *Option) parseAggregate(p *Parser) error { o.AggregatedConstants = []*NamedLiteral{} for { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tRIGHTSQUARE == tok { p.unscan() // caller has checked for open square ; will consume rightsquare, rightcurly and semicolon @@ -190,11 +193,12 @@ func (o *Option) parseAggregate(p *Parser) error { return p.unexpected(lit, "option aggregate key", o) } key := lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tCOLON != tok { return p.unexpected(lit, "option aggregate key colon :", o) } l := new(Literal) + l.LineNumber = line if err := l.parse(p); err != nil { return err } diff --git a/option_test.go b/option_test.go index a383600..14fb069 100644 --- a/option_test.go +++ b/option_test.go @@ -125,6 +125,15 @@ option Help = "me"; // inline` if got, want := o.InlineComment.Lines[0], " inline"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := o.LineNumber, 3; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.Comment.LineNumber, 2; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.InlineComment.LineNumber, 3; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } } func TestIssue8(t *testing.T) { @@ -164,4 +173,19 @@ message Bar { if got, want := ac[1].Source, "baz"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := o.LineNumber, 3; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.Comment.LineNumber, 2; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := f.LineNumber, 5; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := ac[0].LineNumber, 6; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := ac[1].LineNumber, 7; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } } diff --git a/package.go b/package.go index b58879d..e2f8f65 100644 --- a/package.go +++ b/package.go @@ -25,6 +25,7 @@ package proto // Package specifies the namespace for all proto elements. type Package struct { + LineNumber int Comment *Comment Name string InlineComment *Comment @@ -36,7 +37,7 @@ func (p *Package) Doc() *Comment { } func (p *Package) parse(pr *Parser) error { - tok, lit := pr.scanIgnoreWhitespace() + _, tok, lit := pr.scanIgnoreWhitespace() if tIDENT != tok { if !isKeyword(tok) { return pr.unexpected(lit, "package identifier", p) diff --git a/parser.go b/parser.go index 9229d64..b7ad2e4 100644 --- a/parser.go +++ b/parser.go @@ -33,9 +33,10 @@ import ( type Parser struct { s *scanner buf struct { - tok token // last read token - lit string // last read literal - n int // buffer size (max=1) + line int + tok token // last read token + lit string // last read literal + n int // buffer size (max=1) } debug bool } @@ -53,27 +54,27 @@ func (p *Parser) Parse() (*Proto, error) { // scan returns the next token from the underlying scanner. // If a token has been unscanned then read that instead. -func (p *Parser) scan() (tok token, lit string) { +func (p *Parser) scan() (line int, tok token, lit string) { // If we have a token on the buffer, then return it. if p.buf.n != 0 { p.buf.n = 0 - return p.buf.tok, p.buf.lit + return p.buf.line, p.buf.tok, p.buf.lit } // Otherwise read the next token from the scanner. - tok, lit = p.s.scan() + line, tok, lit = p.s.scan() // Save it to the buffer in case we unscan later. - p.buf.tok, p.buf.lit = tok, lit + p.buf.line, p.buf.tok, p.buf.lit = line, tok, lit return } // scanIgnoreWhitespace scans the next non-whitespace token. -func (p *Parser) scanIgnoreWhitespace() (tok token, lit string) { - tok, lit = p.scan() +func (p *Parser) scanIgnoreWhitespace() (line int, tok token, lit string) { + line, tok, lit = p.scan() if tok == tWS { - tok, lit = p.scan() + line, tok, lit = p.scan() } return } diff --git a/parser_test.go b/parser_test.go index 98f1238..c46477b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -33,8 +33,8 @@ func TestParseComment(t *testing.T) { // first // second - /* - ctyle + /* + ctyle multi line */ @@ -63,7 +63,7 @@ func newParserOn(def string) *Parser { // TEMPORARY tests func TestScanIgnoreWhitespace_Digits(t *testing.T) { p := newParserOn("1234") - _, lit := p.scanIgnoreWhitespace() + _, _, lit := p.scanIgnoreWhitespace() if got, want := lit, "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } @@ -71,7 +71,7 @@ func TestScanIgnoreWhitespace_Digits(t *testing.T) { func TestScanIgnoreWhitespace_Minus(t *testing.T) { p := newParserOn("-1234") - _, lit := p.scanIgnoreWhitespace() + _, _, lit := p.scanIgnoreWhitespace() if got, want := lit, "-"; got != want { t.Errorf("got [%v] want [%v]", got, want) } diff --git a/proto.go b/proto.go index d4c9b11..abb1c7a 100644 --- a/proto.go +++ b/proto.go @@ -48,14 +48,15 @@ func (proto *Proto) takeLastComment() (last *Comment) { // parse parsers a complete .proto definition source. func (proto *Proto) parse(p *Parser) error { for { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - if com := mergeOrReturnComment(proto.Elements, lit, p.s.line); com != nil { // not merged? + if com := mergeOrReturnComment(proto.Elements, lit, line); com != nil { // not merged? proto.Elements = append(proto.Elements, com) } case tOPTION: o := new(Option) + o.LineNumber = line o.Comment, proto.Elements = takeLastComment(proto.Elements) if err := o.parse(p); err != nil { return err @@ -63,6 +64,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, o) case tSYNTAX: s := new(Syntax) + s.LineNumber = line s.Comment, proto.Elements = takeLastComment(proto.Elements) if err := s.parse(p); err != nil { return err @@ -70,6 +72,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, s) case tIMPORT: im := new(Import) + im.LineNumber = line im.Comment, proto.Elements = takeLastComment(proto.Elements) if err := im.parse(p); err != nil { return err @@ -77,6 +80,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, im) case tENUM: enum := new(Enum) + enum.LineNumber = line enum.Comment, proto.Elements = takeLastComment(proto.Elements) if err := enum.parse(p); err != nil { return err @@ -84,6 +88,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, enum) case tSERVICE: service := new(Service) + service.LineNumber = line service.Comment, proto.Elements = takeLastComment(proto.Elements) err := service.parse(p) if err != nil { @@ -92,6 +97,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, service) case tPACKAGE: pkg := new(Package) + pkg.LineNumber = line pkg.Comment, proto.Elements = takeLastComment(proto.Elements) if err := pkg.parse(p); err != nil { return err @@ -99,6 +105,7 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, pkg) case tMESSAGE: msg := new(Message) + msg.LineNumber = line msg.Comment, proto.Elements = takeLastComment(proto.Elements) if err := msg.parse(p); err != nil { return err @@ -107,6 +114,7 @@ func (proto *Proto) parse(p *Parser) error { // BEGIN proto2 case tEXTEND: msg := new(Message) + msg.LineNumber = line msg.Comment, proto.Elements = takeLastComment(proto.Elements) msg.IsExtend = true if err := msg.parse(p); err != nil { diff --git a/range_test.go b/range_test.go index bc9a810..1ec5f5a 100644 --- a/range_test.go +++ b/range_test.go @@ -28,7 +28,7 @@ import "testing" func TestParseRanges(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved 2, 15, 9 to 11;`) - _, _ = p.scanIgnoreWhitespace() + _, _, _ = p.scanIgnoreWhitespace() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) @@ -41,7 +41,7 @@ func TestParseRanges(t *testing.T) { func TestParseRangesMax(t *testing.T) { r := new(Extensions) p := newParserOn(`extensions 3 to max;`) - _, _ = p.scanIgnoreWhitespace() + _, _, _ = p.scanIgnoreWhitespace() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) @@ -54,7 +54,7 @@ func TestParseRangesMax(t *testing.T) { func TestParseRangesMultiToMax(t *testing.T) { r := new(Extensions) p := newParserOn(`extensions 1,2 to 5,6 to 9,10 to max;`) - _, _ = p.scanIgnoreWhitespace() + _, _, _ = p.scanIgnoreWhitespace() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) diff --git a/reserved.go b/reserved.go index 656eeb8..68dbbc3 100644 --- a/reserved.go +++ b/reserved.go @@ -25,6 +25,7 @@ package proto // Reserved statements declare a range of field numbers or field names that cannot be used in a message. type Reserved struct { + LineNumber int Comment *Comment Ranges []Range FieldNames []string @@ -43,7 +44,7 @@ func (r *Reserved) Accept(v Visitor) { func (r *Reserved) parse(p *Parser) error { for { - tok, lit := p.scanIgnoreWhitespace() + _, tok, lit := p.scanIgnoreWhitespace() if len(lit) == 0 { return p.unexpected(lit, "reserved string or integer", r) } diff --git a/reserved_test.go b/reserved_test.go index e5e3c3a..fa5e185 100644 --- a/reserved_test.go +++ b/reserved_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -28,7 +28,7 @@ import "testing" func TestReservedRanges(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved 2, 15, 9 to 11;`) - tok, _ := p.scanIgnoreWhitespace() + _, tok, _ := p.scanIgnoreWhitespace() if tRESERVED != tok { t.Fail() } @@ -47,7 +47,7 @@ func TestReservedRanges(t *testing.T) { func TestReservedFieldNames(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved "foo", "bar";`) - _, _ = p.scanIgnoreWhitespace() + _, _, _ = p.scanIgnoreWhitespace() err := r.parse(p) if err != nil { t.Fatal(err) diff --git a/scanner.go b/scanner.go index b144260..eac98bd 100644 --- a/scanner.go +++ b/scanner.go @@ -45,59 +45,61 @@ func newScanner(r io.Reader) *scanner { } // scan returns the next token and literal value. -func (s *scanner) scan() (tok token, lit string) { +func (s *scanner) scan() (line int, tok token, lit string) { // Read the next rune. ch := s.read() - + line = s.line // If we see whitespace then consume all contiguous whitespace. // If we see a letter then consume as an ident or reserved word. // If we see a slash then consume all as a comment (can be multiline) if isWhitespace(ch) { s.unread(ch) - return s.scanWhitespace() + tok, lit = s.scanWhitespace() + return } else if isLetter(ch) || ch == '_' { s.unread(ch) - return s.scanIdent() + tok, lit = s.scanIdent() + return } // Otherwise read the individual character. switch ch { case eof: - return tEOF, "" + return line, tEOF, "" case ';': - return tSEMICOLON, string(ch) + return line, tSEMICOLON, string(ch) case ':': - return tCOLON, string(ch) + return line, tCOLON, string(ch) case '=': - return tEQUALS, string(ch) + return line, tEQUALS, string(ch) case '"': - return tQUOTE, string(ch) + return line, tQUOTE, string(ch) case '\'': - return tSINGLEQUOTE, string(ch) + return line, tSINGLEQUOTE, string(ch) case '(': - return tLEFTPAREN, string(ch) + return line, tLEFTPAREN, string(ch) case ')': - return tRIGHTPAREN, string(ch) + return line, tRIGHTPAREN, string(ch) case '{': - return tLEFTCURLY, string(ch) + return line, tLEFTCURLY, string(ch) case '}': - return tRIGHTCURLY, string(ch) + return line, tRIGHTCURLY, string(ch) case '[': - return tLEFTSQUARE, string(ch) + return line, tLEFTSQUARE, string(ch) case ']': - return tRIGHTSQUARE, string(ch) + return line, tRIGHTSQUARE, string(ch) case '/': - return tCOMMENT, s.scanComment() + return line, tCOMMENT, s.scanComment() case '<': - return tLESS, string(ch) + return line, tLESS, string(ch) case ',': - return tCOMMA, string(ch) + return line, tCOMMA, string(ch) case '.': - return tDOT, string(ch) + return line, tDOT, string(ch) case '>': - return tGREATER, string(ch) + return line, tGREATER, string(ch) } - return tILLEGAL, string(ch) + return line, tILLEGAL, string(ch) } // skipWhitespace consumes all whitespace until eof or a non-whitespace rune. diff --git a/service.go b/service.go index 133e746..5950929 100644 --- a/service.go +++ b/service.go @@ -30,9 +30,10 @@ import ( // Service defines a set of RPC calls. type Service struct { - Comment *Comment - Name string - Elements []Visitee + LineNumber int + Comment *Comment + Name string + Elements []Visitee } // Accept dispatches the call to the visitor. @@ -64,19 +65,19 @@ func (s *Service) takeLastComment() (last *Comment) { // parse continues after reading "service" func (s *Service) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "service identifier", s) } } s.Name = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTCURLY { return p.unexpected(lit, "service opening {", s) } for { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: if com := mergeOrReturnComment(s.Elements, lit, p.s.line); com != nil { // not merged? @@ -84,6 +85,7 @@ func (s *Service) parse(p *Parser) error { } case tRPC: rpc := new(RPC) + rpc.LineNumber = line rpc.Comment, s.Elements = takeLastComment(s.Elements) err := rpc.parse(p) if err != nil { @@ -104,6 +106,7 @@ done: // RPC represents an rpc entry in a message. type RPC struct { + LineNumber int Comment *Comment Name string RequestType string @@ -176,50 +179,50 @@ func (r *RPC) columns() (cols []aligned) { // parse continues after reading "rpc" func (r *RPC) parse(p *Parser) error { - tok, lit := p.scanIgnoreWhitespace() + line, tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { return p.unexpected(lit, "rpc method", r) } r.Name = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTPAREN { return p.unexpected(lit, "rpc type opening (", r) } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if iSTREAM == lit { r.StreamsRequest = true - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() } if tok != tIDENT { return p.unexpected(lit, "rpc stream | request type", r) } r.RequestType = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tRIGHTPAREN { return p.unexpected(lit, "rpc type closing )", r) } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tRETURNS { return p.unexpected(lit, "rpc returns", r) } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tLEFTPAREN { return p.unexpected(lit, "rpc type opening (", r) } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if iSTREAM == lit { r.StreamsReturns = true - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() } if tok != tIDENT { return p.unexpected(lit, "rpc stream | returns type", r) } r.ReturnsType = lit - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tok != tRIGHTPAREN { return p.unexpected(lit, "rpc type closing )", r) } - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tSEMICOLON == tok { p.s.unread(';') // allow for inline comment parsing return nil @@ -227,7 +230,7 @@ func (r *RPC) parse(p *Parser) error { if tLEFTCURLY == tok { // parse options for { - tok, lit = p.scanIgnoreWhitespace() + line, tok, lit = p.scanIgnoreWhitespace() if tRIGHTCURLY == tok { break } @@ -239,6 +242,7 @@ func (r *RPC) parse(p *Parser) error { return p.unexpected(lit, "rpc option", r) } o := new(Option) + o.LineNumber = line if err := o.parse(p); err != nil { return err } diff --git a/service_test.go b/service_test.go index 6dd7d53..dd42c29 100644 --- a/service_test.go +++ b/service_test.go @@ -29,7 +29,7 @@ func TestService(t *testing.T) { proto := `service AccountService { // comment rpc CreateAccount (CreateAccount) returns (ServiceFault); - rpc GetAccounts (stream Int64) returns (Account); + rpc GetAccounts (stream Int64) returns (Account); }` pr, err := newParserOn(proto).Parse() if err != nil { @@ -39,6 +39,9 @@ func TestService(t *testing.T) { if got, want := len(srv.Elements), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := srv.LineNumber, 1; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } rpc1 := srv.Elements[0].(*RPC) if got, want := rpc1.Name, "CreateAccount"; got != want { t.Fatalf("got [%v] want [%v]", got, want) @@ -46,6 +49,9 @@ func TestService(t *testing.T) { if got, want := rpc1.Doc().Message(), " comment"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } + if got, want := rpc1.LineNumber, 3; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } rpc2 := srv.Elements[1].(*RPC) if got, want := rpc2.Name, "GetAccounts"; got != want { t.Errorf("got [%v] want [%v]", got, want) @@ -57,9 +63,9 @@ func TestRPCWithOptionAggregateSyntax(t *testing.T) { // CreateAccount rpc CreateAccount (CreateAccount) returns (ServiceFault){ option (test_ident) = { - test: "test" + test: "test" test2:"test2" - }; + }; } }` pr, err := newParserOn(proto).Parse() diff --git a/syntax.go b/syntax.go index f479552..7122083 100644 --- a/syntax.go +++ b/syntax.go @@ -25,13 +25,14 @@ package proto // Syntax should have value "proto" type Syntax struct { + LineNumber int Comment *Comment Value string InlineComment *Comment } func (s *Syntax) parse(p *Parser) error { - if tok, lit := p.scanIgnoreWhitespace(); tok != tEQUALS { + if _, tok, lit := p.scanIgnoreWhitespace(); tok != tEQUALS { return p.unexpected(lit, "syntax =", s) } lit, ok := p.s.scanLiteral()