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 "errors" 29 "fmt" 30 "io" 31 "runtime" 32 "strconv" 33 "text/scanner" 34) 35 36var startPosition = scanner.Position{Line: 1, Column: 1} 37 38// Parser represents a parser. 39type Parser struct { 40 debug bool 41 scanner *scanner.Scanner 42 buf *nextValues 43 scannerErrors []error 44} 45 46// nextValues is to capture the result of next() 47type nextValues struct { 48 pos scanner.Position 49 tok token 50 lit string 51} 52 53// NewParser returns a new instance of Parser. 54func NewParser(r io.Reader) *Parser { 55 s := new(scanner.Scanner) 56 s.Init(r) 57 s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments 58 p := &Parser{scanner: s} 59 s.Error = p.handleScanError 60 return p 61} 62 63// handleScanError is called from the underlying Scanner 64func (p *Parser) handleScanError(s *scanner.Scanner, msg string) { 65 p.scannerErrors = append(p.scannerErrors, 66 fmt.Errorf("go scanner error at %v = %v", s.Position, msg)) 67} 68 69// Parse parses a proto definition. May return a parse or scanner error. 70func (p *Parser) Parse() (*Proto, error) { 71 proto := new(Proto) 72 parseError := proto.parse(p) 73 // see if it was a scanner error 74 if len(p.scannerErrors) > 0 { 75 buf := new(bytes.Buffer) 76 for _, each := range p.scannerErrors { 77 fmt.Fprintln(buf, each) 78 } 79 return proto, errors.New(buf.String()) 80 } 81 return proto, parseError 82} 83 84// Filename is for reporting. Optional. 85func (p *Parser) Filename(f string) { 86 p.scanner.Filename = f 87} 88 89// next returns the next token using the scanner or drain the buffer. 90func (p *Parser) next() (pos scanner.Position, tok token, lit string) { 91 if p.buf != nil { 92 // consume buf 93 vals := *p.buf 94 p.buf = nil 95 return vals.pos, vals.tok, vals.lit 96 } 97 ch := p.scanner.Scan() 98 if ch == scanner.EOF { 99 return p.scanner.Position, tEOF, "" 100 } 101 lit = p.scanner.TokenText() 102 return p.scanner.Position, asToken(lit), lit 103} 104 105// nextPut sets the buffer 106func (p *Parser) nextPut(pos scanner.Position, tok token, lit string) { 107 p.buf = &nextValues{pos, tok, lit} 108} 109 110func (p *Parser) unexpected(found, expected string, obj interface{}) error { 111 debug := "" 112 if p.debug { 113 _, file, line, _ := runtime.Caller(1) 114 debug = fmt.Sprintf(" at %s:%d (with %#v)", file, line, obj) 115 } 116 return fmt.Errorf("found %q on %v, expected [%s]%s", found, p.scanner.Position, expected, debug) 117} 118 119func (p *Parser) nextInteger() (i int, err error) { 120 _, tok, lit := p.next() 121 if "-" == lit { 122 i, err = p.nextInteger() 123 return i * -1, err 124 } 125 if tok != tIDENT { 126 return 0, errors.New("non integer") 127 } 128 i, err = strconv.Atoi(lit) 129 return 130} 131 132// nextIdentifier consumes tokens which may have one or more dot separators (namespaced idents). 133func (p *Parser) nextIdentifier() (pos scanner.Position, tok token, lit string) { 134 pos, tok, lit = p.next() 135 if tIDENT != tok { 136 return 137 } 138 startPos := pos 139 fullLit := lit 140 // see if identifier is namespaced 141 for { 142 r := p.scanner.Peek() 143 if '.' != r { 144 break 145 } 146 p.next() // consume dot 147 pos, tok, lit := p.next() 148 if tIDENT != tok { 149 p.nextPut(pos, tok, lit) 150 break 151 } 152 fullLit = fmt.Sprintf("%s.%s", fullLit, lit) 153 } 154 return startPos, tIDENT, fullLit 155} 156 157func (p *Parser) peekNonWhitespace() rune { 158 r := p.scanner.Peek() 159 if r == scanner.EOF { 160 return r 161 } 162 if isWhitespace(r) { 163 // consume it 164 p.scanner.Next() 165 return p.peekNonWhitespace() 166 } 167 return r 168} 169