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	"strconv"
28	"text/scanner"
29)
30
31// Enum definition consists of a name and an enum body.
32type Enum struct {
33	Position scanner.Position
34	Comment  *Comment
35	Name     string
36	Elements []Visitee
37}
38
39// Accept dispatches the call to the visitor.
40func (e *Enum) Accept(v Visitor) {
41	v.VisitEnum(e)
42}
43
44// Doc is part of Documented
45func (e *Enum) Doc() *Comment {
46	return e.Comment
47}
48
49// addElement is part of elementContainer
50func (e *Enum) addElement(v Visitee) {
51	e.Elements = append(e.Elements, v)
52}
53
54// elements is part of elementContainer
55func (e *Enum) elements() []Visitee {
56	return e.Elements
57}
58
59// takeLastComment is part of elementContainer
60// removes and returns the last element of the list if it is a Comment.
61func (e *Enum) takeLastComment() (last *Comment) {
62	last, e.Elements = takeLastComment(e.Elements)
63	return
64}
65
66func (e *Enum) parse(p *Parser) error {
67	pos, tok, lit := p.next()
68	if tok != tIDENT {
69		if !isKeyword(tok) {
70			return p.unexpected(lit, "enum identifier", e)
71		}
72	}
73	e.Name = lit
74	_, tok, lit = p.next()
75	if tok != tLEFTCURLY {
76		return p.unexpected(lit, "enum opening {", e)
77	}
78	for {
79		pos, tok, lit = p.next()
80		switch tok {
81		case tCOMMENT:
82			if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged?
83				e.Elements = append(e.Elements, com)
84			}
85		case tOPTION:
86			v := new(Option)
87			v.Position = pos
88			v.Comment = e.takeLastComment()
89			err := v.parse(p)
90			if err != nil {
91				return err
92			}
93			e.Elements = append(e.Elements, v)
94		case tRIGHTCURLY, tEOF:
95			goto done
96		case tSEMICOLON:
97			maybeScanInlineComment(p, e)
98		default:
99			p.nextPut(pos, tok, lit)
100			f := new(EnumField)
101			f.Position = pos
102			f.Comment = e.takeLastComment()
103			err := f.parse(p)
104			if err != nil {
105				return err
106			}
107			e.Elements = append(e.Elements, f)
108		}
109	}
110done:
111	if tok != tRIGHTCURLY {
112		return p.unexpected(lit, "enum closing }", e)
113	}
114	return nil
115}
116
117// EnumField is part of the body of an Enum.
118type EnumField struct {
119	Position      scanner.Position
120	Comment       *Comment
121	Name          string
122	Integer       int
123	ValueOption   *Option
124	InlineComment *Comment
125}
126
127// Accept dispatches the call to the visitor.
128func (f *EnumField) Accept(v Visitor) {
129	v.VisitEnumField(f)
130}
131
132// inlineComment is part of commentInliner.
133func (f *EnumField) inlineComment(c *Comment) {
134	f.InlineComment = c
135}
136
137// Doc is part of Documented
138func (f *EnumField) Doc() *Comment {
139	return f.Comment
140}
141
142// columns returns printable source tokens
143func (f EnumField) columns() (cols []aligned) {
144	cols = append(cols, leftAligned(f.Name), alignedEquals, rightAligned(strconv.Itoa(f.Integer)))
145	if f.ValueOption != nil {
146		cols = append(cols, f.ValueOption.columns()...)
147	}
148	cols = append(cols, alignedSemicolon)
149	if f.InlineComment != nil {
150		cols = append(cols, f.InlineComment.alignedInlinePrefix(), notAligned(f.InlineComment.Message()))
151	}
152	return
153}
154
155func (f *EnumField) parse(p *Parser) error {
156	_, tok, lit := p.nextIdentifier()
157	if tok != tIDENT {
158		if !isKeyword(tok) {
159			return p.unexpected(lit, "enum field identifier", f)
160		}
161	}
162	f.Name = lit
163	pos, tok, lit := p.next()
164	if tok != tEQUALS {
165		return p.unexpected(lit, "enum field =", f)
166	}
167	i, err := p.nextInteger()
168	if err != nil {
169		return p.unexpected(lit, "enum field integer", f)
170	}
171	f.Integer = i
172	pos, tok, lit = p.next()
173	if tok == tLEFTSQUARE {
174		o := new(Option)
175		o.Position = pos
176		o.IsEmbedded = true
177		err := o.parse(p)
178		if err != nil {
179			return err
180		}
181		f.ValueOption = o
182		pos, tok, lit = p.next()
183		if tok != tRIGHTSQUARE {
184			return p.unexpected(lit, "option closing ]", f)
185		}
186	}
187	if tSEMICOLON == tok {
188		p.nextPut(pos, tok, lit) // put back this token for scanning inline comment
189	}
190	return nil
191}
192