Import of https://github.com/ciena/voltctl at commit 40d61fbf3f910ed4017cf67c9c79e8e1f82a33a5

Change-Id: I8464c59e60d76cb8612891db3303878975b5416c
diff --git a/vendor/github.com/jhump/protoreflect/desc/protoparse/proto.y b/vendor/github.com/jhump/protoreflect/desc/protoparse/proto.y
new file mode 100644
index 0000000..faf49d9
--- /dev/null
+++ b/vendor/github.com/jhump/protoreflect/desc/protoparse/proto.y
@@ -0,0 +1,937 @@
+%{
+package protoparse
+
+//lint:file-ignore SA4006 generated parser has unused values
+
+import (
+	"fmt"
+	"math"
+	"unicode"
+
+	"github.com/jhump/protoreflect/desc/internal"
+)
+
+%}
+
+// fields inside this union end up as the fields in a structure known
+// as ${PREFIX}SymType, of which a reference is passed to the lexer.
+%union{
+	file      *fileNode
+	fileDecls []*fileElement
+	syn       *syntaxNode
+	pkg       *packageNode
+	imprt     *importNode
+	msg       *messageNode
+	msgDecls  []*messageElement
+	fld       *fieldNode
+	mapFld    *mapFieldNode
+	grp       *groupNode
+	oo        *oneOfNode
+	ooDecls   []*oneOfElement
+	ext       *extensionRangeNode
+	resvd     *reservedNode
+	en        *enumNode
+	enDecls   []*enumElement
+	env       *enumValueNode
+	extend    *extendNode
+	extDecls  []*extendElement
+	svc       *serviceNode
+	svcDecls  []*serviceElement
+	mtd       *methodNode
+	rpcType   *rpcTypeNode
+	opts      []*optionNode
+	optNm     []*optionNamePartNode
+	rngs      []*rangeNode
+	names     []*stringLiteralNode
+	sl        []valueNode
+	agg       []*aggregateEntryNode
+	aggName   *aggregateNameNode
+	v         valueNode
+	str       *stringLiteralNode
+	i         *negativeIntLiteralNode
+	ui        *intLiteralNode
+	f         *floatLiteralNode
+	id        *identNode
+	b         *basicNode
+	err       error
+}
+
+// any non-terminal which returns a value needs a type, which is
+// really a field name in the above union struct
+%type <file>      file
+%type <syn>       syntax
+%type <fileDecls> fileDecl fileDecls
+%type <imprt>     import
+%type <pkg>       package
+%type <opts>      option fieldOption fieldOptions rpcOption rpcOptions
+%type <optNm>     optionName optionNameRest optionNameComponent
+%type <v>         constant scalarConstant aggregate
+%type <id>        name ident typeIdent keyType
+%type <aggName>   aggName
+%type <i>         negIntLit
+%type <ui>        intLit
+%type <f>         floatLit
+%type <sl>        constantList
+%type <agg>       aggFields aggField aggFieldEntry
+%type <fld>       field oneofField
+%type <oo>        oneof
+%type <grp>       group
+%type <mapFld>    mapField
+%type <msg>       message
+%type <msgDecls>  messageItem messageBody
+%type <ooDecls>   oneofItem oneofBody
+%type <names>     fieldNames
+%type <resvd>     msgReserved enumReserved reservedNames
+%type <rngs>      tagRange tagRanges enumRange enumRanges
+%type <ext>       extensions
+%type <en>        enum
+%type <enDecls>   enumItem enumBody
+%type <env>       enumField
+%type <extend>    extend
+%type <extDecls>  extendItem extendBody
+%type <str>       stringLit
+%type <svc>       service
+%type <svcDecls>  serviceItem serviceBody
+%type <mtd>       rpc
+%type <rpcType>   rpcType
+
+// same for terminals
+%token <str> _STRING_LIT
+%token <ui>  _INT_LIT
+%token <f>   _FLOAT_LIT
+%token <id>  _NAME _FQNAME _TYPENAME
+%token <id>  _SYNTAX _IMPORT _WEAK _PUBLIC _PACKAGE _OPTION _TRUE _FALSE _INF _NAN _REPEATED _OPTIONAL _REQUIRED
+%token <id>  _DOUBLE _FLOAT _INT32 _INT64 _UINT32 _UINT64 _SINT32 _SINT64 _FIXED32 _FIXED64 _SFIXED32 _SFIXED64
+%token <id>  _BOOL _STRING _BYTES _GROUP _ONEOF _MAP _EXTENSIONS _TO _MAX _RESERVED _ENUM _MESSAGE _EXTEND
+%token <id>  _SERVICE _RPC _STREAM _RETURNS
+%token <err> _ERROR
+// we define all of these, even ones that aren't used, to improve error messages
+// so it shows the unexpected symbol instead of showing "$unk"
+%token <b>   '=' ';' ':' '{' '}' '\\' '/' '?' '.' ',' '>' '<' '+' '-' '(' ')' '[' ']' '*' '&' '^' '%' '$' '#' '@' '!' '~' '`'
+
+%%
+
+file : syntax {
+		$$ = &fileNode{syntax: $1}
+		$$.setRange($1, $1)
+		protolex.(*protoLex).res = $$
+	}
+	| fileDecls  {
+		$$ = &fileNode{decls: $1}
+		if len($1) > 0 {
+			$$.setRange($1[0], $1[len($1)-1])
+		}
+		protolex.(*protoLex).res = $$
+	}
+	| syntax fileDecls {
+		$$ = &fileNode{syntax: $1, decls: $2}
+		var end node
+		if len($2) > 0 {
+			end = $2[len($2)-1]
+		} else {
+			end = $1
+		}
+		$$.setRange($1, end)
+		protolex.(*protoLex).res = $$
+	}
+	| {
+	}
+
+fileDecls : fileDecls fileDecl {
+		$$ = append($1, $2...)
+	}
+	| fileDecl
+
+fileDecl : import {
+		$$ = []*fileElement{{imp: $1}}
+	}
+	| package {
+		$$ = []*fileElement{{pkg: $1}}
+	}
+	| option {
+		$$ = []*fileElement{{option: $1[0]}}
+	}
+	| message {
+		$$ = []*fileElement{{message: $1}}
+	}
+	| enum {
+		$$ = []*fileElement{{enum: $1}}
+	}
+	| extend {
+		$$ = []*fileElement{{extend: $1}}
+	}
+	| service {
+		$$ = []*fileElement{{service: $1}}
+	}
+	| ';' {
+		$$ = []*fileElement{{empty: $1}}
+	}
+
+syntax : _SYNTAX '=' stringLit ';' {
+		if $3.val != "proto2" && $3.val != "proto3" {
+			lexError(protolex, $3.start(), "syntax value must be 'proto2' or 'proto3'")
+		}
+		$$ = &syntaxNode{syntax: $3}
+		$$.setRange($1, $4)
+	}
+
+import : _IMPORT stringLit ';' {
+		$$ = &importNode{ name: $2 }
+		$$.setRange($1, $3)
+	}
+	| _IMPORT _WEAK stringLit ';' {
+		$$ = &importNode{ name: $3, weak: true }
+		$$.setRange($1, $4)
+	}
+	| _IMPORT _PUBLIC stringLit ';' {
+		$$ = &importNode{ name: $3, public: true }
+		$$.setRange($1, $4)
+	}
+
+package : _PACKAGE ident ';' {
+		$$ = &packageNode{name: $2}
+		$$.setRange($1, $3)
+	}
+
+ident : name
+	| _FQNAME
+
+option : _OPTION optionName '=' constant ';' {
+		n := &optionNameNode{parts: $2}
+		n.setRange($2[0], $2[len($2)-1])
+		o := &optionNode{name: n, val: $4}
+		o.setRange($1, $5)
+		$$ = []*optionNode{o}
+	}
+
+optionName : ident {
+		$$ = toNameParts($1, 0)
+	}
+	| '(' typeIdent ')' {
+		p := &optionNamePartNode{text: $2, isExtension: true}
+		p.setRange($1, $3)
+		$$ = []*optionNamePartNode{p}
+	}
+	| '(' typeIdent ')' optionNameRest {
+		p := &optionNamePartNode{text: $2, isExtension: true}
+		p.setRange($1, $3)
+		ps := make([]*optionNamePartNode, 1, len($4)+1)
+		ps[0] = p
+		$$ = append(ps, $4...)
+	}
+
+optionNameRest : optionNameComponent
+	| optionNameComponent optionNameRest {
+		$$ = append($1, $2...)
+	}
+
+optionNameComponent : _TYPENAME {
+		$$ = toNameParts($1, 1 /* exclude leading dot */)
+	}
+	| '.' '(' typeIdent ')' {
+		p := &optionNamePartNode{text: $3, isExtension: true}
+		p.setRange($2, $4)
+		$$ = []*optionNamePartNode{p}
+	}
+
+constant : scalarConstant
+	| aggregate
+
+scalarConstant : stringLit {
+		$$ = $1
+	}
+	| intLit {
+		$$ = $1
+	}
+	| negIntLit {
+		$$ = $1
+	}
+	| floatLit {
+		$$ = $1
+	}
+	| name {
+		if $1.val == "true" {
+			$$ = &boolLiteralNode{basicNode: $1.basicNode, val: true}
+		} else if $1.val == "false" {
+			$$ = &boolLiteralNode{basicNode: $1.basicNode, val: false}
+		} else if $1.val == "inf" {
+			f := &floatLiteralNode{val: math.Inf(1)}
+			f.setRange($1, $1)
+			$$ = f
+		} else if $1.val == "nan" {
+			f := &floatLiteralNode{val: math.NaN()}
+			f.setRange($1, $1)
+			$$ = f
+		} else {
+			$$ = $1
+		}
+	}
+
+intLit : _INT_LIT
+	| '+' _INT_LIT {
+		$$ = $2
+	}
+
+negIntLit : '-' _INT_LIT {
+		if $2.val > math.MaxInt64 + 1 {
+			lexError(protolex, $2.start(), fmt.Sprintf("numeric constant %d would underflow (allowed range is %d to %d)", $2.val, int64(math.MinInt64), int64(math.MaxInt64)))
+		}
+		$$ = &negativeIntLiteralNode{val: -int64($2.val)}
+		$$.setRange($1, $2)
+	}
+
+floatLit : _FLOAT_LIT
+	| '-' _FLOAT_LIT {
+		$$ = &floatLiteralNode{val: -$2.val}
+		$$.setRange($1, $2)
+	}
+	| '+' _FLOAT_LIT {
+		$$ = &floatLiteralNode{val: $2.val}
+		$$.setRange($1, $2)
+	}
+	| '+' _INF {
+		$$ = &floatLiteralNode{val: math.Inf(1)}
+		$$.setRange($1, $2)
+	}
+	| '-' _INF {
+		$$ = &floatLiteralNode{val: math.Inf(-1)}
+		$$.setRange($1, $2)
+	}
+
+stringLit : _STRING_LIT
+    | stringLit _STRING_LIT {
+        $$ = &stringLiteralNode{val: $1.val + $2.val}
+        $$.setRange($1, $2)
+    }
+
+aggregate : '{' aggFields '}' {
+		a := &aggregateLiteralNode{elements: $2}
+		a.setRange($1, $3)
+		$$ = a
+	}
+
+aggFields : aggField
+	| aggFields aggField {
+		$$ = append($1, $2...)
+	}
+	| {
+		$$ = nil
+	}
+
+aggField : aggFieldEntry
+	| aggFieldEntry ',' {
+		$$ = $1
+	}
+	| aggFieldEntry ';' {
+		$$ = $1
+	}
+
+aggFieldEntry : aggName ':' scalarConstant {
+		a := &aggregateEntryNode{name: $1, val: $3}
+		a.setRange($1, $3)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName ':' '[' ']' {
+		s := &sliceLiteralNode{}
+		s.setRange($3, $4)
+		a := &aggregateEntryNode{name: $1, val: s}
+		a.setRange($1, $4)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName ':' '[' constantList ']' {
+		s := &sliceLiteralNode{elements: $4}
+		s.setRange($3, $5)
+		a := &aggregateEntryNode{name: $1, val: s}
+		a.setRange($1, $5)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName ':' aggregate {
+		a := &aggregateEntryNode{name: $1, val: $3}
+		a.setRange($1, $3)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName aggregate {
+		a := &aggregateEntryNode{name: $1, val: $2}
+		a.setRange($1, $2)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName ':' '<' aggFields '>' {
+		s := &aggregateLiteralNode{elements: $4}
+		s.setRange($3, $5)
+		a := &aggregateEntryNode{name: $1, val: s}
+		a.setRange($1, $5)
+		$$ = []*aggregateEntryNode{a}
+	}
+	| aggName '<' aggFields '>' {
+		s := &aggregateLiteralNode{elements: $3}
+		s.setRange($2, $4)
+		a := &aggregateEntryNode{name: $1, val: s}
+		a.setRange($1, $4)
+		$$ = []*aggregateEntryNode{a}
+	}
+
+aggName : name {
+		$$ = &aggregateNameNode{name: $1}
+		$$.setRange($1, $1)
+	}
+	| '[' ident ']' {
+		$$ = &aggregateNameNode{name: $2, isExtension: true}
+		$$.setRange($1, $3)
+	}
+
+constantList : constant {
+		$$ = []valueNode{$1}
+	}
+	| constantList ',' constant {
+		$$ = append($1, $3)
+	}
+	| constantList ';' constant {
+		$$ = append($1, $3)
+	}
+	| '<' aggFields '>' {
+		s := &aggregateLiteralNode{elements: $2}
+		s.setRange($1, $3)
+		$$ = []valueNode{s}
+	}
+	| constantList ','  '<' aggFields '>' {
+		s := &aggregateLiteralNode{elements: $4}
+		s.setRange($3, $5)
+		$$ = append($1, s)
+	}
+	| constantList ';'  '<' aggFields '>' {
+		s := &aggregateLiteralNode{elements: $4}
+		s.setRange($3, $5)
+		$$ = append($1, s)
+	}
+
+typeIdent : ident
+	| _TYPENAME
+
+field : _REQUIRED typeIdent name '=' _INT_LIT ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode, required: true}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5}
+		$$.setRange($1, $6)
+	}
+	| _OPTIONAL typeIdent name '=' _INT_LIT ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5}
+		$$.setRange($1, $6)
+	}
+	| _REPEATED typeIdent name '=' _INT_LIT ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode, repeated: true}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5}
+		$$.setRange($1, $6)
+	}
+	| typeIdent name '=' _INT_LIT ';' {
+		checkTag(protolex, $4.start(), $4.val)
+		$$ = &fieldNode{fldType: $1, name: $2, tag: $4}
+		$$.setRange($1, $5)
+	}
+	| _REQUIRED typeIdent name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode, required: true}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5, options: $7}
+		$$.setRange($1, $9)
+	}
+	| _OPTIONAL typeIdent name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5, options: $7}
+		$$.setRange($1, $9)
+	}
+	| _REPEATED typeIdent name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $5.start(), $5.val)
+		lbl := &labelNode{basicNode: $1.basicNode, repeated: true}
+		$$ = &fieldNode{label: lbl, fldType: $2, name: $3, tag: $5, options: $7}
+		$$.setRange($1, $9)
+	}
+	| typeIdent name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $4.start(), $4.val)
+		$$ = &fieldNode{fldType: $1, name: $2, tag: $4, options: $6}
+		$$.setRange($1, $8)
+	}
+
+fieldOptions : fieldOptions ',' fieldOption {
+		$$ = append($1, $3...)
+	}
+	| fieldOption
+
+fieldOption: optionName '=' constant {
+		n := &optionNameNode{parts: $1}
+		n.setRange($1[0], $1[len($1)-1])
+		o := &optionNode{name: n, val: $3}
+		o.setRange($1[0], $3)
+		$$ = []*optionNode{o}
+	}
+
+group : _REQUIRED _GROUP name '=' _INT_LIT '{' messageBody '}' {
+		checkTag(protolex, $5.start(), $5.val)
+		if !unicode.IsUpper(rune($3.val[0])) {
+			lexError(protolex, $3.start(), fmt.Sprintf("group %s should have a name that starts with a capital letter", $3.val))
+		}
+		lbl := &labelNode{basicNode: $1.basicNode, required: true}
+		$$ = &groupNode{groupKeyword: $2, label: lbl, name: $3, tag: $5, decls: $7}
+		$$.setRange($1, $8)
+	}
+	| _OPTIONAL _GROUP name '=' _INT_LIT '{' messageBody '}' {
+		checkTag(protolex, $5.start(), $5.val)
+		if !unicode.IsUpper(rune($3.val[0])) {
+			lexError(protolex, $3.start(), fmt.Sprintf("group %s should have a name that starts with a capital letter", $3.val))
+		}
+		lbl := &labelNode{basicNode: $1.basicNode}
+		$$ = &groupNode{groupKeyword: $2, label: lbl, name: $3, tag: $5, decls: $7}
+		$$.setRange($1, $8)
+	}
+	| _REPEATED _GROUP name '=' _INT_LIT '{' messageBody '}' {
+		checkTag(protolex, $5.start(), $5.val)
+		if !unicode.IsUpper(rune($3.val[0])) {
+			lexError(protolex, $3.start(), fmt.Sprintf("group %s should have a name that starts with a capital letter", $3.val))
+		}
+		lbl := &labelNode{basicNode: $1.basicNode, repeated: true}
+		$$ = &groupNode{groupKeyword: $2, label: lbl, name: $3, tag: $5, decls: $7}
+		$$.setRange($1, $8)
+	}
+
+oneof : _ONEOF name '{' oneofBody '}' {
+		c := 0
+		for _, el := range $4 {
+			if el.field != nil {
+				c++
+			}
+		}
+		if c == 0 {
+			lexError(protolex, $1.start(), "oneof must contain at least one field")
+		}
+		$$ = &oneOfNode{name: $2, decls: $4}
+		$$.setRange($1, $5)
+	}
+
+oneofBody : oneofBody oneofItem {
+		$$ = append($1, $2...)
+	}
+	| oneofItem
+	| {
+		$$ = nil
+	}
+
+oneofItem : option {
+		$$ = []*oneOfElement{{option: $1[0]}}
+	}
+	| oneofField {
+		$$ = []*oneOfElement{{field: $1}}
+	}
+	| ';' {
+		$$ = []*oneOfElement{{empty: $1}}
+	}
+
+oneofField : typeIdent name '=' _INT_LIT ';' {
+		checkTag(protolex, $4.start(), $4.val)
+		$$ = &fieldNode{fldType: $1, name: $2, tag: $4}
+		$$.setRange($1, $5)
+	}
+	| typeIdent name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $4.start(), $4.val)
+		$$ = &fieldNode{fldType: $1, name: $2, tag: $4, options: $6}
+		$$.setRange($1, $8)
+	}
+
+mapField : _MAP '<' keyType ',' typeIdent '>' name '=' _INT_LIT ';' {
+		checkTag(protolex, $9.start(), $9.val)
+		$$ = &mapFieldNode{mapKeyword: $1, keyType: $3, valueType: $5, name: $7, tag: $9}
+		$$.setRange($1, $10)
+	}
+	| _MAP '<' keyType ',' typeIdent '>' name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkTag(protolex, $9.start(), $9.val)
+		$$ = &mapFieldNode{mapKeyword: $1, keyType: $3, valueType: $5, name: $7, tag: $9, options: $11}
+		$$.setRange($1, $13)
+	}
+
+keyType : _INT32
+	| _INT64
+	| _UINT32
+	| _UINT64
+	| _SINT32
+	| _SINT64
+	| _FIXED32
+	| _FIXED64
+	| _SFIXED32
+	| _SFIXED64
+	| _BOOL
+	| _STRING
+
+extensions : _EXTENSIONS tagRanges ';' {
+		$$ = &extensionRangeNode{ranges: $2}
+		$$.setRange($1, $3)
+	}
+	| _EXTENSIONS tagRanges '[' fieldOptions ']' ';' {
+		$$ = &extensionRangeNode{ranges: $2, options: $4}
+		$$.setRange($1, $6)
+	}
+
+tagRanges : tagRanges ',' tagRange {
+		$$ = append($1, $3...)
+	}
+	| tagRange
+
+tagRange : _INT_LIT {
+		if $1.val > internal.MaxTag {
+			lexError(protolex, $1.start(), fmt.Sprintf("range includes out-of-range tag: %d (should be between 0 and %d)", $1.val, internal.MaxTag))
+		}
+		r := &rangeNode{stNode: $1, enNode: $1, st: int32($1.val), en: int32($1.val)}
+		r.setRange($1, $1)
+		$$ = []*rangeNode{r}
+	}
+	| _INT_LIT _TO _INT_LIT {
+		if $1.val > internal.MaxTag {
+			lexError(protolex, $1.start(), fmt.Sprintf("range start is out-of-range tag: %d (should be between 0 and %d)", $1.val, internal.MaxTag))
+		}
+		if $3.val > internal.MaxTag {
+			lexError(protolex, $3.start(), fmt.Sprintf("range end is out-of-range tag: %d (should be between 0 and %d)", $3.val, internal.MaxTag))
+		}
+		if $1.val > $3.val {
+			lexError(protolex, $1.start(), fmt.Sprintf("range, %d to %d, is invalid: start must be <= end", $1.val, $3.val))
+		}
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: int32($3.val)}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+	| _INT_LIT _TO _MAX {
+		if $1.val > internal.MaxTag {
+			lexError(protolex, $1.start(), fmt.Sprintf("range start is out-of-range tag: %d (should be between 0 and %d)", $1.val, internal.MaxTag))
+		}
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: internal.MaxTag}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+
+enumRanges : enumRanges ',' enumRange {
+		$$ = append($1, $3...)
+	}
+	| enumRange
+
+enumRange : _INT_LIT {
+		checkUint64InInt32Range(protolex, $1.start(), $1.val)
+		r := &rangeNode{stNode: $1, enNode: $1, st: int32($1.val), en: int32($1.val)}
+		r.setRange($1, $1)
+		$$ = []*rangeNode{r}
+	}
+	| negIntLit {
+		checkInt64InInt32Range(protolex, $1.start(), $1.val)
+		r := &rangeNode{stNode: $1, enNode: $1, st: int32($1.val), en: int32($1.val)}
+		r.setRange($1, $1)
+		$$ = []*rangeNode{r}
+	}
+	| _INT_LIT _TO _INT_LIT {
+		checkUint64InInt32Range(protolex, $1.start(), $1.val)
+		checkUint64InInt32Range(protolex, $3.start(), $3.val)
+		if $1.val > $3.val {
+			lexError(protolex, $1.start(), fmt.Sprintf("range, %d to %d, is invalid: start must be <= end", $1.val, $3.val))
+		}
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: int32($3.val)}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+	| negIntLit _TO negIntLit {
+		checkInt64InInt32Range(protolex, $1.start(), $1.val)
+		checkInt64InInt32Range(protolex, $3.start(), $3.val)
+		if $1.val > $3.val {
+			lexError(protolex, $1.start(), fmt.Sprintf("range, %d to %d, is invalid: start must be <= end", $1.val, $3.val))
+		}
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: int32($3.val)}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+	| negIntLit _TO _INT_LIT {
+		checkInt64InInt32Range(protolex, $1.start(), $1.val)
+		checkUint64InInt32Range(protolex, $3.start(), $3.val)
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: int32($3.val)}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+	| _INT_LIT _TO _MAX {
+		checkUint64InInt32Range(protolex, $1.start(), $1.val)
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: math.MaxInt32}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+	| negIntLit _TO _MAX {
+		checkInt64InInt32Range(protolex, $1.start(), $1.val)
+		r := &rangeNode{stNode: $1, enNode: $3, st: int32($1.val), en: math.MaxInt32}
+		r.setRange($1, $3)
+		$$ = []*rangeNode{r}
+	}
+
+msgReserved : _RESERVED tagRanges ';' {
+		$$ = &reservedNode{ranges: $2}
+		$$.setRange($1, $3)
+	}
+	| reservedNames
+
+enumReserved : _RESERVED enumRanges ';' {
+		$$ = &reservedNode{ranges: $2}
+		$$.setRange($1, $3)
+	}
+	| reservedNames
+
+reservedNames : _RESERVED fieldNames ';' {
+		rsvd := map[string]struct{}{}
+		for _, n := range $2 {
+			if _, ok := rsvd[n.val]; ok {
+				lexError(protolex, n.start(), fmt.Sprintf("name %q is reserved multiple times", n.val))
+				break
+			}
+			rsvd[n.val] = struct{}{}
+		}
+		$$ = &reservedNode{names: $2}
+		$$.setRange($1, $3)
+	}
+
+fieldNames : fieldNames ',' stringLit {
+		$$ = append($1, $3)
+	}
+	| stringLit {
+		$$ = []*stringLiteralNode{$1}
+	}
+
+enum : _ENUM name '{' enumBody '}' {
+		c := 0
+		for _, el := range $4 {
+			if el.value != nil {
+				c++
+			}
+		}
+		if c == 0 {
+			lexError(protolex, $1.start(), "enums must define at least one value")
+		}
+		$$ = &enumNode{name: $2, decls: $4}
+		$$.setRange($1, $5)
+	}
+
+enumBody : enumBody enumItem {
+		$$ = append($1, $2...)
+	}
+	| enumItem
+	| {
+		$$ = nil
+	}
+
+enumItem : option {
+		$$ = []*enumElement{{option: $1[0]}}
+	}
+	| enumField {
+		$$ = []*enumElement{{value: $1}}
+	}
+	| enumReserved {
+		$$ = []*enumElement{{reserved: $1}}
+	}
+	| ';' {
+		$$ = []*enumElement{{empty: $1}}
+	}
+
+enumField : name '=' _INT_LIT ';' {
+		checkUint64InInt32Range(protolex, $3.start(), $3.val)
+		$$ = &enumValueNode{name: $1, numberP: $3}
+		$$.setRange($1, $4)
+	}
+	|  name '=' _INT_LIT '[' fieldOptions ']' ';' {
+		checkUint64InInt32Range(protolex, $3.start(), $3.val)
+		$$ = &enumValueNode{name: $1, numberP: $3, options: $5}
+		$$.setRange($1, $7)
+	}
+	| name '=' negIntLit ';' {
+		checkInt64InInt32Range(protolex, $3.start(), $3.val)
+		$$ = &enumValueNode{name: $1, numberN: $3}
+		$$.setRange($1, $4)
+	}
+	|  name '=' negIntLit '[' fieldOptions ']' ';' {
+		checkInt64InInt32Range(protolex, $3.start(), $3.val)
+		$$ = &enumValueNode{name: $1, numberN: $3, options: $5}
+		$$.setRange($1, $7)
+	}
+
+message : _MESSAGE name '{' messageBody '}' {
+		$$ = &messageNode{name: $2, decls: $4}
+		$$.setRange($1, $5)
+	}
+
+messageBody : messageBody messageItem {
+		$$ = append($1, $2...)
+	}
+	| messageItem
+	| {
+		$$ = nil
+	}
+
+messageItem : field {
+		$$ = []*messageElement{{field: $1}}
+	}
+	| enum {
+		$$ = []*messageElement{{enum: $1}}
+	}
+	| message {
+		$$ = []*messageElement{{nested: $1}}
+	}
+	| extend {
+		$$ = []*messageElement{{extend: $1}}
+	}
+	| extensions {
+		$$ = []*messageElement{{extensionRange: $1}}
+	}
+	| group {
+		$$ = []*messageElement{{group: $1}}
+	}
+	| option {
+		$$ = []*messageElement{{option: $1[0]}}
+	}
+	| oneof {
+		$$ = []*messageElement{{oneOf: $1}}
+	}
+	| mapField {
+		$$ = []*messageElement{{mapField: $1}}
+	}
+	| msgReserved {
+		$$ = []*messageElement{{reserved: $1}}
+	}
+	| ';' {
+		$$ = []*messageElement{{empty: $1}}
+	}
+
+extend : _EXTEND typeIdent '{' extendBody '}' {
+		c := 0
+		for _, el := range $4 {
+			if el.field != nil || el.group != nil {
+				c++
+			}
+		}
+		if c == 0 {
+			lexError(protolex, $1.start(), "extend sections must define at least one extension")
+		}
+		$$ = &extendNode{extendee: $2, decls: $4}
+		$$.setRange($1, $5)
+	}
+
+extendBody : extendBody extendItem {
+		$$ = append($1, $2...)
+	}
+	| extendItem
+	| {
+		$$ = nil
+	}
+
+extendItem : field {
+		$$ = []*extendElement{{field: $1}}
+	}
+	| group {
+		$$ = []*extendElement{{group: $1}}
+	}
+	| ';' {
+		$$ = []*extendElement{{empty: $1}}
+	}
+
+service : _SERVICE name '{' serviceBody '}' {
+		$$ = &serviceNode{name: $2, decls: $4}
+		$$.setRange($1, $5)
+	}
+
+serviceBody : serviceBody serviceItem {
+		$$ = append($1, $2...)
+	}
+	| serviceItem
+	| {
+		$$ = nil
+	}
+
+// NB: doc suggests support for "stream" declaration, separate from "rpc", but
+// it does not appear to be supported in protoc (doc is likely from grammar for
+// Google-internal version of protoc, with support for streaming stubby)
+serviceItem : option {
+		$$ = []*serviceElement{{option: $1[0]}}
+	}
+	| rpc {
+		$$ = []*serviceElement{{rpc: $1}}
+	}
+	| ';' {
+		$$ = []*serviceElement{{empty: $1}}
+	}
+
+rpc : _RPC name '(' rpcType ')' _RETURNS '(' rpcType ')' ';' {
+		$$ = &methodNode{name: $2, input: $4, output: $8}
+		$$.setRange($1, $10)
+	}
+	| _RPC name '(' rpcType ')' _RETURNS '(' rpcType ')' '{' rpcOptions '}' {
+		$$ = &methodNode{name: $2, input: $4, output: $8, options: $11}
+		$$.setRange($1, $12)
+	}
+
+rpcType : _STREAM typeIdent {
+		$$ = &rpcTypeNode{msgType: $2, streamKeyword: $1}
+		$$.setRange($1, $2)
+	}
+	| typeIdent {
+		$$ = &rpcTypeNode{msgType: $1}
+		$$.setRange($1, $1)
+	}
+
+rpcOptions : rpcOptions rpcOption {
+		$$ = append($1, $2...)
+	}
+	| rpcOption
+	| {
+		$$ = []*optionNode{}
+	}
+
+rpcOption : option {
+		$$ = $1
+	}
+	| ';' {
+		$$ = []*optionNode{}
+	}
+
+name : _NAME
+	| _SYNTAX
+	| _IMPORT
+	| _WEAK
+	| _PUBLIC
+	| _PACKAGE
+	| _OPTION
+	| _TRUE
+	| _FALSE
+	| _INF
+	| _NAN
+	| _REPEATED
+	| _OPTIONAL
+	| _REQUIRED
+	| _DOUBLE
+	| _FLOAT
+	| _INT32
+	| _INT64
+	| _UINT32
+	| _UINT64
+	| _SINT32
+	| _SINT64
+	| _FIXED32
+	| _FIXED64
+	| _SFIXED32
+	| _SFIXED64
+	| _BOOL
+	| _STRING
+	| _BYTES
+	| _GROUP
+	| _ONEOF
+	| _MAP
+	| _EXTENSIONS
+	| _TO
+	| _MAX
+	| _RESERVED
+	| _ENUM
+	| _MESSAGE
+	| _EXTEND
+	| _SERVICE
+	| _RPC
+	| _STREAM
+	| _RETURNS
+
+%%