You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
5.7 KiB
211 lines
5.7 KiB
// Copyright (c) 2017 Ernest Micklei |
|
// |
|
// MIT License |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining |
|
// a copy of this software and associated documentation files (the |
|
// "Software"), to deal in the Software without restriction, including |
|
// without limitation the rights to use, copy, modify, merge, publish, |
|
// distribute, sublicense, and/or sell copies of the Software, and to |
|
// permit persons to whom the Software is furnished to do so, subject to |
|
// the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be |
|
// included in all copies or substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
package proto |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"runtime" |
|
"strconv" |
|
"strings" |
|
"text/scanner" |
|
) |
|
|
|
// Parser represents a parser. |
|
type Parser struct { |
|
debug bool |
|
scanner *scanner.Scanner |
|
buf *nextValues |
|
scannerErrors []error |
|
} |
|
|
|
// nextValues is to capture the result of next() |
|
type nextValues struct { |
|
pos scanner.Position |
|
tok token |
|
lit string |
|
} |
|
|
|
// NewParser returns a new instance of Parser. |
|
func NewParser(r io.Reader) *Parser { |
|
s := new(scanner.Scanner) |
|
s.Init(r) |
|
s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments |
|
p := &Parser{scanner: s} |
|
s.Error = p.handleScanError |
|
return p |
|
} |
|
|
|
// handleScanError is called from the underlying Scanner |
|
func (p *Parser) handleScanError(s *scanner.Scanner, msg string) { |
|
p.scannerErrors = append(p.scannerErrors, |
|
fmt.Errorf("go scanner error at %v = %v", s.Position, msg)) |
|
} |
|
|
|
// ignoreIllegalEscapesWhile is called for scanning constants of an option. |
|
// Such content can have a syntax that is not acceptable by the Go scanner. |
|
// This temporary installs a handler that ignores only one type of error: illegal char escape |
|
func (p *Parser) ignoreIllegalEscapesWhile(block func()) { |
|
// during block call change error handler |
|
p.scanner.Error = func(s *scanner.Scanner, msg string) { |
|
if strings.Contains(msg, "illegal char escape") { // too bad there is no constant for this in scanner pkg |
|
return |
|
} |
|
p.handleScanError(s, msg) |
|
} |
|
block() |
|
// restore |
|
p.scanner.Error = p.handleScanError |
|
} |
|
|
|
// Parse parses a proto definition. May return a parse or scanner error. |
|
func (p *Parser) Parse() (*Proto, error) { |
|
proto := new(Proto) |
|
if p.scanner.Filename != "" { |
|
proto.Filename = p.scanner.Filename |
|
} |
|
pro, parseError := proto.parse(p) |
|
// see if it was a scanner error |
|
if len(p.scannerErrors) > 0 { |
|
buf := new(bytes.Buffer) |
|
for _, each := range p.scannerErrors { |
|
fmt.Fprintln(buf, each) |
|
} |
|
return proto, errors.New(buf.String()) |
|
} |
|
return pro, parseError |
|
} |
|
|
|
// Filename is for reporting. Optional. |
|
func (p *Parser) Filename(f string) { |
|
p.scanner.Filename = f |
|
} |
|
|
|
// next returns the next token using the scanner or drain the buffer. |
|
func (p *Parser) next() (pos scanner.Position, tok token, lit string) { |
|
if p.buf != nil { |
|
// consume buf |
|
vals := *p.buf |
|
p.buf = nil |
|
return vals.pos, vals.tok, vals.lit |
|
} |
|
ch := p.scanner.Scan() |
|
if ch == scanner.EOF { |
|
return p.scanner.Position, tEOF, "" |
|
} |
|
lit = p.scanner.TokenText() |
|
return p.scanner.Position, asToken(lit), lit |
|
} |
|
|
|
// nextPut sets the buffer |
|
func (p *Parser) nextPut(pos scanner.Position, tok token, lit string) { |
|
p.buf = &nextValues{pos, tok, lit} |
|
} |
|
|
|
func (p *Parser) unexpected(found, expected string, obj interface{}) error { |
|
debug := "" |
|
if p.debug { |
|
_, file, line, _ := runtime.Caller(1) |
|
debug = fmt.Sprintf(" at %s:%d (with %#v)", file, line, obj) |
|
} |
|
return fmt.Errorf("%v: found %q but expected [%s]%s", p.scanner.Position, found, expected, debug) |
|
} |
|
|
|
func (p *Parser) nextInteger() (i int, err error) { |
|
_, tok, lit := p.next() |
|
if "-" == lit { |
|
i, err = p.nextInteger() |
|
return i * -1, err |
|
} |
|
if tok != tIDENT { |
|
return 0, errors.New("non integer") |
|
} |
|
if strings.HasPrefix(lit, "0x") { |
|
// hex decode |
|
var i64 int64 |
|
i64, err = strconv.ParseInt(lit, 0, 64) |
|
return int(i64), err |
|
} |
|
i, err = strconv.Atoi(lit) |
|
return |
|
} |
|
|
|
// nextIdentifier consumes tokens which may have one or more dot separators (namespaced idents). |
|
func (p *Parser) nextIdentifier() (pos scanner.Position, tok token, lit string) { |
|
return p.nextIdent(false) |
|
} |
|
|
|
// nextTypeName implements the Packages and Name Resolution for finding the name of the type. |
|
func (p *Parser) nextTypeName() (pos scanner.Position, tok token, lit string) { |
|
pos, tok, lit = p.nextIdent(false) |
|
if tDOT == tok { |
|
// leading dot allowed |
|
pos, tok, lit = p.nextIdent(false) |
|
lit = "." + lit |
|
} |
|
return |
|
} |
|
|
|
func (p *Parser) nextIdent(keywordStartAllowed bool) (pos scanner.Position, tok token, lit string) { |
|
pos, tok, lit = p.next() |
|
if tIDENT != tok { |
|
// can be keyword |
|
if !(isKeyword(tok) && keywordStartAllowed) { |
|
return |
|
} |
|
// proceed with keyword as first literal |
|
} |
|
startPos := pos |
|
fullLit := lit |
|
// see if identifier is namespaced |
|
for { |
|
r := p.scanner.Peek() |
|
if '.' != r { |
|
break |
|
} |
|
p.next() // consume dot |
|
pos, tok, lit := p.next() |
|
if tIDENT != tok && !isKeyword(tok) { |
|
p.nextPut(pos, tok, lit) |
|
break |
|
} |
|
fullLit = fmt.Sprintf("%s.%s", fullLit, lit) |
|
} |
|
return startPos, tIDENT, fullLit |
|
} |
|
|
|
func (p *Parser) peekNonWhitespace() rune { |
|
r := p.scanner.Peek() |
|
if r == scanner.EOF { |
|
return r |
|
} |
|
if isWhitespace(r) { |
|
// consume it |
|
p.scanner.Next() |
|
return p.peekNonWhitespace() |
|
} |
|
return r |
|
}
|
|
|