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 "bytes" 28 "fmt" 29 "text/scanner" 30) 31 32// Option is a protoc compiler option 33type Option struct { 34 Position scanner.Position 35 Comment *Comment 36 Name string 37 Constant Literal 38 IsEmbedded bool 39 AggregatedConstants []*NamedLiteral 40 InlineComment *Comment 41} 42 43// columns returns printable source tokens 44func (o *Option) columns() (cols []aligned) { 45 if !o.IsEmbedded { 46 cols = append(cols, leftAligned("option ")) 47 } else { 48 cols = append(cols, leftAligned(" [")) 49 } 50 cols = append(cols, o.keyValuePair(o.IsEmbedded)...) 51 if o.IsEmbedded { 52 cols = append(cols, leftAligned("]")) 53 } 54 if !o.IsEmbedded { 55 cols = append(cols, alignedSemicolon) 56 if o.InlineComment != nil { 57 cols = append(cols, notAligned(" //"), notAligned(o.InlineComment.Message())) 58 } 59 } 60 return 61} 62 63// keyValuePair returns key = value or "value" 64func (o *Option) keyValuePair(embedded bool) (cols []aligned) { 65 equals := alignedEquals 66 name := o.Name 67 if embedded { 68 return append(cols, leftAligned(name), equals, leftAligned(o.Constant.String())) // numbers right, strings left? TODO 69 } 70 return append(cols, rightAligned(name), equals, rightAligned(o.Constant.String())) 71} 72 73// parse reads an Option body 74// ( ident | "(" fullIdent ")" ) { "." ident } "=" constant ";" 75func (o *Option) parse(p *Parser) error { 76 pos, tok, lit := p.nextIdentifier() 77 if tLEFTPAREN == tok { 78 pos, tok, lit = p.nextIdentifier() 79 if tok != tIDENT { 80 if !isKeyword(tok) { 81 return p.unexpected(lit, "option full identifier", o) 82 } 83 } 84 pos, tok, _ = p.next() 85 if tok != tRIGHTPAREN { 86 return p.unexpected(lit, "full identifier closing )", o) 87 } 88 o.Name = fmt.Sprintf("(%s)", lit) 89 } else { 90 // non full ident 91 if tIDENT != tok { 92 if !isKeyword(tok) { 93 return p.unexpected(lit, "option identifier", o) 94 } 95 } 96 o.Name = lit 97 } 98 pos, tok, lit = p.next() 99 if tDOT == tok { 100 // extend identifier 101 pos, tok, lit = p.nextIdentifier() 102 if tok != tIDENT { 103 return p.unexpected(lit, "option postfix identifier", o) 104 } 105 o.Name = fmt.Sprintf("%s.%s", o.Name, lit) 106 pos, tok, lit = p.next() 107 } 108 if tEQUALS != tok { 109 return p.unexpected(lit, "option constant =", o) 110 } 111 r := p.peekNonWhitespace() 112 if '{' == r { 113 p.next() // consume { 114 return o.parseAggregate(p) 115 } 116 // non aggregate 117 l := new(Literal) 118 l.Position = pos 119 if err := l.parse(p); err != nil { 120 return err 121 } 122 o.Constant = *l 123 return nil 124} 125 126// inlineComment is part of commentInliner. 127func (o *Option) inlineComment(c *Comment) { 128 o.InlineComment = c 129} 130 131// Accept dispatches the call to the visitor. 132func (o *Option) Accept(v Visitor) { 133 v.VisitOption(o) 134} 135 136// Doc is part of Documented 137func (o *Option) Doc() *Comment { 138 return o.Comment 139} 140 141// Literal represents intLit,floatLit,strLit or boolLit 142type Literal struct { 143 Position scanner.Position 144 Source string 145 IsString bool 146} 147 148// String returns the source (if quoted then use double quote). 149func (l Literal) String() string { 150 var buf bytes.Buffer 151 if l.IsString { 152 buf.WriteRune('"') 153 } 154 buf.WriteString(l.Source) 155 if l.IsString { 156 buf.WriteRune('"') 157 } 158 return buf.String() 159} 160 161// parse expects to read a literal constant after =. 162func (l *Literal) parse(p *Parser) error { 163 pos, _, lit := p.next() 164 if "-" == lit { 165 // negative number 166 if err := l.parse(p); err != nil { 167 return err 168 } 169 // modify source and position 170 l.Position, l.Source = pos, "-"+l.Source 171 return nil 172 } 173 source := lit 174 isString := isString(lit) 175 if isString { 176 source = unQuote(source) 177 } 178 l.Position, l.Source, l.IsString = pos, source, isString 179 return nil 180} 181 182// NamedLiteral associates a name with a Literal 183type NamedLiteral struct { 184 *Literal 185 Name string 186} 187 188// parseAggregate reads options written using aggregate syntax 189func (o *Option) parseAggregate(p *Parser) error { 190 o.AggregatedConstants = []*NamedLiteral{} 191 for { 192 pos, tok, lit := p.next() 193 if tRIGHTSQUARE == tok { 194 p.nextPut(pos, tok, lit) 195 // caller has checked for open square ; will consume rightsquare, rightcurly and semicolon 196 return nil 197 } 198 if tRIGHTCURLY == tok { 199 continue 200 } 201 if tSEMICOLON == tok { 202 return nil 203 } 204 if tCOMMA == tok { 205 if len(o.AggregatedConstants) == 0 { 206 return p.unexpected(lit, "non-empty option aggregate key", o) 207 } 208 continue 209 } 210 if tIDENT != tok { 211 return p.unexpected(lit, "option aggregate key", o) 212 } 213 key := lit 214 pos, tok, lit = p.next() 215 if tCOLON != tok { 216 return p.unexpected(lit, "option aggregate key colon :", o) 217 } 218 l := new(Literal) 219 l.Position = pos 220 if err := l.parse(p); err != nil { 221 return err 222 } 223 o.AggregatedConstants = append(o.AggregatedConstants, &NamedLiteral{Name: key, Literal: l}) 224 } 225} 226