%{
package parser

//lint:file-ignore SA4006 generated parser has unused values

import (
	"math"
	"strings"

	"github.com/bufbuild/protocompile/ast"
)

%}

// 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         *ast.FileNode
	syn          *ast.SyntaxNode
	ed           *ast.EditionNode
	fileElements []ast.FileElement
	pkg          nodeWithRunes[*ast.PackageNode]
	imprt        nodeWithRunes[*ast.ImportNode]
	msg          nodeWithRunes[*ast.MessageNode]
	msgElements  []ast.MessageElement
	fld          *ast.FieldNode
	msgFld       nodeWithRunes[*ast.FieldNode]
	mapFld       nodeWithRunes[*ast.MapFieldNode]
	mapType      *ast.MapTypeNode
	grp          *ast.GroupNode
	msgGrp       nodeWithRunes[*ast.GroupNode]
	oo           nodeWithRunes[*ast.OneofNode]
	ooElement    ast.OneofElement
	ooElements   []ast.OneofElement
	ext          nodeWithRunes[*ast.ExtensionRangeNode]
	resvd        nodeWithRunes[*ast.ReservedNode]
	en           nodeWithRunes[*ast.EnumNode]
	enElements   []ast.EnumElement
	env          nodeWithRunes[*ast.EnumValueNode]
	extend       nodeWithRunes[*ast.ExtendNode]
	extElement   ast.ExtendElement
	extElements  []ast.ExtendElement
	svc          nodeWithRunes[*ast.ServiceNode]
	svcElements  []ast.ServiceElement
	mtd          nodeWithRunes[*ast.RPCNode]
	mtdMsgType   *ast.RPCTypeNode
	mtdElements  []ast.RPCElement
	optRaw       *ast.OptionNode
	opt          nodeWithRunes[*ast.OptionNode]
	opts         *compactOptionSlices
	refRaw       *ast.FieldReferenceNode
	ref			     nodeWithRunes[*ast.FieldReferenceNode]
	optNms       *fieldRefSlices
	cmpctOpts    *ast.CompactOptionsNode
	rng          *ast.RangeNode
	rngs         *rangeSlices
	names        *nameSlices
	cidPart      nodeWithRunes[*ast.IdentNode]
	cid          *identSlices
	tid          ast.IdentValueNode
	sl           *valueSlices
	msgLitFlds   *messageFieldList
	msgLitFld    *ast.MessageFieldNode
	v            ast.ValueNode
	il           ast.IntValueNode
	str          []*ast.StringLiteralNode
	s            *ast.StringLiteralNode
	i            *ast.UintLiteralNode
	f            *ast.FloatLiteralNode
	id           *ast.IdentNode
	b            *ast.RuneNode
	bs           []*ast.RuneNode
	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>          syntaxDecl
%type <ed>           editionDecl
%type <fileElements> fileBody fileElement fileElements
%type <imprt>        importDecl
%type <pkg>          packageDecl
%type <optRaw>       compactOption oneofOptionDecl
%type <opt>          optionDecl compactOptionEntry compactOptionFinal
%type <opts>         compactOptionDecls compactOptionLeadingDecls
%type <refRaw>       extensionName messageLiteralFieldName optionNamePart
%type <ref>          optionNameEntry optionNameFinal
%type <optNms>       optionName optionNameLeading
%type <cmpctOpts>    compactOptions
%type <v>            fieldValue optionValue scalarValue fieldScalarValue messageLiteralWithBraces messageLiteral numLit specialFloatLit listLiteral listElement listOfMessagesLiteral messageValue
%type <il>           enumValueNumber
%type <id>           identifier mapKeyType msgElementName extElementName oneofElementName notGroupElementName mtdElementName enumValueName fieldCardinality declIdent nonDeclIdent
%type <cidPart>      qualifiedIdentifierEntry qualifiedIdentifierFinal mtdElementIdentEntry mtdElementIdentFinal
%type <cid>          qualifiedIdentifier msgElementIdent extElementIdent oneofElementIdent notGroupElementIdent mtdElementIdent qualifiedIdentifierDot qualifiedIdentifierLeading mtdElementIdentLeading
%type <tid>          typeName msgElementTypeIdent extElementTypeIdent oneofElementTypeIdent notGroupElementTypeIdent mtdElementTypeIdent
%type <sl>           listElements messageLiterals
%type <msgLitFlds>   messageLiteralFieldEntry messageLiteralFields messageTextFormat
%type <msgLitFld>    messageLiteralField
%type <msgFld>       messageFieldDecl
%type <fld>          oneofFieldDecl extensionFieldDecl
%type <oo>           oneofDecl
%type <grp>          groupDecl oneofGroupDecl
%type <msgGrp>       messageGroupDecl
%type <mapFld>       mapFieldDecl
%type <mapType>      mapType
%type <msg>          messageDecl
%type <msgElements>  messageElement messageElements messageBody
%type <ooElement>    oneofElement
%type <ooElements>   oneofElements oneofBody
%type <names>        fieldNameStrings fieldNameIdents
%type <resvd>        msgReserved enumReserved reservedNames
%type <rng>          tagRange enumValueRange
%type <rngs>         tagRanges enumValueRanges
%type <ext>          extensionRangeDecl
%type <en>           enumDecl
%type <enElements>   enumElement enumElements enumBody
%type <env>          enumValueDecl
%type <extend>       extensionDecl
%type <extElement>   extensionElement
%type <extElements>  extensionElements extensionBody
%type <str>          stringLit
%type <svc>          serviceDecl
%type <svcElements>  serviceElement serviceElements serviceBody
%type <mtd>          methodDecl
%type <mtdElements>  methodElement methodElements methodBody
%type <mtdMsgType>   methodMessageType
%type <b>            semicolon
%type <bs>           semicolons semicolonList

// same for terminals
%token <s>   _STRING_LIT
%token <i>   _INT_LIT
%token <f>   _FLOAT_LIT
%token <id>  _NAME
%token <id>  _SYNTAX _EDITION _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 _EXPORT _LOCAL
%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 : syntaxDecl {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNode(lex.info, $1, nil, lex.eof)
		lex.res = $$
	}
	| editionDecl {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNodeWithEdition(lex.info, $1, nil, lex.eof)
		lex.res = $$
	}
	| fileBody  {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNode(lex.info, nil, $1, lex.eof)
		lex.res = $$
	}
	| syntaxDecl fileBody {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNode(lex.info, $1, $2, lex.eof)
		lex.res = $$
	}
	| editionDecl fileBody {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNodeWithEdition(lex.info, $1, $2, lex.eof)
		lex.res = $$
	}
	| {
		lex := protolex.(*protoLex)
		$$ = ast.NewFileNode(lex.info, nil, nil, lex.eof)
		lex.res = $$
	}

fileBody : semicolons fileElements {
		$$ = prependRunes(toFileElement, $1, $2)
	}

fileElements : fileElements fileElement {
		$$ = append($1, $2...)
	}
	| fileElement {
		$$ = $1
	}

fileElement : importDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| packageDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| optionDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| messageDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| enumDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| extensionDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| serviceDecl {
	  $$ = toElements[ast.FileElement](toFileElement, $1.Node, $1.Runes)
	}
	| error {
		$$ = nil
	}

semicolonList : ';' {
		$$ = []*ast.RuneNode{$1}
	}
	| semicolonList ';' {
		$$ = append($1, $2)
	}

semicolons : semicolonList {
		$$ = $1
	}
	| {
		$$ = nil
	}

semicolon : ';' {
		$$ = $1
	} |
	{
		protolex.(*protoLex).Error("syntax error: expecting ';'")
		$$ = nil
	}

syntaxDecl : _SYNTAX '=' stringLit ';' {
		$$ = ast.NewSyntaxNode($1.ToKeyword(), $2, toStringValueNode($3), $4)
	}

editionDecl : _EDITION '=' stringLit ';' {
		$$ = ast.NewEditionNode($1.ToKeyword(), $2, toStringValueNode($3), $4)
	}

importDecl : _IMPORT stringLit semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewImportNodeWithModifier($1.ToKeyword(), nil, toStringValueNode($2), semi), extra...)
	}
	| _IMPORT _WEAK stringLit semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewImportNodeWithModifier($1.ToKeyword(), $2.ToKeyword(), toStringValueNode($3), semi), extra...)
	}
	| _IMPORT _PUBLIC stringLit semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewImportNodeWithModifier($1.ToKeyword(), $2.ToKeyword(), toStringValueNode($3), semi), extra...)
	}
	| _IMPORT _OPTION stringLit semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewImportNodeWithModifier($1.ToKeyword(), $2.ToKeyword(), toStringValueNode($3), semi), extra...)
	}

packageDecl : _PACKAGE qualifiedIdentifier semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewPackageNode($1.ToKeyword(), $2.toIdentValueNode(nil), semi), extra...)
	}

qualifiedIdentifier : identifier {
		$$ = &identSlices{idents: []*ast.IdentNode{$1}}
	}
	| qualifiedIdentifier '.' identifier {
		$1.idents = append($1.idents, $3)
		$1.dots = append($1.dots, $2)
		$$ = $1
	}

qualifiedIdentifierDot : qualifiedIdentifierFinal {
		$$ = &identSlices{idents: []*ast.IdentNode{$1.Node}, dots: $1.Runes}
	}
	| qualifiedIdentifierLeading qualifiedIdentifierFinal {
		$1.idents = append($1.idents, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

qualifiedIdentifierLeading : qualifiedIdentifierEntry {
	  $$ = &identSlices{idents: []*ast.IdentNode{$1.Node}, dots: $1.Runes}
	}
	| qualifiedIdentifierLeading qualifiedIdentifierEntry {
		$1.idents = append($1.idents, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

qualifiedIdentifierFinal : identifier {
		$$ = newNodeWithRunes($1)
	}
	| qualifiedIdentifierEntry {
		protolex.(*protoLex).Error("syntax error: unexpected '.'")
		$$ = $1
	}

qualifiedIdentifierEntry : identifier '.' {
		$$ = newNodeWithRunes($1, $2)
	}

// to mimic limitations of protoc recursive-descent parser,
// we don't allowed message statement keywords as identifiers
// (or oneof statement keywords [e.g. "option"] below)

msgElementIdent : msgElementName {
		$$ = &identSlices{idents: []*ast.IdentNode{$1}}
	}
	| msgElementIdent '.' identifier {
		$1.idents = append($1.idents, $3)
		$1.dots = append($1.dots, $2)
		$$ = $1
	}

extElementIdent : extElementName {
		$$ = &identSlices{idents: []*ast.IdentNode{$1}}
	}
	| extElementIdent '.' identifier {
		$1.idents = append($1.idents, $3)
		$1.dots = append($1.dots, $2)
		$$ = $1
	}

oneofElementIdent : oneofElementName {
		$$ = &identSlices{idents: []*ast.IdentNode{$1}}
	}
	| oneofElementIdent '.' identifier {
		$1.idents = append($1.idents, $3)
		$1.dots = append($1.dots, $2)
		$$ = $1
	}

notGroupElementIdent : notGroupElementName {
		$$ = &identSlices{idents: []*ast.IdentNode{$1}}
	}
	| notGroupElementIdent '.' identifier {
		$1.idents = append($1.idents, $3)
		$1.dots = append($1.dots, $2)
		$$ = $1
	}

mtdElementIdent : mtdElementIdentFinal {
		$$ = &identSlices{idents: []*ast.IdentNode{$1.Node}, dots: $1.Runes}
	}
	| mtdElementIdentLeading mtdElementIdentFinal {
		$1.idents = append($1.idents, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

mtdElementIdentLeading : mtdElementIdentEntry {
	  $$ = &identSlices{idents: []*ast.IdentNode{$1.Node}, dots: $1.Runes}
	}
	| mtdElementIdentLeading mtdElementIdentEntry {
		$1.idents = append($1.idents, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

mtdElementIdentFinal : mtdElementName {
	  $$ = newNodeWithRunes($1)
  }
  | mtdElementIdentEntry {
		protolex.(*protoLex).Error("syntax error: unexpected '.'")
		$$ = $1
	}

mtdElementIdentEntry : mtdElementName '.' {
	  $$ = newNodeWithRunes($1, $2)
	}

oneofOptionDecl : _OPTION optionName '=' optionValue semicolon {
		optName := ast.NewOptionNameNode($2.refs, $2.dots)
		$$ = ast.NewOptionNode($1.ToKeyword(), optName, $3, $4, $5)
	}

optionDecl : _OPTION optionName '=' optionValue semicolons {
		optName := ast.NewOptionNameNode($2.refs, $2.dots)
		semi, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewOptionNode($1.ToKeyword(), optName, $3, $4, semi), extra...)
	}

optionNamePart : identifier {
		$$ = ast.NewFieldReferenceNode($1)
	}
	| extensionName {
		$$ = $1
	}

optionNameEntry : optionNamePart '.' {
		$$ = newNodeWithRunes($1, $2)
	}

optionNameFinal : optionNamePart {
		$$ = newNodeWithRunes($1)
	}
	| optionNameEntry {
		protolex.(*protoLex).Error("syntax error: unexpected '.'")
		$$ = $1
	}

optionNameLeading : optionNameEntry {
	  $$ = &fieldRefSlices{refs: []*ast.FieldReferenceNode{$1.Node}, dots: $1.Runes}
  }
	| optionNameLeading optionNameEntry {
		$1.refs = append($1.refs, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

optionName : optionNameFinal {
	  $$ = &fieldRefSlices{refs: []*ast.FieldReferenceNode{$1.Node}, dots: $1.Runes}
	}
	| optionNameLeading optionNameFinal {
		$1.refs = append($1.refs, $2.Node)
		$1.dots = append($1.dots, $2.Runes...)
		$$ = $1
	}

extensionName : '(' typeName ')' {
		$$ = ast.NewExtensionFieldReferenceNode($1, $2, $3)
	}

optionValue : scalarValue
	| messageLiteralWithBraces

scalarValue : stringLit {
		$$ = toStringValueNode($1)
	}
	| numLit
	| specialFloatLit
	| identifier {
		$$ = $1
	}

numLit : _FLOAT_LIT {
		$$ = $1
	}
	| '-' _FLOAT_LIT {
		$$ = ast.NewSignedFloatLiteralNode($1, $2)
	}
	| _INT_LIT {
		$$ = $1
	}
	| '-' _INT_LIT {
		if $2.Val > math.MaxInt64 + 1 {
			// can't represent as int so treat as float literal
			$$ = ast.NewSignedFloatLiteralNode($1, $2)
		} else {
			$$ = ast.NewNegativeIntLiteralNode($1, $2)
		}
	}

specialFloatLit : '-' _INF {
		f := ast.NewSpecialFloatLiteralNode($2.ToKeyword())
		$$ = ast.NewSignedFloatLiteralNode($1, f)
	}
	| '-' _NAN {
		f := ast.NewSpecialFloatLiteralNode($2.ToKeyword())
		$$ = ast.NewSignedFloatLiteralNode($1, f)
	}

stringLit : _STRING_LIT {
		$$ = []*ast.StringLiteralNode{$1}
	}
	| stringLit _STRING_LIT {
		$$ = append($1, $2)
	}

messageLiteralWithBraces : '{' messageTextFormat '}' {
		if $2 == nil {
			$$ = ast.NewMessageLiteralNode($1, nil, nil, $3)
		} else {
			fields, delimiters := $2.toNodes()
			$$ = ast.NewMessageLiteralNode($1, fields, delimiters, $3)
		}
	}
	| '{' '}' {
		$$ = ast.NewMessageLiteralNode($1, nil, nil, $2)
	}

messageTextFormat : messageLiteralFields

messageLiteralFields : messageLiteralFieldEntry
	| messageLiteralFieldEntry messageLiteralFields {
		if $1 != nil {
			$1.next = $2
			$$ = $1
		} else {
			$$ = $2
		}
	}

messageLiteralFieldEntry : messageLiteralField {
		if $1 != nil {
			$$ = &messageFieldList{field: $1}
		} else {
			$$ = nil
		}
	}
	| messageLiteralField ',' {
		if $1 != nil {
			$$ = &messageFieldList{field: $1, delimiter: $2}
		} else {
			$$ = nil
		}
	}
	| messageLiteralField ';' {
		if $1 != nil {
			$$ = &messageFieldList{field: $1, delimiter: $2}
		} else {
			$$ = nil
		}
	}
	| error ',' {
		$$ = nil
	}
	| error ';' {
		$$ = nil
	}
	| error {
		$$ = nil
	}

messageLiteralField : messageLiteralFieldName ':' fieldValue {
		if $1 != nil && $2 != nil {
			$$ = ast.NewMessageFieldNode($1, $2, $3)
		} else {
			$$ = nil
		}
	}
	| messageLiteralFieldName messageValue {
		if $1 != nil && $2 != nil {
			$$ = ast.NewMessageFieldNode($1, nil, $2)
		} else {
			$$ = nil
		}
	}
	| error ':' fieldValue {
		$$ = nil
	}

messageLiteralFieldName : identifier {
		$$ = ast.NewFieldReferenceNode($1)
	}
	| '[' qualifiedIdentifierDot ']' {
		$$ = ast.NewExtensionFieldReferenceNode($1, $2.toIdentValueNode(nil), $3)
	}
	| '[' qualifiedIdentifierDot '/' qualifiedIdentifierDot ']' {
		$$ = ast.NewAnyTypeReferenceNode($1, $2.toIdentValueNode(nil), $3, $4.toIdentValueNode(nil), $5)
	}
	| '[' error ']' {
		$$ = nil
	}

fieldValue : fieldScalarValue
	| messageLiteral
	| listLiteral

fieldScalarValue : stringLit {
		$$ = toStringValueNode($1)
	}
	| numLit
	| '-' identifier {
		kw := $2.ToKeyword()
		switch strings.ToLower(kw.Val) {
		case "inf", "infinity", "nan":
			// these are acceptable
		default:
			// anything else is not
			protolex.(*protoLex).Error(`only identifiers "inf", "infinity", or "nan" may appear after negative sign`)
		}
		// we'll validate the identifier later
		f := ast.NewSpecialFloatLiteralNode(kw)
		$$ = ast.NewSignedFloatLiteralNode($1, f)
	}
	| identifier {
		$$ = $1
	}

messageValue : messageLiteral
	| listOfMessagesLiteral

messageLiteral : messageLiteralWithBraces
	| '<' messageTextFormat '>' {
		if $2 == nil {
			$$ = ast.NewMessageLiteralNode($1, nil, nil, $3)
		} else {
			fields, delimiters := $2.toNodes()
			$$ = ast.NewMessageLiteralNode($1, fields, delimiters, $3)
		}
	}
	| '<' '>' {
		$$ = ast.NewMessageLiteralNode($1, nil, nil, $2)
	}

listLiteral : '[' listElements ']' {
		if $2 == nil {
			$$ = ast.NewArrayLiteralNode($1, nil, nil, $3)
		} else {
			$$ = ast.NewArrayLiteralNode($1, $2.vals, $2.commas, $3)
		}
	}
	| '[' ']' {
		$$ = ast.NewArrayLiteralNode($1, nil, nil, $2)
	}
	| '[' error ']' {
		$$ = ast.NewArrayLiteralNode($1, nil, nil, $3)
	}

listElements : listElement {
		$$ = &valueSlices{vals: []ast.ValueNode{$1}}
	}
	| listElements ',' listElement {
		$1.vals = append($1.vals, $3)
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

listElement : fieldScalarValue
	| messageLiteral

listOfMessagesLiteral : '[' messageLiterals ']' {
		if $2 == nil {
			$$ = ast.NewArrayLiteralNode($1, nil, nil, $3)
		} else {
			$$ = ast.NewArrayLiteralNode($1, $2.vals, $2.commas, $3)
		}
	}
	| '[' ']' {
		$$ = ast.NewArrayLiteralNode($1, nil, nil, $2)
	}
	| '[' error ']' {
		$$ = ast.NewArrayLiteralNode($1, nil, nil, $3)
	}

messageLiterals : messageLiteral {
		$$ = &valueSlices{vals: []ast.ValueNode{$1}}
	}
	| messageLiterals ',' messageLiteral {
		$1.vals = append($1.vals, $3)
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

typeName : qualifiedIdentifierDot {
		$$ = $1.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifierDot {
		$$ = $2.toIdentValueNode($1)
	}

msgElementTypeIdent : msgElementIdent {
		$$ = $1.toIdentValueNode(nil)
	}
	// We have to carve out productions for names that start with
	// 'export' or 'local' to avoid shift/reduce conflicts in the
	// generated parser.
	| _EXPORT '.' qualifiedIdentifier {
		$3.prefix($1, $2)
		$$ = $3.toIdentValueNode(nil)
    }
	|  _LOCAL '.' qualifiedIdentifier {
		$3.prefix($1, $2)
		$$ = $3.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifier {
		$$ = $2.toIdentValueNode($1)
	}

extElementTypeIdent : extElementIdent {
		$$ = $1.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifier {
		$$ = $2.toIdentValueNode($1)
	}

oneofElementTypeIdent : oneofElementIdent {
		$$ = $1.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifier {
		$$ = $2.toIdentValueNode($1)
	}

notGroupElementTypeIdent : notGroupElementIdent {
		$$ = $1.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifier {
		$$ = $2.toIdentValueNode($1)
	}

mtdElementTypeIdent : mtdElementIdent {
		$$ = $1.toIdentValueNode(nil)
	}
	| '.' qualifiedIdentifierDot {
		$$ = $2.toIdentValueNode($1)
	}

fieldCardinality : _REQUIRED
	| _OPTIONAL
	| _REPEATED

compactOptions : '[' compactOptionDecls ']' {
		$$ = ast.NewCompactOptionsNode($1, $2.options, $2.commas, $3)
	}
	| '[' ']' {
		protolex.(*protoLex).Error("compact options must have at least one option")
		$$ = ast.NewCompactOptionsNode($1, nil, nil, $2)
	}

compactOptionDecls : compactOptionFinal {
		$$ = &compactOptionSlices{options: []*ast.OptionNode{$1.Node}, commas: $1.Runes}
	}
	| compactOptionLeadingDecls compactOptionFinal {
		$1.options = append($1.options, $2.Node)
		$1.commas = append($1.commas, $2.Runes...)
		$$ = $1
	}

compactOptionLeadingDecls : compactOptionEntry {
	  $$ = &compactOptionSlices{options: []*ast.OptionNode{$1.Node}, commas: $1.Runes}
	}
	| compactOptionLeadingDecls compactOptionEntry {
		$1.options = append($1.options, $2.Node)
		$1.commas = append($1.commas, $2.Runes...)
		$$ = $1
	}

compactOptionFinal : compactOption {
		$$ = newNodeWithRunes($1)
	}
	| compactOptionEntry {
		protolex.(*protoLex).Error("syntax error: unexpected ','")
		$$ = $1
	}

compactOptionEntry : compactOption ',' {
	  $$ = newNodeWithRunes($1, $2)
  }

compactOption : optionName '=' optionValue {
		optName := ast.NewOptionNameNode($1.refs, $1.dots)
		$$ = ast.NewCompactOptionNode(optName, $2, $3)
	}
	| optionName {
		optName := ast.NewOptionNameNode($1.refs, $1.dots)
		protolex.(*protoLex).Error("compact option must have a value")
		$$ = ast.NewCompactOptionNode(optName, nil, nil)
	}


groupDecl : fieldCardinality _GROUP identifier '=' _INT_LIT '{' messageBody '}' {
		$$ = ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, nil, $6, $7, $8)
	}
	| fieldCardinality _GROUP identifier '=' _INT_LIT compactOptions '{' messageBody '}' {
		$$ = ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6, $7, $8, $9)
	}

messageGroupDecl : fieldCardinality _GROUP identifier '=' _INT_LIT '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, nil, $6, $7, $8), $9...)
	}
	| fieldCardinality _GROUP identifier '=' _INT_LIT compactOptions '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6, $7, $8, $9), $10...)
	}
	| fieldCardinality _GROUP identifier '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, nil, nil, nil, $4, $5, $6), $7...)
	}
	| fieldCardinality _GROUP identifier compactOptions '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewGroupNode($1.ToKeyword(), $2.ToKeyword(), $3, nil, nil, $4, $5, $6, $7), $8...)
	}

oneofDecl : _ONEOF identifier '{' oneofBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewOneofNode($1.ToKeyword(), $2, $3, $4, $5), $6...)
	}

oneofBody : {
		$$ = nil
	}
	| oneofElements

oneofElements : oneofElements oneofElement {
		if $2 != nil {
			$$ = append($1, $2)
		} else {
			$$ = $1
		}
	}
	| oneofElement {
		if $1 != nil {
			$$ = []ast.OneofElement{$1}
		} else {
			$$ = nil
		}
	}

oneofElement : oneofOptionDecl {
		$$ = $1
	}
	| oneofFieldDecl {
		$$ = $1
	}
	| oneofGroupDecl {
		$$ = $1
	}
	| error ';' {
		$$ = nil
	}
	| error {
		$$ = nil
	}

oneofFieldDecl : oneofElementTypeIdent identifier '=' _INT_LIT semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, $3, $4, nil, $5)
	}
	| oneofElementTypeIdent identifier '=' _INT_LIT compactOptions semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, $3, $4, $5, $6)
	}
	| oneofElementTypeIdent identifier semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, nil, nil, nil, $3)
	}
	| oneofElementTypeIdent identifier compactOptions semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, nil, nil, $3, $4)
	}

oneofGroupDecl : _GROUP identifier '=' _INT_LIT '{' messageBody '}' {
		$$ = ast.NewGroupNode(nil, $1.ToKeyword(), $2, $3, $4, nil, $5, $6, $7)
	}
	| _GROUP identifier '=' _INT_LIT compactOptions '{' messageBody '}' {
		$$ = ast.NewGroupNode(nil, $1.ToKeyword(), $2, $3, $4, $5, $6, $7, $8)
	}
	| _GROUP identifier '{' messageBody '}' {
		$$ = ast.NewGroupNode(nil, $1.ToKeyword(), $2, nil, nil, nil, $3, $4, $5)
	}
	| _GROUP identifier compactOptions '{' messageBody '}' {
		$$ = ast.NewGroupNode(nil, $1.ToKeyword(), $2, nil, nil, $3, $4, $5, $6)
	}


mapFieldDecl : mapType identifier '=' _INT_LIT semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewMapFieldNode($1, $2, $3, $4, nil, semi), extra...)
	}
	| mapType identifier '=' _INT_LIT compactOptions semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewMapFieldNode($1, $2, $3, $4, $5, semi), extra...)
	}
	| mapType identifier semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewMapFieldNode($1, $2, nil, nil, nil, semi), extra...)
	}
	| mapType identifier compactOptions semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewMapFieldNode($1, $2, nil, nil, $3, semi), extra...)
	}

mapType : _MAP '<' mapKeyType ',' typeName '>' {
		$$ = ast.NewMapTypeNode($1.ToKeyword(), $2, $3, $4, $5, $6)
	}

mapKeyType : _INT32
	| _INT64
	| _UINT32
	| _UINT64
	| _SINT32
	| _SINT64
	| _FIXED32
	| _FIXED64
	| _SFIXED32
	| _SFIXED64
	| _BOOL
	| _STRING

extensionRangeDecl : _EXTENSIONS tagRanges ';' semicolons {
	  // TODO: Tolerate a missing semicolon here. This currnelty creates a shift/reduce conflict
		// between `extensions 1 to 10` and `extensions 1` followed by `to = 10`.
		$$ = newNodeWithRunes(ast.NewExtensionRangeNode($1.ToKeyword(), $2.ranges, $2.commas, nil, $3), $4...)
	}
	| _EXTENSIONS tagRanges compactOptions semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewExtensionRangeNode($1.ToKeyword(), $2.ranges, $2.commas, $3, semi), extra...)
	}

tagRanges : tagRange {
		$$ = &rangeSlices{ranges: []*ast.RangeNode{$1}}
	}
	| tagRanges ',' tagRange {
		$1.ranges = append($1.ranges, $3)
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

tagRange : _INT_LIT {
		$$ = ast.NewRangeNode($1, nil, nil, nil)
	}
	| _INT_LIT _TO _INT_LIT {
		$$ = ast.NewRangeNode($1, $2.ToKeyword(), $3, nil)
	}
	| _INT_LIT _TO _MAX {
		$$ = ast.NewRangeNode($1, $2.ToKeyword(), nil, $3.ToKeyword())
	}

enumValueRanges : enumValueRange {
		$$ = &rangeSlices{ranges: []*ast.RangeNode{$1}}
	}
	| enumValueRanges ',' enumValueRange {
		$1.ranges = append($1.ranges, $3)
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

enumValueRange : enumValueNumber {
		$$ = ast.NewRangeNode($1, nil, nil, nil)
	}
	| enumValueNumber _TO enumValueNumber {
		$$ = ast.NewRangeNode($1, $2.ToKeyword(), $3, nil)
	}
	| enumValueNumber _TO _MAX {
		$$ = ast.NewRangeNode($1, $2.ToKeyword(), nil, $3.ToKeyword())
	}

enumValueNumber : _INT_LIT {
		$$ = $1
	}
	| '-' _INT_LIT {
		$$ = ast.NewNegativeIntLiteralNode($1, $2)
	}

msgReserved : _RESERVED tagRanges ';' semicolons {
	  // TODO: Tolerate a missing semicolon here. This currnelty creates a shift/reduce conflict
		// between `reserved 1 to 10` and `reserved 1` followed by `to = 10`.
		$$ = newNodeWithRunes(ast.NewReservedRangesNode($1.ToKeyword(), $2.ranges, $2.commas, $3), $4...)
	}
	| reservedNames

enumReserved : _RESERVED enumValueRanges ';' semicolons {
	  // TODO: Tolerate a missing semicolon here. This currnelty creates a shift/reduce conflict
		// between `reserved 1 to 10` and `reserved 1` followed by `to = 10`.
		$$ = newNodeWithRunes(ast.NewReservedRangesNode($1.ToKeyword(), $2.ranges, $2.commas, $3), $4...)
	}
	| reservedNames

reservedNames : _RESERVED fieldNameStrings semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewReservedNamesNode($1.ToKeyword(), $2.names, $2.commas, semi), extra...)
	}
	| _RESERVED fieldNameIdents semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewReservedIdentifiersNode($1.ToKeyword(), $2.idents, $2.commas, semi), extra...)
	}

fieldNameStrings : stringLit {
		$$ = &nameSlices{names: []ast.StringValueNode{toStringValueNode($1)}}
	}
	| fieldNameStrings ',' stringLit {
		$1.names = append($1.names, toStringValueNode($3))
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

fieldNameIdents : identifier {
		$$ = &nameSlices{idents: []*ast.IdentNode{$1}}
	}
	| fieldNameIdents ',' identifier {
		$1.idents = append($1.idents, $3)
		$1.commas = append($1.commas, $2)
		$$ = $1
	}

enumDecl : _ENUM identifier '{' enumBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewEnumNode($1.ToKeyword(), $2, $3, $4, $5), $6...)
	}
	| _EXPORT _ENUM identifier '{' enumBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewEnumNodeWithVisibility($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6), $7...)
	}
	| _LOCAL _ENUM identifier '{' enumBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewEnumNodeWithVisibility($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6), $7...)
	}

enumBody : semicolons {
		$$ = prependRunes(toEnumElement, $1, nil)
	}
	| semicolons enumElements {
		$$ = prependRunes(toEnumElement, $1, $2)
	}

enumElements : enumElements enumElement {
		$$ = append($1, $2...)
	}
	| enumElement {
		$$ = $1
	}

enumElement : optionDecl {
		$$ = toElements[ast.EnumElement](toEnumElement, $1.Node, $1.Runes)
	}
	| enumValueDecl {
		$$ = toElements[ast.EnumElement](toEnumElement, $1.Node, $1.Runes)
	}
	| enumReserved {
		$$ = toElements[ast.EnumElement](toEnumElement, $1.Node, $1.Runes)
	}
	| error {
		$$ = nil
	}

enumValueDecl : enumValueName '=' enumValueNumber semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewEnumValueNode($1, $2, $3, nil, semi), extra...)
	}
	|  enumValueName '=' enumValueNumber compactOptions semicolons {
		semi, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewEnumValueNode($1, $2, $3, $4, semi), extra...)
	}

messageDecl : _MESSAGE identifier '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewMessageNode($1.ToKeyword(), $2, $3, $4, $5), $6...)
	}
	| _EXPORT _MESSAGE identifier '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewMessageNodeWithVisibility($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6), $7...)
	}
	| _LOCAL _MESSAGE identifier '{' messageBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewMessageNodeWithVisibility($1.ToKeyword(), $2.ToKeyword(), $3, $4, $5, $6), $7...)
	}

messageBody : semicolons {
		$$ = prependRunes(toMessageElement, $1, nil)
	}
	| semicolons messageElements {
		$$ = prependRunes(toMessageElement, $1, $2)
	}

messageElements : messageElements messageElement {
		$$ = append($1, $2...)
	}
	| messageElement {
		$$ = $1
	}


messageElement : messageFieldDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| enumDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| messageDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| extensionDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| extensionRangeDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| messageGroupDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| optionDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| oneofDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| mapFieldDecl {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| msgReserved {
		$$ = toElements[ast.MessageElement](toMessageElement, $1.Node, $1.Runes)
	}
	| error {
		$$ = nil
	}

messageFieldDecl : fieldCardinality notGroupElementTypeIdent identifier '=' _INT_LIT semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewFieldNode($1.ToKeyword(), $2, $3, $4, $5, nil, semis), extra...)
	}
	| fieldCardinality notGroupElementTypeIdent identifier '=' _INT_LIT compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($7)
		$$ = newNodeWithRunes(ast.NewFieldNode($1.ToKeyword(), $2, $3, $4, $5, $6, semis), extra...)
	}
	| msgElementTypeIdent identifier '=' _INT_LIT semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, nil, semis), extra...)
	}
	| msgElementTypeIdent identifier '=' _INT_LIT compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, $5, semis), extra...)
	}
	| fieldCardinality notGroupElementTypeIdent identifier semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewFieldNode($1.ToKeyword(), $2, $3, nil, nil, nil, semis), extra...)
	}
	| fieldCardinality notGroupElementTypeIdent identifier compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewFieldNode($1.ToKeyword(), $2, $3, nil, nil, $4, semis), extra...)
	}
	| msgElementTypeIdent identifier semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, nil, semis), extra...)
	}
	| msgElementTypeIdent identifier compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, $3, semis), extra...)
	}
	// We have to carve out productions for fields without a cardinality
	// and type name that is 'export' or 'local' to avoid shift/reduce
	// conflicts in the generated parser.
	| _EXPORT identifier '=' _INT_LIT semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, nil, semis), extra...)
	}
	| _EXPORT identifier '=' _INT_LIT compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, $5, semis), extra...)
	}
	| _EXPORT identifier compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, $3, semis), extra...)
	}
	| _LOCAL identifier '=' _INT_LIT semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($5)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, nil, semis), extra...)
	}
	| _LOCAL identifier '=' _INT_LIT compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, $3, $4, $5, semis), extra...)
	}
	| _LOCAL identifier compactOptions semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($4)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, $3, semis), extra...)
	}
	// These are for lenient parsing of malformed declarations. The trick here is that
	// "export enum" is a valid prefix for enums with visibility or for a field named
	// "enum" in a pre-edition-2024 file where there's a type named "export". Same goes
	// for "local message" and the other permutations of visibility keyword and decl.
	// The semicolons production is for lenient parsing of declarations that are missing
	// semicolons. But we can't accept that for "export enum" -- we require a semicolon
	// in order to then parse it as a field (vs. an enum declaration). Hence the use of
	// semicolonList in some places.
	| _EXPORT nonDeclIdent semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, nil, semis), extra...)
	}
	| _EXPORT declIdent semicolonList {
		semis, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, nil, semis), extra...)
	}
	| _LOCAL nonDeclIdent semicolons {
		semis, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, nil, semis), extra...)
	}
	| _LOCAL declIdent semicolonList {
		semis, extra := protolex.(*protoLex).requireSemicolon($3)
		$$ = newNodeWithRunes(ast.NewFieldNode(nil, $1, $2, nil, nil, nil, semis), extra...)
	}


extensionDecl : _EXTEND typeName '{' extensionBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewExtendNode($1.ToKeyword(), $2, $3, $4, $5), $6...)
	}

extensionBody : {
		$$ = nil
	}
	| extensionElements

extensionElements : extensionElements extensionElement {
		if $2 != nil {
			$$ = append($1, $2)
		} else {
			$$ = $1
		}
	}
	| extensionElement {
		if $1 != nil {
			$$ = []ast.ExtendElement{$1}
		} else {
			$$ = nil
		}
	}

extensionElement : extensionFieldDecl {
		$$ = $1
	}
	| groupDecl {
		$$ = $1
	}
	| error ';' {
		$$ = nil
	}
	| error {
		$$ = nil
	}

extensionFieldDecl : fieldCardinality notGroupElementTypeIdent identifier '=' _INT_LIT semicolon {
		$$ = ast.NewFieldNode($1.ToKeyword(), $2, $3, $4, $5, nil, $6)
	}
	| fieldCardinality notGroupElementTypeIdent identifier '=' _INT_LIT compactOptions semicolon {
		$$ = ast.NewFieldNode($1.ToKeyword(), $2, $3, $4, $5, $6, $7)
	}
	| extElementTypeIdent identifier '=' _INT_LIT semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, $3, $4, nil, $5)
	}
	| extElementTypeIdent identifier '=' _INT_LIT compactOptions semicolon {
		$$ = ast.NewFieldNode(nil, $1, $2, $3, $4, $5, $6)
	}

serviceDecl : _SERVICE identifier '{' serviceBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewServiceNode($1.ToKeyword(), $2, $3, $4, $5), $6...)
	}

serviceBody : semicolons {
	  $$ = prependRunes(toServiceElement, $1, nil)
	}
	| semicolons serviceElements {
		$$ = prependRunes(toServiceElement, $1, $2)
	}

serviceElements : serviceElements serviceElement {
		$$ = append($1, $2...)
	}
	| serviceElement {
		$$ = $1
	}

// 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)
serviceElement : optionDecl {
		$$ = toElements[ast.ServiceElement](toServiceElement, $1.Node, $1.Runes)
	}
	| methodDecl {
		$$ = toElements[ast.ServiceElement](toServiceElement, $1.Node, $1.Runes)
	}
	| error {
		$$ = nil
	}

methodDecl : _RPC identifier methodMessageType _RETURNS methodMessageType semicolons {
	  semi, extra := protolex.(*protoLex).requireSemicolon($6)
		$$ = newNodeWithRunes(ast.NewRPCNode($1.ToKeyword(), $2, $3, $4.ToKeyword(), $5, semi), extra...)
	}
	| _RPC identifier methodMessageType _RETURNS methodMessageType '{' methodBody '}' semicolons {
		$$ = newNodeWithRunes(ast.NewRPCNodeWithBody($1.ToKeyword(), $2, $3, $4.ToKeyword(), $5, $6, $7, $8), $9...)
	}

methodMessageType : '(' _STREAM typeName ')' {
		$$ = ast.NewRPCTypeNode($1, $2.ToKeyword(), $3, $4)
	}
	| '(' mtdElementTypeIdent ')' {
		$$ = ast.NewRPCTypeNode($1, nil, $2, $3)
	}

methodBody : semicolons {
		$$ = prependRunes(toMethodElement, $1, nil)
	}
	| semicolons methodElements {
		$$ = prependRunes(toMethodElement, $1, $2)
	}

methodElements : methodElements methodElement {
		$$ = append($1, $2...)
	}
	| methodElement {
		$$ = $1
	}

methodElement : optionDecl {
		$$ = toElements[ast.RPCElement](toMethodElement, $1.Node, $1.Runes)
	}
	| error {
		$$ = nil
	}

// excludes message, enum, oneof, extensions, reserved, extend,
//   option, group, optional, required, and repeated
// NOTE: also excludes export, local, but not because they aren't
// allowed but because they require special handling to avoid
// conflicts in the parser
msgElementName : _NAME
	| _SYNTAX
	| _EDITION
	| _IMPORT
	| _WEAK
	| _PUBLIC
	| _PACKAGE
	| _TRUE
	| _FALSE
	| _INF
	| _NAN
	| _DOUBLE
	| _FLOAT
	| _INT32
	| _INT64
	| _UINT32
	| _UINT64
	| _SINT32
	| _SINT64
	| _FIXED32
	| _FIXED64
	| _SFIXED32
	| _SFIXED64
	| _BOOL
	| _STRING
	| _BYTES
	| _MAP
	| _TO
	| _MAX
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS


// excludes group, optional, required, and repeated
extElementName : _NAME
	| _SYNTAX
	| _EDITION
	| _IMPORT
	| _WEAK
	| _PUBLIC
	| _PACKAGE
	| _OPTION
	| _TRUE
	| _FALSE
	| _INF
	| _NAN
	| _DOUBLE
	| _FLOAT
	| _INT32
	| _INT64
	| _UINT32
	| _UINT64
	| _SINT32
	| _SINT64
	| _FIXED32
	| _FIXED64
	| _SFIXED32
	| _SFIXED64
	| _BOOL
	| _STRING
	| _BYTES
	| _ONEOF
	| _MAP
	| _EXTENSIONS
	| _TO
	| _MAX
	| _RESERVED
	| _ENUM
	| _MESSAGE
	| _EXTEND
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS
	| _EXPORT
	| _LOCAL

// excludes reserved, option
enumValueName : _NAME
	| _SYNTAX
	| _EDITION
	| _IMPORT
	| _WEAK
	| _PUBLIC
	| _PACKAGE
	| _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
	| _ENUM
	| _MESSAGE
	| _EXTEND
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS
	| _EXPORT
	| _LOCAL

// excludes group, option, optional, required, and repeated
oneofElementName : _NAME
	| _SYNTAX
	| _EDITION
	| _IMPORT
	| _WEAK
	| _PUBLIC
	| _PACKAGE
	| _TRUE
	| _FALSE
	| _INF
	| _NAN
	| _DOUBLE
	| _FLOAT
	| _INT32
	| _INT64
	| _UINT32
	| _UINT64
	| _SINT32
	| _SINT64
	| _FIXED32
	| _FIXED64
	| _SFIXED32
	| _SFIXED64
	| _BOOL
	| _STRING
	| _BYTES
	| _ONEOF
	| _MAP
	| _EXTENSIONS
	| _TO
	| _MAX
	| _RESERVED
	| _ENUM
	| _MESSAGE
	| _EXTEND
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS
	| _EXPORT
	| _LOCAL

// excludes group
notGroupElementName : _NAME
	| _SYNTAX
	| _EDITION
	| _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
	| _ONEOF
	| _MAP
	| _EXTENSIONS
	| _TO
	| _MAX
	| _RESERVED
	| _ENUM
	| _MESSAGE
	| _EXTEND
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS
	| _EXPORT
	| _LOCAL

// excludes stream
mtdElementName : _NAME
	| _SYNTAX
	| _EDITION
	| _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
	| _RETURNS
	| _EXPORT
	| _LOCAL

declIdent : _ENUM
	| _MESSAGE

// excludes message and enum, which are keywords that could otherwise
// follow "export" or "local" and confuse the grammar
nonDeclIdent : _NAME
	| _SYNTAX
	| _EDITION
	| _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
	| _EXTEND
	| _SERVICE
	| _RPC
	| _STREAM
	| _RETURNS
	| _EXPORT
	| _LOCAL

identifier : _NAME
	| _SYNTAX
	| _EDITION
	| _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
	| _EXPORT
	| _LOCAL

%%
