Skip to content

Commit

Permalink
Editions uses identifiers instead of string literals for reserved nam…
Browse files Browse the repository at this point in the history
…es (#176)

This implements the use of identifiers, instead of string literals, in
`reserved` statements. This new syntax is for editions; proto2 and
proto3 continue to use string literals for backwards compatibility.

This effectively ports protocolbuffers/protobuf#13471
to protocompile.
  • Loading branch information
jhump authored Aug 23, 2023
1 parent c2470f7 commit dbf43b6
Show file tree
Hide file tree
Showing 6 changed files with 604 additions and 408 deletions.
57 changes: 55 additions & 2 deletions ast/ranges.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,20 @@ func (n *RangeNode) EndValueAsInt32(min, max int32) (int32, bool) {
//
// reserved 1, 10-12, 15;
// reserved "foo", "bar", "baz";
// reserved foo, bar, baz;
type ReservedNode struct {
compositeNode
Keyword *KeywordNode
// If non-empty, this node represents reserved ranges and Names will be empty.
// If non-empty, this node represents reserved ranges, and Names and Identifiers
// will be empty.
Ranges []*RangeNode
// If non-empty, this node represents reserved names and Ranges will be empty.
// If non-empty, this node represents reserved names as string literals, and
// Ranges and Identifiers will be empty. String literals are used for reserved
// names in proto2 and proto3 syntax.
Names []StringValueNode
// If non-empty, this node represents reserved names as identifiers, and Ranges
// and Names will be empty. Identifiers are used for reserved names in editions.
Identifiers []*IdentNode
// Commas represent the separating ',' characters between options. The
// length of this slice must be exactly len(Ranges)-1 or len(Names)-1, depending
// on whether this node represents reserved ranges or reserved names. Each item
Expand Down Expand Up @@ -317,3 +324,49 @@ func NewReservedNamesNode(keyword *KeywordNode, names []StringValueNode, commas
Semicolon: semicolon,
}
}

// NewReservedIdentifiersNode creates a new *ReservedNode that represents reserved
// names. All args must be non-nil.
// - keyword: The token corresponding to the "reserved" keyword.
// - names: One or more names.
// - commas: Tokens that represent the "," runes that delimit the names.
// The length of commas must be one less than the length of names.
// - semicolon The token corresponding to the ";" rune that ends the declaration.
func NewReservedIdentifiersNode(keyword *KeywordNode, names []*IdentNode, commas []*RuneNode, semicolon *RuneNode) *ReservedNode {
if keyword == nil {
panic("keyword is nil")
}
if semicolon == nil {
panic("semicolon is nil")
}
if len(names) == 0 {
panic("must have at least one name")
}
if len(commas) != len(names)-1 {
panic(fmt.Sprintf("%d names requires %d commas, not %d", len(names), len(names)-1, len(commas)))
}
children := make([]Node, 0, len(names)*2+1)
children = append(children, keyword)
for i, name := range names {
if i > 0 {
if commas[i-1] == nil {
panic(fmt.Sprintf("commas[%d] is nil", i-1))
}
children = append(children, commas[i-1])
}
if name == nil {
panic(fmt.Sprintf("names[%d] is nil", i))
}
children = append(children, name)
}
children = append(children, semicolon)
return &ReservedNode{
compositeNode: compositeNode{
children: children,
},
Keyword: keyword,
Identifiers: names,
Commas: commas,
Semicolon: semicolon,
}
}
2 changes: 2 additions & 0 deletions parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ func toStringValueNode(strs []*ast.StringLiteralNode) ast.StringValueNode {
}

type nameSlices struct {
// only names or idents will be set, never both
names []ast.StringValueNode
idents []*ast.IdentNode
commas []*ast.RuneNode
}

Expand Down
20 changes: 16 additions & 4 deletions parser/proto.y
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ import (
%type <msgElements> messageElements messageBody
%type <ooElement> oneofElement
%type <ooElements> oneofElements oneofBody
%type <names> fieldNames
%type <names> fieldNameStrings fieldNameIdents
%type <resvd> msgReserved enumReserved reservedNames
%type <rng> tagRange enumValueRange
%type <rngs> tagRanges enumValueRanges
Expand Down Expand Up @@ -737,19 +737,31 @@ enumReserved : _RESERVED enumValueRanges ';' {
}
| reservedNames

reservedNames : _RESERVED fieldNames ';' {
reservedNames : _RESERVED fieldNameStrings ';' {
$$ = ast.NewReservedNamesNode($1.ToKeyword(), $2.names, $2.commas, $3)
}
| _RESERVED fieldNameIdents ';' {
$$ = ast.NewReservedIdentifiersNode($1.ToKeyword(), $2.idents, $2.commas, $3)
}

fieldNames : stringLit {
fieldNameStrings : stringLit {
$$ = &nameSlices{names: []ast.StringValueNode{toStringValueNode($1)}}
}
| fieldNames ',' stringLit {
| 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 '}' {
$$ = ast.NewEnumNode($1.ToKeyword(), $2, $3, $4, $5)
}
Expand Down
Loading

0 comments on commit dbf43b6

Please sign in to comment.