1// Copyright (c) 2017 Ernest Micklei
2//
3// MIT License
4//
5// Permission is hereby granted, free of charge, to any person obtaining
6// a copy of this software and associated documentation files (the
7// "Software"), to deal in the Software without restriction, including
8// without limitation the rights to use, copy, modify, merge, publish,
9// distribute, sublicense, and/or sell copies of the Software, and to
10// permit persons to whom the Software is furnished to do so, subject to
11// the following conditions:
12//
13// The above copyright notice and this permission notice shall be
14// included in all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24package proto
25
26// Proto represents a .proto definition
27type Proto struct {
28	Filename string
29	Elements []Visitee
30}
31
32// Accept dispatches the call to the visitor.
33func (proto *Proto) Accept(v Visitor) {
34	// As Proto is not (yet) a Visitee, we enumerate its elements instead
35	//v.VisitProto(proto)
36	for _, each := range proto.Elements {
37		each.Accept(v)
38	}
39}
40
41// addElement is part of elementContainer
42func (proto *Proto) addElement(v Visitee) {
43	v.parent(proto)
44	proto.Elements = append(proto.Elements, v)
45}
46
47// elements is part of elementContainer
48func (proto *Proto) elements() []Visitee {
49	return proto.Elements
50}
51
52// takeLastComment is part of elementContainer
53// removes and returns the last element of the list if it is a Comment.
54func (proto *Proto) takeLastComment(expectedOnLine int) (last *Comment) {
55	last, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, expectedOnLine)
56	return
57}
58
59// parse parsers a complete .proto definition source.
60func (proto *Proto) parse(p *Parser) error {
61	for {
62		pos, tok, lit := p.next()
63		switch {
64		case isComment(lit):
65			if com := mergeOrReturnComment(proto.Elements, lit, pos); com != nil { // not merged?
66				proto.Elements = append(proto.Elements, com)
67			}
68		case tOPTION == tok:
69			o := new(Option)
70			o.Position = pos
71			o.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
72			if err := o.parse(p); err != nil {
73				return err
74			}
75			proto.addElement(o)
76		case tSYNTAX == tok:
77			s := new(Syntax)
78			s.Position = pos
79			s.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
80			if err := s.parse(p); err != nil {
81				return err
82			}
83			proto.addElement(s)
84		case tIMPORT == tok:
85			im := new(Import)
86			im.Position = pos
87			im.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
88			if err := im.parse(p); err != nil {
89				return err
90			}
91			proto.addElement(im)
92		case tENUM == tok:
93			enum := new(Enum)
94			enum.Position = pos
95			enum.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
96			if err := enum.parse(p); err != nil {
97				return err
98			}
99			proto.addElement(enum)
100		case tSERVICE == tok:
101			service := new(Service)
102			service.Position = pos
103			service.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
104			err := service.parse(p)
105			if err != nil {
106				return err
107			}
108			proto.addElement(service)
109		case tPACKAGE == tok:
110			pkg := new(Package)
111			pkg.Position = pos
112			pkg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
113			if err := pkg.parse(p); err != nil {
114				return err
115			}
116			proto.addElement(pkg)
117		case tMESSAGE == tok:
118			msg := new(Message)
119			msg.Position = pos
120			msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
121			if err := msg.parse(p); err != nil {
122				return err
123			}
124			proto.addElement(msg)
125		// BEGIN proto2
126		case tEXTEND == tok:
127			msg := new(Message)
128			msg.Position = pos
129			msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
130			msg.IsExtend = true
131			if err := msg.parse(p); err != nil {
132				return err
133			}
134			proto.addElement(msg)
135		// END proto2
136		case tSEMICOLON == tok:
137			maybeScanInlineComment(p, proto)
138			// continue
139		case tEOF == tok:
140			goto done
141		default:
142			return p.unexpected(lit, ".proto element {comment|option|import|syntax|enum|service|package|message}", p)
143		}
144	}
145done:
146	return nil
147}
148
149func (proto *Proto) parent(v Visitee) {}
150
151// elementContainer unifies types that have elements.
152type elementContainer interface {
153	addElement(v Visitee)
154	elements() []Visitee
155	takeLastComment(expectedOnLine int) *Comment
156}
157