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
26import (
27	"text/scanner"
28)
29
30// Service defines a set of RPC calls.
31type Service struct {
32	Position scanner.Position
33	Comment  *Comment
34	Name     string
35	Elements []Visitee
36	Parent   Visitee
37}
38
39// Accept dispatches the call to the visitor.
40func (s *Service) Accept(v Visitor) {
41	v.VisitService(s)
42}
43
44// Doc is part of Documented
45func (s *Service) Doc() *Comment {
46	return s.Comment
47}
48
49// addElement is part of elementContainer
50func (s *Service) addElement(v Visitee) {
51	v.parent(s)
52	s.Elements = append(s.Elements, v)
53}
54
55// elements is part of elementContainer
56func (s *Service) elements() []Visitee {
57	return s.Elements
58}
59
60// takeLastComment is part of elementContainer
61// removes and returns the last elements of the list if it is a Comment.
62func (s *Service) takeLastComment(expectedOnLine int) (last *Comment) {
63	last, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, expectedOnLine)
64	return
65}
66
67// parse continues after reading "service"
68func (s *Service) parse(p *Parser) error {
69	pos, tok, lit := p.nextIdentifier()
70	if tok != tIDENT {
71		if !isKeyword(tok) {
72			return p.unexpected(lit, "service identifier", s)
73		}
74	}
75	s.Name = lit
76	pos, tok, lit = p.next()
77	if tok != tLEFTCURLY {
78		return p.unexpected(lit, "service opening {", s)
79	}
80	for {
81		pos, tok, lit = p.next()
82		switch tok {
83		case tCOMMENT:
84			if com := mergeOrReturnComment(s.Elements, lit, pos); com != nil { // not merged?
85				s.addElement(com)
86			}
87		case tOPTION:
88			opt := new(Option)
89			opt.Position = pos
90			opt.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.elements(), pos.Line-1)
91			if err := opt.parse(p); err != nil {
92				return err
93			}
94			s.addElement(opt)
95		case tRPC:
96			rpc := new(RPC)
97			rpc.Position = pos
98			rpc.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, pos.Line-1)
99			err := rpc.parse(p)
100			if err != nil {
101				return err
102			}
103			s.addElement(rpc)
104			maybeScanInlineComment(p, s)
105		case tSEMICOLON:
106			maybeScanInlineComment(p, s)
107		case tRIGHTCURLY:
108			goto done
109		default:
110			return p.unexpected(lit, "service comment|rpc", s)
111		}
112	}
113done:
114	return nil
115}
116
117func (s *Service) parent(v Visitee) { s.Parent = v }
118
119// RPC represents an rpc entry in a message.
120type RPC struct {
121	Position       scanner.Position
122	Comment        *Comment
123	Name           string
124	RequestType    string
125	StreamsRequest bool
126	ReturnsType    string
127	StreamsReturns bool
128	Elements       []Visitee
129	InlineComment  *Comment
130	Parent         Visitee
131
132	// Options field is DEPRECATED, use Elements instead.
133	Options []*Option
134}
135
136// Accept dispatches the call to the visitor.
137func (r *RPC) Accept(v Visitor) {
138	v.VisitRPC(r)
139}
140
141// Doc is part of Documented
142func (r *RPC) Doc() *Comment {
143	return r.Comment
144}
145
146// inlineComment is part of commentInliner.
147func (r *RPC) inlineComment(c *Comment) {
148	r.InlineComment = c
149}
150
151// parse continues after reading "rpc"
152func (r *RPC) parse(p *Parser) error {
153	pos, tok, lit := p.next()
154	if tok != tIDENT {
155		return p.unexpected(lit, "rpc method", r)
156	}
157	r.Name = lit
158	pos, tok, lit = p.next()
159	if tok != tLEFTPAREN {
160		return p.unexpected(lit, "rpc type opening (", r)
161	}
162	pos, tok, lit = p.nextTypeName()
163	if tSTREAM == tok {
164		r.StreamsRequest = true
165		pos, tok, lit = p.nextTypeName()
166	}
167	if tok != tIDENT {
168		return p.unexpected(lit, "rpc stream | request type", r)
169	}
170	r.RequestType = lit
171	pos, tok, lit = p.next()
172	if tok != tRIGHTPAREN {
173		return p.unexpected(lit, "rpc type closing )", r)
174	}
175	pos, tok, lit = p.next()
176	if tok != tRETURNS {
177		return p.unexpected(lit, "rpc returns", r)
178	}
179	pos, tok, lit = p.next()
180	if tok != tLEFTPAREN {
181		return p.unexpected(lit, "rpc type opening (", r)
182	}
183	pos, tok, lit = p.nextTypeName()
184	if tSTREAM == tok {
185		r.StreamsReturns = true
186		pos, tok, lit = p.nextTypeName()
187	}
188	if tok != tIDENT {
189		return p.unexpected(lit, "rpc stream | returns type", r)
190	}
191	r.ReturnsType = lit
192	pos, tok, lit = p.next()
193	if tok != tRIGHTPAREN {
194		return p.unexpected(lit, "rpc type closing )", r)
195	}
196	pos, tok, lit = p.next()
197	if tSEMICOLON == tok {
198		p.nextPut(pos, tok, lit) // allow for inline comment parsing
199		return nil
200	}
201	if tLEFTCURLY == tok {
202		// parse options
203		for {
204			pos, tok, lit = p.next()
205			if tRIGHTCURLY == tok {
206				break
207			}
208			if isComment(lit) {
209				if com := mergeOrReturnComment(r.elements(), lit, pos); com != nil { // not merged?
210					r.addElement(com)
211					continue
212				}
213			}
214			if tSEMICOLON == tok {
215				maybeScanInlineComment(p, r)
216				continue
217			}
218			if tOPTION == tok {
219				o := new(Option)
220				o.Position = pos
221				if err := o.parse(p); err != nil {
222					return err
223				}
224				r.addElement(o)
225			}
226		}
227	}
228	return nil
229}
230
231// addElement is part of elementContainer
232func (r *RPC) addElement(v Visitee) {
233	v.parent(r)
234	r.Elements = append(r.Elements, v)
235	// handle deprecated field
236	if option, ok := v.(*Option); ok {
237		r.Options = append(r.Options, option)
238	}
239}
240
241// elements is part of elementContainer
242func (r *RPC) elements() []Visitee {
243	return r.Elements
244}
245
246func (r *RPC) takeLastComment(expectedOnLine int) (last *Comment) {
247	last, r.Elements = takeLastCommentIfEndsOnLine(r.Elements, expectedOnLine)
248	return
249}
250
251func (r *RPC) parent(v Visitee) { r.Parent = v }
252