Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"comment.go",
"enum.go",
"extensions.go",
"field.go",
"group.go",
"import.go",
"message.go",
"oneof.go",
"option.go",
"package.go",
"parser.go",
"proto.go",
"range.go",
"reserved.go",
"service.go",
"syntax.go",
"token.go",
"visitor.go",
"walk.go",
],
importmap = "go-common/vendor/github.com/emicklei/proto",
importpath = "go-common/app/admin/ep/melloi/service/proto",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,22 @@
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.

View File

@@ -0,0 +1,81 @@
SRCS := $(shell find . -name '*.go')
BIN_DEPS := \
github.com/golang/lint/golint \
github.com/kisielk/errcheck \
honnef.co/go/tools/cmd/staticcheck \
honnef.co/go/tools/cmd/unused
.PHONY: all
all: test
.PHONY: deps
deps:
go get -d -v ./...
.PHONY: updatedeps
updatedeps:
go get -d -v -u -f ./...
.PHONY: bindeps
bindeps:
go get -v $(BIN_DEPS)
.PHONY: updatebindeps
updatebindeps:
go get -u -v $(BIN_DEPS)
.PHONY: testdeps
testdeps: bindeps
go get -d -v -t ./...
.PHONY: updatetestdeps
updatetestdeps: updatebindeps
go get -d -v -t -u -f ./...
.PHONY: install
install: deps
go install ./...
.PHONY: golint
golint: testdeps
@# TODO: readd cmd/proto2gql when fixed
@#for file in $(SRCS); do
for file in $(shell echo $(SRCS) | grep -v cmd/proto2gql); do \
golint $${file}; \
if [ -n "$$(golint $${file})" ]; then \
exit 1; \
fi; \
done
.PHONY: vet
vet: testdeps
go vet ./...
.PHONY: testdeps
errcheck: testdeps
errcheck ./...
.PHONY: staticcheck
staticcheck: testdeps
staticcheck ./...
.PHONY: unused
unused: testdeps
unused ./...
.PHONY: lint
# TODO: readd errcheck and unused when fixed
#lint: golint vet errcheck staticcheck unused
lint: golint vet staticcheck
.PHONY: test
test: testdeps lint
go test -race ./...
.PHONY: clean
clean:
go clean -i ./...
integration:
PB=y go test -cover

View File

@@ -0,0 +1,50 @@
# proto
[![Build Status](https://travis-ci.org/emicklei/proto.png)](https://travis-ci.org/emicklei/proto)
[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/proto)](https://goreportcard.com/report/github.com/emicklei/proto)
[![GoDoc](https://godoc.org/github.com/emicklei/proto?status.svg)](https://godoc.org/github.com/emicklei/proto)
Package in Go for parsing Google Protocol Buffers [.proto files version 2 + 3] (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec)
### install
go get -u -v github.com/emicklei/proto
### usage
package main
import (
"fmt"
"os"
"github.com/emicklei/proto"
)
func main() {
reader, _ := os.Open("test.proto")
defer reader.Close()
parser := proto.NewParser(reader)
definition, _ := parser.Parse()
proto.Walk(definition,
proto.WithService(handleService),
proto.WithMessage(handleMessage))
}
func handleService(s *proto.Service) {
fmt.Println(s.Name)
}
func handleMessage(m *proto.Message) {
fmt.Println(m.Name)
}
### contributions
See (https://github.com/emicklei/proto-contrib) for other contributions on top of this package such as protofmt, proto2xsd and proto2gql.
© 2017, [ernestmicklei.com](http://ernestmicklei.com). MIT License. Contributions welcome.

View File

@@ -0,0 +1,146 @@
// 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 (
"strings"
"text/scanner"
)
// Comment one or more comment text lines, either in c- or c++ style.
type Comment struct {
Position scanner.Position
// Lines are comment text lines without prefixes //, ///, /* or suffix */
Lines []string
Cstyle bool // refers to /* ... */, C++ style is using //
ExtraSlash bool // is true if the comment starts with 3 slashes
}
// newComment returns a comment.
func newComment(pos scanner.Position, lit string) *Comment {
extraSlash := strings.HasPrefix(lit, "///")
isCstyle := strings.HasPrefix(lit, "/*") && strings.HasSuffix(lit, "*/")
var lines []string
if isCstyle {
withoutMarkers := strings.TrimRight(strings.TrimLeft(lit, "/*"), "*/")
lines = strings.Split(withoutMarkers, "\n")
} else {
lines = strings.Split(strings.TrimLeft(lit, "/"), "\n")
}
return &Comment{Position: pos, Lines: lines, Cstyle: isCstyle, ExtraSlash: extraSlash}
}
//type inlineComment struct {
// line string
// extraSlash bool
//}
// Accept dispatches the call to the visitor.
func (c *Comment) Accept(v Visitor) {
v.VisitComment(c)
}
// Merge appends all lines from the argument comment.
func (c *Comment) Merge(other *Comment) {
c.Lines = append(c.Lines, other.Lines...)
c.Cstyle = c.Cstyle || other.Cstyle
}
func (c Comment) hasTextOnLine(line int) bool {
if len(c.Lines) == 0 {
return false
}
return c.Position.Line <= line && line <= c.Position.Line+len(c.Lines)-1
}
// Message returns the first line or empty if no lines.
func (c Comment) Message() string {
if len(c.Lines) == 0 {
return ""
}
return c.Lines[0]
}
// commentInliner is for types that can have an inline comment.
type commentInliner interface {
inlineComment(c *Comment)
}
// maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added.
func maybeScanInlineComment(p *Parser, c elementContainer) {
currentPos := p.scanner.Position
// see if there is an inline Comment
pos, tok, lit := p.next()
esize := len(c.elements())
// seen comment and on same line and elements have been added
if tCOMMENT == tok && pos.Line == currentPos.Line && esize > 0 {
// if the last added element can have an inline comment then set it
last := c.elements()[esize-1]
if inliner, ok := last.(commentInliner); ok {
// TODO skip multiline?
inliner.inlineComment(newComment(pos, lit))
}
} else {
p.nextPut(pos, tok, lit)
}
}
// takeLastCommentIfEndsOnLine removes and returns the last element of the list if it is a Comment
func takeLastCommentIfEndsOnLine(list []Visitee, line int) (*Comment, []Visitee) {
if len(list) == 0 {
return nil, list
}
if last, ok := list[len(list)-1].(*Comment); ok && last.hasTextOnLine(line) {
return last, list[:len(list)-1]
}
return nil, list
}
// mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line).
func mergeOrReturnComment(elements []Visitee, lit string, pos scanner.Position) *Comment {
com := newComment(pos, lit)
esize := len(elements)
if esize == 0 {
return com
}
// last element must be a comment to merge
last, ok := elements[esize-1].(*Comment)
if !ok {
return com
}
// do not merge c-style comments
if last.Cstyle {
return com
}
// last comment has text on previous line
// TODO handle last line of file could be inline comment
if !last.hasTextOnLine(pos.Line - 1) {
return com
}
last.Merge(com)
return nil
}
// parent is part of elementContainer
func (c *Comment) parent(Visitee) {}

View File

@@ -0,0 +1,210 @@
// 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 (
"text/scanner"
)
// Enum definition consists of a name and an enum body.
type Enum struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (e *Enum) Accept(v Visitor) {
v.VisitEnum(e)
}
// Doc is part of Documented
func (e *Enum) Doc() *Comment {
return e.Comment
}
// addElement is part of elementContainer
func (e *Enum) addElement(v Visitee) {
v.parent(e)
e.Elements = append(e.Elements, v)
}
// elements is part of elementContainer
func (e *Enum) elements() []Visitee {
return e.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (e *Enum) takeLastComment(expectedOnLine int) (last *Comment) {
last, e.Elements = takeLastCommentIfEndsOnLine(e.Elements, expectedOnLine)
return
}
func (e *Enum) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "enum identifier", e)
}
}
e.Name = lit
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "enum opening {", e)
}
for {
pos, tok, lit = p.next()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged?
e.addElement(com)
}
case tOPTION:
v := new(Option)
v.Position = pos
v.Comment = e.takeLastComment(pos.Line)
err := v.parse(p)
if err != nil {
return err
}
e.addElement(v)
case tRIGHTCURLY, tEOF:
goto done
case tSEMICOLON:
maybeScanInlineComment(p, e)
case tRESERVED:
r := new(Reserved)
r.Position = pos
r.Comment = e.takeLastComment(pos.Line - 1)
if err := r.parse(p); err != nil {
return err
}
e.addElement(r)
default:
p.nextPut(pos, tok, lit)
f := new(EnumField)
f.Position = pos
f.Comment = e.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
e.addElement(f)
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "enum closing }", e)
}
return nil
}
// parent is part of elementContainer
func (e *Enum) parent(p Visitee) { e.Parent = p }
// EnumField is part of the body of an Enum.
type EnumField struct {
Position scanner.Position
Comment *Comment
Name string
Integer int
// ValueOption is deprecated, use Elements instead
ValueOption *Option
Elements []Visitee // such as Option and Comment
InlineComment *Comment
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (f *EnumField) Accept(v Visitor) {
v.VisitEnumField(f)
}
// inlineComment is part of commentInliner.
func (f *EnumField) inlineComment(c *Comment) {
f.InlineComment = c
}
// Doc is part of Documented
func (f *EnumField) Doc() *Comment {
return f.Comment
}
func (f *EnumField) parse(p *Parser) (err error) {
_, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "enum field identifier", f)
}
}
f.Name = lit
pos, tok, lit := p.next()
if tok != tEQUALS {
return p.unexpected(lit, "enum field =", f)
}
var i int
if i, err = p.nextInteger(); err != nil {
return p.unexpected(err.Error(), "enum field integer", f)
}
f.Integer = i
pos, tok, lit = p.next()
if tok == tLEFTSQUARE {
for {
o := new(Option)
o.Position = pos
o.IsEmbedded = true
err := o.parse(p)
if err != nil {
return err
}
// update deprecated field with the last option found
f.ValueOption = o
f.addElement(o)
pos, tok, lit = p.next()
if tok == tCOMMA {
continue
}
if tok == tRIGHTSQUARE {
break
}
}
}
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit) // put back this token for scanning inline comment
}
return
}
// addElement is part of elementContainer
func (f *EnumField) addElement(v Visitee) {
v.parent(f)
f.Elements = append(f.Elements, v)
}
func (f *EnumField) parent(v Visitee) { f.Parent = v }

View File

@@ -0,0 +1,61 @@
// 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 (
"text/scanner"
)
// Extensions declare that a range of field numbers in a message are available for third-party extensions.
// proto2 only
type Extensions struct {
Position scanner.Position
Comment *Comment
Ranges []Range
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (e *Extensions) inlineComment(c *Comment) {
e.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (e *Extensions) Accept(v Visitor) {
v.VisitExtensions(e)
}
// parse expects ranges
func (e *Extensions) parse(p *Parser) error {
list, err := parseRanges(p, e)
if err != nil {
return err
}
e.Ranges = list
return nil
}
// parent is part of elementContainer
func (e *Extensions) parent(p Visitee) { e.Parent = p }

View File

@@ -0,0 +1,180 @@
// 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 (
"text/scanner"
)
// Field is an abstract message field.
type Field struct {
Position scanner.Position
Comment *Comment
Name string
Type string
Sequence int
Options []*Option
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (f *Field) inlineComment(c *Comment) {
f.InlineComment = c
}
// NormalField represents a field in a Message.
type NormalField struct {
*Field
Repeated bool
Optional bool // proto2
Required bool // proto2
}
func newNormalField() *NormalField { return &NormalField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (f *NormalField) Accept(v Visitor) {
v.VisitNormalField(f)
}
// Doc is part of Documented
func (f *NormalField) Doc() *Comment {
return f.Comment
}
// parse expects:
// [ "repeated" | "optional" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
func (f *NormalField) parse(p *Parser) error {
for {
_, tok, lit := p.nextTypeName()
switch tok {
case tREPEATED:
f.Repeated = true
return f.parse(p)
case tOPTIONAL: // proto2
f.Optional = true
return f.parse(p)
case tIDENT:
f.Type = lit
return parseFieldAfterType(f.Field, p)
default:
goto done
}
}
done:
return nil
}
// parseFieldAfterType expects:
// fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";
func parseFieldAfterType(f *Field, p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "field identifier", f)
}
}
f.Name = lit
pos, tok, lit = p.next()
if tok != tEQUALS {
return p.unexpected(lit, "field =", f)
}
i, err := p.nextInteger()
if err != nil {
return p.unexpected(lit, "field sequence number", f)
}
f.Sequence = i
// see if there are options
pos, tok, _ = p.next()
if tLEFTSQUARE != tok {
p.nextPut(pos, tok, lit)
return nil
}
// consume options
for {
o := new(Option)
o.Position = pos
o.IsEmbedded = true
err := o.parse(p)
if err != nil {
return err
}
f.Options = append(f.Options, o)
pos, tok, lit = p.next()
if tRIGHTSQUARE == tok {
break
}
if tCOMMA != tok {
return p.unexpected(lit, "option ,", o)
}
}
return nil
}
// MapField represents a map entry in a message.
type MapField struct {
*Field
KeyType string
}
func newMapField() *MapField { return &MapField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (f *MapField) Accept(v Visitor) {
v.VisitMapField(f)
}
// parse expects:
// mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
// keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" |
// "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string"
func (f *MapField) parse(p *Parser) error {
_, tok, lit := p.next()
if tLESS != tok {
return p.unexpected(lit, "map keyType <", f)
}
_, tok, lit = p.nextTypeName()
if tIDENT != tok {
return p.unexpected(lit, "map identifier", f)
}
f.KeyType = lit
_, tok, lit = p.next()
if tCOMMA != tok {
return p.unexpected(lit, "map type separator ,", f)
}
_, tok, lit = p.nextTypeName()
if tIDENT != tok {
return p.unexpected(lit, "map valueType identifier", f)
}
f.Type = lit
_, tok, lit = p.next()
if tGREATER != tok {
return p.unexpected(lit, "map valueType >", f)
}
return parseFieldAfterType(f.Field, p)
}
func (f *Field) parent(v Visitee) { f.Parent = v }

View File

@@ -0,0 +1 @@
module github.com/emicklei/proto

View File

@@ -0,0 +1,99 @@
// 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 (
"text/scanner"
)
// Group represents a (proto2 only) group.
// https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#group_field
type Group struct {
Position scanner.Position
Comment *Comment
Name string
Optional bool
Repeated bool
Required bool
Sequence int
Elements []Visitee
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (g *Group) Accept(v Visitor) {
v.VisitGroup(g)
}
// addElement is part of elementContainer
func (g *Group) addElement(v Visitee) {
v.parent(g)
g.Elements = append(g.Elements, v)
}
// elements is part of elementContainer
func (g *Group) elements() []Visitee {
return g.Elements
}
// Doc is part of Documented
func (g *Group) Doc() *Comment {
return g.Comment
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (g *Group) takeLastComment(expectedOnLine int) (last *Comment) {
last, g.Elements = takeLastCommentIfEndsOnLine(g.Elements, expectedOnLine)
return
}
// parse expects:
// groupName "=" fieldNumber { messageBody }
func (g *Group) parse(p *Parser) (err error) {
_, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "group name", g)
}
}
g.Name = lit
_, tok, lit = p.next()
if tok != tEQUALS {
return p.unexpected(lit, "group =", g)
}
var i int
if i, err = p.nextInteger(); err != nil {
return p.unexpected(lit, "group sequence number", g)
}
g.Sequence = i
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "group opening {", g)
}
return parseMessageBody(p, g)
}
func (g *Group) parent(v Visitee) { g.Parent = v }

View File

@@ -0,0 +1,72 @@
// 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 (
"text/scanner"
)
// Import holds a filename to another .proto definition.
type Import struct {
Position scanner.Position
Comment *Comment
Filename string
Kind string // weak, public, <empty>
InlineComment *Comment
Parent Visitee
}
func (i *Import) parse(p *Parser) error {
_, tok, lit := p.next()
switch tok {
case tWEAK:
i.Kind = lit
return i.parse(p)
case tPUBLIC:
i.Kind = lit
return i.parse(p)
case tIDENT:
i.Filename = unQuote(lit)
default:
return p.unexpected(lit, "import classifier weak|public|quoted", i)
}
return nil
}
// Accept dispatches the call to the visitor.
func (i *Import) Accept(v Visitor) {
v.VisitImport(i)
}
// inlineComment is part of commentInliner.
func (i *Import) inlineComment(c *Comment) {
i.InlineComment = c
}
// Doc is part of Documented
func (i *Import) Doc() *Comment {
return i.Comment
}
func (i *Import) parent(v Visitee) { i.Parent = v }

View File

@@ -0,0 +1,232 @@
// 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 (
"text/scanner"
)
// Message consists of a message name and a message body.
type Message struct {
Position scanner.Position
Comment *Comment
Name string
IsExtend bool
Elements []Visitee
Parent Visitee
}
func (m *Message) groupName() string {
if m.IsExtend {
return "extend"
}
return "message"
}
// parse expects ident { messageBody
func (m *Message) parse(p *Parser) error {
_, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, m.groupName()+" identifier", m)
}
}
m.Name = lit
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, m.groupName()+" opening {", m)
}
return parseMessageBody(p, m)
}
// parseMessageBody parses elements after {. It consumes the closing }
func parseMessageBody(p *Parser, c elementContainer) error {
var (
pos scanner.Position
tok token
lit string
)
for {
pos, tok, lit = p.next()
switch {
case isComment(lit):
if com := mergeOrReturnComment(c.elements(), lit, pos); com != nil { // not merged?
c.addElement(com)
}
case tENUM == tok:
e := new(Enum)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
case tMESSAGE == tok:
msg := new(Message)
msg.Position = pos
msg.Comment = c.takeLastComment(pos.Line - 1)
if err := msg.parse(p); err != nil {
return err
}
c.addElement(msg)
case tOPTION == tok:
o := new(Option)
o.Position = pos
o.Comment = c.takeLastComment(pos.Line - 1)
if err := o.parse(p); err != nil {
return err
}
c.addElement(o)
case tONEOF == tok:
o := new(Oneof)
o.Position = pos
o.Comment = c.takeLastComment(pos.Line - 1)
if err := o.parse(p); err != nil {
return err
}
c.addElement(o)
case tMAP == tok:
f := newMapField()
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
case tRESERVED == tok:
r := new(Reserved)
r.Position = pos
r.Comment = c.takeLastComment(pos.Line - 1)
if err := r.parse(p); err != nil {
return err
}
c.addElement(r)
// BEGIN proto2
case tOPTIONAL == tok || tREPEATED == tok || tREQUIRED == tok:
// look ahead
prevTok := tok
pos, tok, lit = p.next()
if tGROUP == tok {
g := new(Group)
g.Position = pos
g.Comment = c.takeLastComment(pos.Line - 1)
g.Optional = prevTok == tOPTIONAL
g.Repeated = prevTok == tREPEATED
g.Required = prevTok == tREQUIRED
if err := g.parse(p); err != nil {
return err
}
c.addElement(g)
} else {
// not a group, will be tFIELD
p.nextPut(pos, tok, lit)
f := newNormalField()
f.Type = lit
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
f.Optional = prevTok == tOPTIONAL
f.Repeated = prevTok == tREPEATED
f.Required = prevTok == tREQUIRED
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
}
case tGROUP == tok:
g := new(Group)
g.Position = pos
g.Comment = c.takeLastComment(pos.Line - 1)
if err := g.parse(p); err != nil {
return err
}
c.addElement(g)
case tEXTENSIONS == tok:
e := new(Extensions)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
case tEXTEND == tok:
e := new(Message)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
e.IsExtend = true
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
// END proto2 only
case tRIGHTCURLY == tok || tEOF == tok:
goto done
case tSEMICOLON == tok:
maybeScanInlineComment(p, c)
// continue
default:
// tFIELD
p.nextPut(pos, tok, lit)
f := newNormalField()
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "extend|message|group closing }", c)
}
return nil
}
// Accept dispatches the call to the visitor.
func (m *Message) Accept(v Visitor) {
v.VisitMessage(m)
}
// addElement is part of elementContainer
func (m *Message) addElement(v Visitee) {
v.parent(m)
m.Elements = append(m.Elements, v)
}
// elements is part of elementContainer
func (m *Message) elements() []Visitee {
return m.Elements
}
func (m *Message) takeLastComment(expectedOnLine int) (last *Comment) {
last, m.Elements = takeLastCommentIfEndsOnLine(m.Elements, expectedOnLine)
return
}
// Doc is part of Documented
func (m *Message) Doc() *Comment {
return m.Comment
}
func (m *Message) parent(v Visitee) { m.Parent = v }

View File

@@ -0,0 +1,140 @@
// 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 (
"text/scanner"
)
// Oneof is a field alternate.
type Oneof struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
}
// addElement is part of elementContainer
func (o *Oneof) addElement(v Visitee) {
v.parent(o)
o.Elements = append(o.Elements, v)
}
// elements is part of elementContainer
func (o *Oneof) elements() []Visitee {
return o.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (o *Oneof) takeLastComment(expectedOnLine int) (last *Comment) {
last, o.Elements = takeLastCommentIfEndsOnLine(o.Elements, expectedOnLine)
return last
}
// parse expects:
// oneofName "{" { oneofField | emptyStatement } "}"
func (o *Oneof) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "oneof identifier", o)
}
}
o.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "oneof opening {", o)
}
for {
pos, tok, lit = p.nextTypeName()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(o.elements(), lit, pos); com != nil { // not merged?
o.addElement(com)
}
case tIDENT:
f := newOneOfField()
f.Position = pos
f.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1) // TODO call takeLastComment instead?
f.Type = lit
if err := parseFieldAfterType(f.Field, p); err != nil {
return err
}
o.addElement(f)
case tGROUP:
g := new(Group)
g.Position = pos
g.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1)
if err := g.parse(p); err != nil {
return err
}
o.addElement(g)
case tOPTION:
opt := new(Option)
opt.Position = pos
opt.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1)
if err := opt.parse(p); err != nil {
return err
}
o.addElement(opt)
case tSEMICOLON:
maybeScanInlineComment(p, o)
// continue
default:
goto done
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "oneof closing }", o)
}
return nil
}
// Accept dispatches the call to the visitor.
func (o *Oneof) Accept(v Visitor) {
v.VisitOneof(o)
}
// OneOfField is part of Oneof.
type OneOfField struct {
*Field
}
func newOneOfField() *OneOfField { return &OneOfField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (o *OneOfField) Accept(v Visitor) {
v.VisitOneofField(o)
}
// Doc is part of Documented
// Note: although Doc() is defined on Field, it must be implemented here as well.
func (o *OneOfField) Doc() *Comment {
return o.Comment
}
func (o *Oneof) parent(v Visitee) { o.Parent = v }

View File

@@ -0,0 +1,365 @@
// 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"
"fmt"
"sort"
"text/scanner"
)
// Option is a protoc compiler option
type Option struct {
Position scanner.Position
Comment *Comment
Name string
Constant Literal
IsEmbedded bool
// AggregatedConstants is DEPRECATED. These Literals are populated into Constant.OrderedMap
AggregatedConstants []*NamedLiteral
InlineComment *Comment
Parent Visitee
}
// parse reads an Option body
// ( ident | "(" fullIdent ")" ) { "." ident } "=" constant ";"
func (o *Option) parse(p *Parser) error {
pos, tok, lit := p.nextIdentifier()
if tLEFTPAREN == tok {
pos, tok, lit = p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "option full identifier", o)
}
}
pos, tok, _ = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "option full identifier closing )", o)
}
o.Name = fmt.Sprintf("(%s)", lit)
} else {
// non full ident
if tIDENT != tok {
if !isKeyword(tok) {
return p.unexpected(lit, "option identifier", o)
}
}
o.Name = lit
}
pos, tok, lit = p.next()
if tDOT == tok {
// extend identifier
pos, tok, lit = p.nextIdent(true) // keyword allowed as start
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "option postfix identifier", o)
}
}
o.Name = fmt.Sprintf("%s.%s", o.Name, lit)
pos, tok, lit = p.next()
}
if tEQUALS != tok {
return p.unexpected(lit, "option value assignment =", o)
}
r := p.peekNonWhitespace()
var err error
// values of an option can have illegal escape sequences
// for the standard Go scanner used by this package.
p.ignoreIllegalEscapesWhile(func() {
if '{' == r {
// aggregate
p.next() // consume {
err = o.parseAggregate(p)
} else {
// non aggregate
l := new(Literal)
l.Position = pos
if e := l.parse(p); e != nil {
err = e
}
o.Constant = *l
}
})
return err
}
// inlineComment is part of commentInliner.
func (o *Option) inlineComment(c *Comment) {
o.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (o *Option) Accept(v Visitor) {
v.VisitOption(o)
}
// Doc is part of Documented
func (o *Option) Doc() *Comment {
return o.Comment
}
// Literal represents intLit,floatLit,strLit or boolLit or a nested structure thereof.
type Literal struct {
Position scanner.Position
Source string
IsString bool
// literal value can be an array literal value (even nested)
Array []*Literal
// literal value can be a map of literals (even nested)
// DEPRECATED: use OrderedMap instead
Map map[string]*Literal
// literal value can be a map of literals (even nested)
// this is done as pairs of name keys and literal values so the original ordering is preserved
OrderedMap LiteralMap
}
// LiteralMap is like a map of *Literal but preserved the ordering.
// Can be iterated yielding *NamedLiteral values.
type LiteralMap []*NamedLiteral
// Get returns a Literal from the map.
func (m LiteralMap) Get(key string) (*Literal, bool) {
for _, each := range m {
if each.Name == key {
// exit on the first match
return each.Literal, true
}
}
return new(Literal), false
}
// SourceRepresentation returns the source (if quoted then use double quote).
func (l Literal) SourceRepresentation() string {
var buf bytes.Buffer
if l.IsString {
buf.WriteRune('"')
}
buf.WriteString(l.Source)
if l.IsString {
buf.WriteRune('"')
}
return buf.String()
}
// parse expects to read a literal constant after =.
func (l *Literal) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok == tLEFTSQUARE {
// collect array elements
array := []*Literal{}
for {
e := new(Literal)
if err := e.parse(p); err != nil {
return err
}
array = append(array, e)
_, tok, lit = p.next()
if tok == tCOMMA {
continue
}
if tok == tRIGHTSQUARE {
break
}
return p.unexpected(lit, ", or ]", l)
}
l.Array = array
l.IsString = false
l.Position = pos
return nil
}
if tLEFTCURLY == tok {
l.Position, l.Source, l.IsString = pos, "", false
constants, err := parseAggregateConstants(p, l)
if err != nil {
return nil
}
l.OrderedMap = LiteralMap(constants)
return nil
}
if "-" == lit {
// negative number
if err := l.parse(p); err != nil {
return err
}
// modify source and position
l.Position, l.Source = pos, "-"+l.Source
return nil
}
source := lit
iss := isString(lit)
if iss {
source = unQuote(source)
}
l.Position, l.Source, l.IsString = pos, source, iss
// peek for multiline strings
for {
pos, tok, lit = p.next()
if isString(lit) {
l.Source += unQuote(lit)
} else {
p.nextPut(pos, tok, lit)
break
}
}
return nil
}
// NamedLiteral associates a name with a Literal
type NamedLiteral struct {
*Literal
Name string
// PrintsColon is true when the Name must be printed with a colon suffix
PrintsColon bool
}
// parseAggregate reads options written using aggregate syntax.
// tLEFTCURLY { has been consumed
func (o *Option) parseAggregate(p *Parser) error {
constants, err := parseAggregateConstants(p, o)
literalMap := map[string]*Literal{}
for _, each := range constants {
literalMap[each.Name] = each.Literal
}
o.Constant = Literal{Map: literalMap, OrderedMap: constants, Position: o.Position}
// reconstruct the old, deprecated field
o.AggregatedConstants = collectAggregatedConstants(literalMap)
return err
}
// flatten the maps of each literal, recursively
// this func exists for deprecated Option.AggregatedConstants.
func collectAggregatedConstants(m map[string]*Literal) (list []*NamedLiteral) {
for k, v := range m {
if v.Map != nil {
sublist := collectAggregatedConstants(v.Map)
for _, each := range sublist {
list = append(list, &NamedLiteral{
Name: k + "." + each.Name,
PrintsColon: true,
Literal: each.Literal,
})
}
} else {
list = append(list, &NamedLiteral{
Name: k,
PrintsColon: true,
Literal: v,
})
}
}
// sort list by position of literal
sort.Sort(byPosition(list))
return
}
type byPosition []*NamedLiteral
func (b byPosition) Less(i, j int) bool {
return b[i].Literal.Position.Line < b[j].Literal.Position.Line
}
func (b byPosition) Len() int { return len(b) }
func (b byPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func parseAggregateConstants(p *Parser, container interface{}) (list []*NamedLiteral, err error) {
for {
pos, tok, lit := p.nextIdentifier()
if tRIGHTSQUARE == tok {
p.nextPut(pos, tok, lit)
// caller has checked for open square ; will consume rightsquare, rightcurly and semicolon
return
}
if tRIGHTCURLY == tok {
return
}
if tSEMICOLON == tok {
// just consume it
continue
//return
}
if tCOMMENT == tok {
// assign to last parsed literal
// TODO: see TestUseOfSemicolonsInAggregatedConstants
continue
}
if tCOMMA == tok {
if len(list) == 0 {
err = p.unexpected(lit, "non-empty option aggregate key", container)
return
}
continue
}
if tIDENT != tok && !isKeyword(tok) {
err = p.unexpected(lit, "option aggregate key", container)
return
}
// workaround issue #59 TODO
if isString(lit) && len(list) > 0 {
// concatenate with previous constant
list[len(list)-1].Source += unQuote(lit)
continue
}
key := lit
printsColon := false
// expect colon, aggregate or plain literal
pos, tok, lit = p.next()
if tCOLON == tok {
// consume it
printsColon = true
pos, tok, lit = p.next()
}
// see if nested aggregate is started
if tLEFTCURLY == tok {
nested, fault := parseAggregateConstants(p, container)
if fault != nil {
err = fault
return
}
// create the map
m := map[string]*Literal{}
for _, each := range nested {
m[each.Name] = each.Literal
}
list = append(list, &NamedLiteral{
Name: key,
PrintsColon: printsColon,
Literal: &Literal{Map: m, OrderedMap: LiteralMap(nested)}})
continue
}
// no aggregate, put back token
p.nextPut(pos, tok, lit)
// now we see plain literal
l := new(Literal)
l.Position = pos
if err = l.parse(p); err != nil {
return
}
list = append(list, &NamedLiteral{Name: key, Literal: l, PrintsColon: printsColon})
}
}
func (o *Option) parent(v Visitee) { o.Parent = v }

View File

@@ -0,0 +1,63 @@
// 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 "text/scanner"
// Package specifies the namespace for all proto elements.
type Package struct {
Position scanner.Position
Comment *Comment
Name string
InlineComment *Comment
Parent Visitee
}
// Doc is part of Documented
func (p *Package) Doc() *Comment {
return p.Comment
}
func (p *Package) parse(pr *Parser) error {
_, tok, lit := pr.nextIdent(true)
if tIDENT != tok {
if !isKeyword(tok) {
return pr.unexpected(lit, "package identifier", p)
}
}
p.Name = lit
return nil
}
// Accept dispatches the call to the visitor.
func (p *Package) Accept(v Visitor) {
v.VisitPackage(p)
}
// inlineComment is part of commentInliner.
func (p *Package) inlineComment(c *Comment) {
p.InlineComment = c
}
func (p *Package) parent(v Visitee) { p.Parent = v }

View File

@@ -0,0 +1,211 @@
// 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
}

View File

@@ -0,0 +1,170 @@
// 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
// Proto represents a .proto definition
type Proto struct {
Filename string
Elements []Visitee
Imports []Import
Enums []Enum
Package []Package
Options []Option
Messages []Message
Services []Service
Extends []Extensions
}
// Accept dispatches the call to the visitor.
func (proto *Proto) Accept(v Visitor) {
// As Proto is not (yet) a Visitee, we enumerate its elements instead
//v.VisitProto(proto)
for _, each := range proto.Elements {
each.Accept(v)
}
}
// addElement is part of elementContainer
func (proto *Proto) addElement(v Visitee) {
v.parent(proto)
proto.Elements = append(proto.Elements, v)
}
// elements is part of elementContainer
func (proto *Proto) elements() []Visitee {
return proto.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (proto *Proto) takeLastComment(expectedOnLine int) (last *Comment) {
last, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, expectedOnLine)
return
}
// parse parsers a complete .proto definition source.
func (proto *Proto) parse(p *Parser) (*Proto, error) {
pro := new(Proto)
for {
pos, tok, lit := p.next()
switch {
case isComment(lit):
if com := mergeOrReturnComment(proto.Elements, lit, pos); com != nil { // not merged?
proto.Elements = append(proto.Elements, com)
}
case tOPTION == tok:
o := new(Option)
o.Position = pos
o.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := o.parse(p); err != nil {
return pro, err
}
pro.Options = append(pro.Options, *o)
proto.addElement(o)
case tSYNTAX == tok:
s := new(Syntax)
s.Position = pos
s.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := s.parse(p); err != nil {
return pro, err
}
proto.addElement(s)
case tIMPORT == tok:
im := new(Import)
im.Position = pos
im.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := im.parse(p); err != nil {
return pro, err
}
pro.Imports = append(pro.Imports, *im)
proto.addElement(im)
case tENUM == tok:
enum := new(Enum)
enum.Position = pos
enum.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := enum.parse(p); err != nil {
return pro, err
}
pro.Enums = append(pro.Enums, *enum)
proto.addElement(enum)
case tSERVICE == tok:
service := new(Service)
service.Position = pos
service.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
err := service.parse(p)
if err != nil {
return pro, err
}
pro.Services = append(pro.Services, *service)
proto.addElement(service)
case tPACKAGE == tok:
pkg := new(Package)
pkg.Position = pos
pkg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := pkg.parse(p); err != nil {
return pro, err
}
pro.Package = append(pro.Package, *pkg)
proto.addElement(pkg)
case tMESSAGE == tok:
msg := new(Message)
msg.Position = pos
msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := msg.parse(p); err != nil {
return pro, err
}
pro.Messages = append(pro.Messages, *msg)
proto.addElement(msg)
// BEGIN proto2
case tEXTEND == tok:
msg := new(Message)
msg.Position = pos
msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
msg.IsExtend = true
if err := msg.parse(p); err != nil {
return pro, err
}
proto.addElement(msg)
// END proto2
case tSEMICOLON == tok:
maybeScanInlineComment(p, proto)
// continue
case tEOF == tok:
goto done
default:
return pro, p.unexpected(lit, ".proto element {comment|option|import|syntax|enum|service|package|message}", p)
}
}
done:
return pro, nil
}
func (proto *Proto) parent(v Visitee) {}
// elementContainer unifies types that have elements.
type elementContainer interface {
addElement(v Visitee)
elements() []Visitee
takeLastComment(expectedOnLine int) *Comment
}

View File

@@ -0,0 +1,90 @@
// 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 (
"fmt"
"strconv"
)
// Range is to specify number intervals (with special end value "max")
type Range struct {
From, To int
Max bool
}
// SourceRepresentation return a single number if from = to. Returns <from> to <to> otherwise unless Max then return <from> to max.
func (r Range) SourceRepresentation() string {
if r.Max {
return fmt.Sprintf("%d to max", r.From)
}
if r.From == r.To {
return strconv.Itoa(r.From)
}
return fmt.Sprintf("%d to %d", r.From, r.To)
}
// parseRanges is used to parse ranges for extensions and reserved
func parseRanges(p *Parser, n Visitee) (list []Range, err error) {
seenTo := false
for {
pos, tok, lit := p.next()
if isString(lit) {
return list, p.unexpected(lit, "integer, <to> <max>", n)
}
switch lit {
case ",":
case "to":
seenTo = true
case ";":
p.nextPut(pos, tok, lit) // allow for inline comment parsing
goto done
case "max":
if !seenTo {
return list, p.unexpected(lit, "to", n)
}
from := list[len(list)-1]
list = append(list[0:len(list)-1], Range{From: from.From, Max: true})
default:
// must be number
i, err := strconv.Atoi(lit)
if err != nil {
return list, p.unexpected(lit, "range integer", n)
}
if seenTo {
// replace last two ranges with one
if len(list) < 1 {
p.unexpected(lit, "integer", n)
}
from := list[len(list)-1]
list = append(list[0:len(list)-1], Range{From: from.From, To: i})
seenTo = false
} else {
list = append(list, Range{From: i, To: i})
}
}
}
done:
return
}

View File

@@ -0,0 +1,78 @@
// 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 "text/scanner"
// Reserved statements declare a range of field numbers or field names that cannot be used in a message.
type Reserved struct {
Position scanner.Position
Comment *Comment
Ranges []Range
FieldNames []string
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (r *Reserved) inlineComment(c *Comment) {
r.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (r *Reserved) Accept(v Visitor) {
v.VisitReserved(r)
}
func (r *Reserved) parse(p *Parser) error {
for {
pos, tok, lit := p.next()
if len(lit) == 0 {
return p.unexpected(lit, "reserved string or integer", r)
}
// first char that determined tok
ch := []rune(lit)[0]
if isDigit(ch) {
// use unread here because it could be start of ranges
p.nextPut(pos, tok, lit)
list, err := parseRanges(p, r)
if err != nil {
return err
}
r.Ranges = list
continue
}
if isString(lit) {
r.FieldNames = append(r.FieldNames, unQuote(lit))
continue
}
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit)
break
}
}
return nil
}
func (r *Reserved) parent(v Visitee) { r.Parent = v }

View File

@@ -0,0 +1,257 @@
// 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 (
"text/scanner"
)
// Service defines a set of RPC calls.
type Service struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
RPCElements []RPC
}
// Accept dispatches the call to the visitor.
func (s *Service) Accept(v Visitor) {
v.VisitService(s)
}
// Doc is part of Documented
func (s *Service) Doc() *Comment {
return s.Comment
}
// addElement is part of elementContainer
func (s *Service) addElement(v Visitee) {
v.parent(s)
s.Elements = append(s.Elements, v)
}
// elements is part of elementContainer
func (s *Service) elements() []Visitee {
return s.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last elements of the list if it is a Comment.
func (s *Service) takeLastComment(expectedOnLine int) (last *Comment) {
last, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, expectedOnLine)
return
}
// parse continues after reading "service"
func (s *Service) parse(p *Parser) error {
pos, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "service identifier", s)
}
}
s.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "service opening {", s)
}
for {
pos, tok, lit = p.next()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(s.Elements, lit, pos); com != nil { // not merged?
s.addElement(com)
}
case tOPTION:
opt := new(Option)
opt.Position = pos
opt.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.elements(), pos.Line-1)
if err := opt.parse(p); err != nil {
return err
}
s.addElement(opt)
case tRPC:
rpc := new(RPC)
rpc.Position = pos
rpc.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, pos.Line-1)
err := rpc.parse(p)
if err != nil {
return err
}
s.RPCElements = append(s.RPCElements, *rpc)
s.addElement(rpc)
maybeScanInlineComment(p, s)
case tSEMICOLON:
maybeScanInlineComment(p, s)
case tRIGHTCURLY:
goto done
default:
return p.unexpected(lit, "service comment|rpc", s)
}
}
done:
return nil
}
func (s *Service) parent(v Visitee) { s.Parent = v }
// RPC represents an rpc entry in a message.
type RPC struct {
Position scanner.Position
Comment *Comment
Name string
RequestType string
StreamsRequest bool
ReturnsType string
StreamsReturns bool
Elements []Visitee
InlineComment *Comment
Parent Visitee
// Options field is DEPRECATED, use Elements instead.
Options []*Option
}
// Accept dispatches the call to the visitor.
func (r *RPC) Accept(v Visitor) {
v.VisitRPC(r)
}
// Doc is part of Documented
func (r *RPC) Doc() *Comment {
return r.Comment
}
// inlineComment is part of commentInliner.
func (r *RPC) inlineComment(c *Comment) {
r.InlineComment = c
}
// parse continues after reading "rpc"
func (r *RPC) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
return p.unexpected(lit, "rpc method", r)
}
r.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTPAREN {
return p.unexpected(lit, "rpc type opening (", r)
}
pos, tok, lit = p.nextIdentifier()
if tSTREAM == tok {
r.StreamsRequest = true
pos, tok, lit = p.nextIdentifier()
}
if tok != tIDENT {
return p.unexpected(lit, "rpc stream | request type", r)
}
r.RequestType = lit
pos, tok, lit = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "rpc type closing )", r)
}
pos, tok, lit = p.next()
if tok != tRETURNS {
return p.unexpected(lit, "rpc returns", r)
}
pos, tok, lit = p.next()
if tok != tLEFTPAREN {
return p.unexpected(lit, "rpc type opening (", r)
}
pos, tok, lit = p.nextIdentifier()
if tSTREAM == tok {
r.StreamsReturns = true
pos, tok, lit = p.nextIdentifier()
}
if tok == tDOT {
pos, tok, lit = p.nextIdentifier()
}
if tok != tIDENT {
return p.unexpected(lit, "rpc stream | returns type", r)
}
r.ReturnsType = lit
pos, tok, lit = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "rpc type closing )", r)
}
pos, tok, lit = p.next()
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit) // allow for inline comment parsing
return nil
}
if tLEFTCURLY == tok {
// parse options
for {
pos, tok, lit = p.next()
if tRIGHTCURLY == tok {
break
}
if isComment(lit) {
if com := mergeOrReturnComment(r.elements(), lit, pos); com != nil { // not merged?
r.addElement(com)
continue
}
}
if tSEMICOLON == tok {
maybeScanInlineComment(p, r)
continue
}
if tOPTION == tok {
o := new(Option)
o.Position = pos
if err := o.parse(p); err != nil {
return err
}
r.addElement(o)
}
}
}
return nil
}
// addElement is part of elementContainer
func (r *RPC) addElement(v Visitee) {
v.parent(r)
r.Elements = append(r.Elements, v)
// handle deprecated field
if option, ok := v.(*Option); ok {
r.Options = append(r.Options, option)
}
}
// elements is part of elementContainer
func (r *RPC) elements() []Visitee {
return r.Elements
}
func (r *RPC) takeLastComment(expectedOnLine int) (last *Comment) {
last, r.Elements = takeLastCommentIfEndsOnLine(r.Elements, expectedOnLine)
return
}
func (r *RPC) parent(v Visitee) { r.Parent = v }

View File

@@ -0,0 +1,66 @@
// 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 (
"text/scanner"
)
// Syntax should have value "proto"
type Syntax struct {
Position scanner.Position
Comment *Comment
Value string
InlineComment *Comment
Parent Visitee
}
func (s *Syntax) parse(p *Parser) error {
if _, tok, lit := p.next(); tok != tEQUALS {
return p.unexpected(lit, "syntax =", s)
}
_, _, lit := p.next()
if !isString(lit) {
return p.unexpected(lit, "syntax string constant", s)
}
s.Value = unQuote(lit)
return nil
}
// Accept dispatches the call to the visitor.
func (s *Syntax) Accept(v Visitor) {
v.VisitSyntax(s)
}
// Doc is part of Documented
func (s *Syntax) Doc() *Comment {
return s.Comment
}
// inlineComment is part of commentInliner.
func (s *Syntax) inlineComment(c *Comment) {
s.InlineComment = c
}
func (s *Syntax) parent(v Visitee) { s.Parent = v }

View File

@@ -0,0 +1,212 @@
// 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 (
"strings"
)
// token represents a lexical token.
type token int
const (
// Special tokens
tILLEGAL token = iota
tEOF
tWS
// Literals
tIDENT
// Misc characters
tSEMICOLON // ;
tCOLON // :
tEQUALS // =
tQUOTE // "
tSINGLEQUOTE // '
tLEFTPAREN // (
tRIGHTPAREN // )
tLEFTCURLY // {
tRIGHTCURLY // }
tLEFTSQUARE // [
tRIGHTSQUARE // ]
tCOMMENT // /
tLESS // <
tGREATER // >
tCOMMA // ,
tDOT // .
// Keywords
keywordsStart
tSYNTAX
tSERVICE
tRPC
tRETURNS
tMESSAGE
tIMPORT
tPACKAGE
tOPTION
tREPEATED
tWEAK
tPUBLIC
// special fields
tONEOF
tMAP
tRESERVED
tENUM
tSTREAM
// BEGIN proto2
tOPTIONAL
tGROUP
tEXTENSIONS
tEXTEND
tREQUIRED
// END proto2
keywordsEnd
)
// typeTokens exists for future validation
// const typeTokens = "double float int32 int64 uint32 uint64 sint32 sint64 fixed32 sfixed32 sfixed64 bool string bytes"
// isKeyword returns if tok is in the keywords range
func isKeyword(tok token) bool {
return keywordsStart < tok && tok < keywordsEnd
}
// isWhitespace checks for space,tab and newline
func isWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
}
// isDigit returns true if the rune is a digit.
func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') }
// isString checks if the literal is quoted (single or double).
func isString(lit string) bool {
return (strings.HasPrefix(lit, "\"") &&
strings.HasSuffix(lit, "\"")) ||
(strings.HasPrefix(lit, "'") &&
strings.HasSuffix(lit, "'"))
}
func isComment(lit string) bool {
return strings.HasPrefix(lit, "//") || strings.HasPrefix(lit, "/*")
}
func unQuote(lit string) string {
return strings.Trim(lit, "\"'")
}
func asToken(literal string) token {
switch literal {
// delimiters
case ";":
return tSEMICOLON
case ":":
return tCOLON
case "=":
return tEQUALS
case "\"":
return tQUOTE
case "'":
return tSINGLEQUOTE
case "(":
return tLEFTPAREN
case ")":
return tRIGHTPAREN
case "{":
return tLEFTCURLY
case "}":
return tRIGHTCURLY
case "[":
return tLEFTSQUARE
case "]":
return tRIGHTSQUARE
case "<":
return tLESS
case ">":
return tGREATER
case ",":
return tCOMMA
case ".":
return tDOT
// words
case "syntax":
return tSYNTAX
case "service":
return tSERVICE
case "rpc":
return tRPC
case "returns":
return tRETURNS
case "option":
return tOPTION
case "message":
return tMESSAGE
case "import":
return tIMPORT
case "package":
return tPACKAGE
case "oneof":
return tONEOF
// special fields
case "map":
return tMAP
case "reserved":
return tRESERVED
case "enum":
return tENUM
case "repeated":
return tREPEATED
case "weak":
return tWEAK
case "public":
return tPUBLIC
case "stream":
return tSTREAM
// proto2
case "optional":
return tOPTIONAL
case "group":
return tGROUP
case "extensions":
return tEXTENSIONS
case "extend":
return tEXTEND
case "required":
return tREQUIRED
case "ws":
return tWS
case "ill":
return tILLEGAL
default:
// special cases
if isComment(literal) {
return tCOMMENT
}
return tIDENT
}
}

View File

@@ -0,0 +1,58 @@
// 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
// Visitor is for dispatching Proto elements.
type Visitor interface {
//VisitProto(p *Proto)
VisitMessage(m *Message)
VisitService(v *Service)
VisitSyntax(s *Syntax)
VisitPackage(p *Package)
VisitOption(o *Option)
VisitImport(i *Import)
VisitNormalField(i *NormalField)
VisitEnumField(i *EnumField)
VisitEnum(e *Enum)
VisitComment(e *Comment)
VisitOneof(o *Oneof)
VisitOneofField(o *OneOfField)
VisitReserved(r *Reserved)
VisitRPC(r *RPC)
VisitMapField(f *MapField)
// proto2
VisitGroup(g *Group)
VisitExtensions(e *Extensions)
}
// Visitee is implemented by all Proto elements.
type Visitee interface {
Accept(v Visitor)
parent(e Visitee)
}
// Documented is for types that may have an associated comment (not inlined).
type Documented interface {
Doc() *Comment
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2018 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
// Handler is a type of function that accepts a Visitee.
type Handler func(v Visitee)
// Walk recursively pays a visit to all Visitees of a Proto and calls each handler with it.
func Walk(proto *Proto, handlers ...Handler) {
walk(proto, handlers...)
}
func walk(container elementContainer, handlers ...Handler) {
for _, eachElement := range container.elements() {
for _, eachFilter := range handlers {
eachFilter(eachElement)
}
if next, ok := eachElement.(elementContainer); ok {
walk(next, handlers...)
}
}
}
// WithMessage returns a Handler that will call the apply function when the Visitee is a Message.
func WithMessage(apply func(*Message)) Handler {
return func(v Visitee) {
if s, ok := v.(*Message); ok {
apply(s)
}
}
}
// WithOption returns a Handler that will call the apply function when the Visitee is a Option.
func WithOption(apply func(*Option)) Handler {
return func(v Visitee) {
if s, ok := v.(*Option); ok {
apply(s)
}
}
}
// WithEnum returns a Handler that will call the apply function when the Visitee is a Enum.
func WithEnum(apply func(*Enum)) Handler {
return func(v Visitee) {
if s, ok := v.(*Enum); ok {
apply(s)
}
}
}
// WithOneof returns a Handler that will call the apply function when the Visitee is a Oneof.
func WithOneof(apply func(*Oneof)) Handler {
return func(v Visitee) {
if s, ok := v.(*Oneof); ok {
apply(s)
}
}
}
// WithService returns a Handler that will call the apply function when the Visitee is a Service.
func WithService(apply func(*Service)) Handler {
return func(v Visitee) {
if s, ok := v.(*Service); ok {
apply(s)
}
}
}
// WithRPC returns a Handler that will call the apply function when the Visitee is a RPC.
func WithRPC(apply func(*RPC)) Handler {
return func(v Visitee) {
if s, ok := v.(*RPC); ok {
apply(s)
}
}
}