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