Skip to content

Commit 7588e1e

Browse files
Add enum to IDL and Schema (#54)
It is now possible to declare enums in the IDL. We will use the enums in a future PR to generate enum code.
1 parent c5c038a commit 7588e1e

File tree

5 files changed

+226
-10
lines changed

5 files changed

+226
-10
lines changed

go/pkg/idl/lexer.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"strconv"
89
"unicode"
910
)
1011

@@ -23,8 +24,9 @@ type Lexer struct {
2324
curPos Pos
2425
prevPos Pos
2526

26-
identRunes []rune
27+
tokenRunes []rune
2728
ident string
29+
uintNumber uint64
2830
}
2931

3032
// Pos indicates a position in the input stream.
@@ -46,6 +48,7 @@ const (
4648
tStruct
4749
tOneof
4850
tMultimap
51+
tEnum
4952

5053
tOptional
5154
tRoot
@@ -60,6 +63,9 @@ const (
6063
tString
6164
tBytes
6265

66+
tIntNumber
67+
68+
tAssign = '='
6369
tLBracket = '['
6470
tRBracket = ']'
6571
tLParen = '('
@@ -88,6 +94,7 @@ var keywords = map[string]Token{
8894
"struct": tStruct,
8995
"oneof": tOneof,
9096
"multimap": tMultimap,
97+
"enum": tEnum,
9198
"optional": tOptional,
9299
"root": tRoot,
93100
"dict": tDict,
@@ -151,6 +158,8 @@ func (l *Lexer) Next() {
151158
}
152159

153160
switch l.nextRune {
161+
case tAssign:
162+
l.token = tAssign
154163
case tLParen:
155164
l.token = tLParen
156165
case tRParen:
@@ -168,6 +177,10 @@ func (l *Lexer) Next() {
168177
// This is a letter. It must a start of an identifier or keyword.
169178
l.readIdentOrKeyword()
170179
return
180+
} else if isDigit(l.nextRune) {
181+
// This is a digit. It must be a number.
182+
l.readUint64Number()
183+
return
171184
}
172185
l.token = tError
173186
l.errMsg = fmt.Sprintf("invalid character: %c", l.nextRune)
@@ -234,12 +247,12 @@ func (l *Lexer) readNextRune() {
234247
}
235248

236249
func (l *Lexer) readIdentOrKeyword() Token {
237-
l.identRunes = l.identRunes[:0]
250+
l.tokenRunes = l.tokenRunes[:0]
238251

239252
// The first character is already read. Subsequent characters must be
240253
// letters, digits or underscore.
241254
for (unicode.IsLetter(l.nextRune) || unicode.IsDigit(l.nextRune) || l.nextRune == '_') && !l.isError {
242-
l.identRunes = append(l.identRunes, l.nextRune)
255+
l.tokenRunes = append(l.tokenRunes, l.nextRune)
243256
l.readNextRune()
244257
if l.isEOF {
245258
break
@@ -250,7 +263,7 @@ func (l *Lexer) readIdentOrKeyword() Token {
250263
}
251264
}
252265

253-
l.ident = string(l.identRunes)
266+
l.ident = string(l.tokenRunes)
254267

255268
// Check if it is a keyword.
256269
if token, ok := keywords[l.ident]; ok {
@@ -273,3 +286,48 @@ func (l *Lexer) Ident() string {
273286
func (l *Lexer) TokenStartPos() Pos {
274287
return l.prevPos
275288
}
289+
290+
func (l *Lexer) Uint64Number() uint64 {
291+
return l.uintNumber
292+
}
293+
294+
func isDigit(r rune) bool {
295+
return r >= '0' && r <= '9'
296+
}
297+
298+
func isNumberContinuation(r rune) bool {
299+
return isDigit(r) || r == '_' || r == 'b' || r == 'x' || r == 'o' || r == 'B' || r == 'X' || r == 'O'
300+
}
301+
302+
func (l *Lexer) readUint64Number() {
303+
l.tokenRunes = l.tokenRunes[:0]
304+
305+
// The first character is already read.
306+
307+
for {
308+
if l.isError {
309+
l.token = tError
310+
return
311+
}
312+
l.tokenRunes = append(l.tokenRunes, l.nextRune)
313+
l.readNextRune()
314+
if l.isEOF || !isNumberContinuation(l.nextRune) {
315+
break
316+
}
317+
}
318+
319+
// This correctly parses decimal, hexadecimal, octal and binary numbers.
320+
val, err := strconv.ParseUint(string(l.tokenRunes), 0, 64)
321+
if err != nil {
322+
l.token = tError
323+
l.errMsg = fmt.Sprintf("invalid number: %s", string(l.tokenRunes))
324+
return
325+
}
326+
327+
l.uintNumber = val
328+
l.token = tIntNumber
329+
}
330+
331+
func (l *Lexer) ErrMsg() string {
332+
return l.errMsg
333+
}

go/pkg/idl/parser.go

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func (p *Parser) Parse() error {
4848
p.schema = &schema.Schema{
4949
Structs: map[string]*schema.Struct{},
5050
Multimaps: map[string]*schema.Multimap{},
51+
Enums: map[string]*schema.Enum{},
5152
}
5253

5354
if err := p.parsePackage(); err != nil {
@@ -63,6 +64,8 @@ func (p *Parser) Parse() error {
6364
err = p.parseOneof()
6465
case tMultimap:
6566
err = p.parseMultimap()
67+
case tEnum:
68+
err = p.parseEnum()
6669
default:
6770
return p.error("expected struct, oneof or multimap")
6871
}
@@ -76,13 +79,22 @@ func (p *Parser) Parse() error {
7679
return p.resolveFieldTypes()
7780
}
7881

82+
func (p *Parser) isTopLevelNameUsed(name string) bool {
83+
return p.schema.Structs[name] != nil || p.schema.Multimaps[name] != nil || p.schema.Enums[name] != nil
84+
}
85+
7986
func (p *Parser) parseStruct() (*schema.Struct, error) {
8087
p.lexer.Next() // skip "struct"
8188

8289
if p.lexer.Token() != tIdent {
8390
return nil, p.error("struct name expected")
8491
}
8592
structName := p.lexer.Ident()
93+
94+
if p.isTopLevelNameUsed(structName) {
95+
return nil, p.error("duplicate top-level identifier: " + structName)
96+
}
97+
8698
p.lexer.Next()
8799

88100
str := &schema.Struct{
@@ -126,6 +138,11 @@ func (p *Parser) parseMultimap() error {
126138
return p.error("multimap name expected")
127139
}
128140
multimapName := p.lexer.Ident()
141+
142+
if p.isTopLevelNameUsed(multimapName) {
143+
return p.error("duplicate top-level identifier: " + multimapName)
144+
}
145+
129146
p.lexer.Next()
130147

131148
mm := &schema.Multimap{
@@ -387,11 +404,36 @@ func (p *Parser) resolveFieldTypes() error {
387404
}
388405

389406
func (p *Parser) resolveFieldType(fieldType *schema.FieldType) error {
390-
if fieldType.Struct != "" {
391-
_, ok := p.schema.Multimaps[fieldType.Struct]
392-
if ok {
393-
fieldType.MultiMap = fieldType.Struct
407+
typeName := fieldType.Struct
408+
if typeName != "" {
409+
matches := 0
410+
_, isStruct := p.schema.Structs[typeName]
411+
if isStruct {
412+
matches++
413+
}
414+
415+
_, isMultimap := p.schema.Multimaps[typeName]
416+
if isMultimap {
417+
fieldType.MultiMap = typeName
418+
fieldType.Struct = ""
419+
matches++
420+
}
421+
422+
_, isEnum := p.schema.Enums[typeName]
423+
if isEnum {
424+
// All enums are uint64.
425+
t := schema.PrimitiveTypeUint64
426+
fieldType.Primitive = &t
427+
fieldType.Enum = typeName
394428
fieldType.Struct = ""
429+
matches++
430+
}
431+
432+
if matches == 0 {
433+
return p.error("unknown type: " + typeName)
434+
}
435+
if matches > 1 {
436+
return p.error("ambiguous type: " + typeName)
395437
}
396438
}
397439
return nil
@@ -409,3 +451,78 @@ func (p *Parser) parsePackage() error {
409451
}
410452
return nil
411453
}
454+
455+
func (p *Parser) parseEnum() error {
456+
p.lexer.Next() // skip "enum"
457+
458+
if p.lexer.Token() != tIdent {
459+
return p.error("enum name expected")
460+
}
461+
enumName := p.lexer.Ident()
462+
463+
if p.isTopLevelNameUsed(enumName) {
464+
return p.error("duplicate top-level identifier: " + enumName)
465+
}
466+
467+
p.lexer.Next()
468+
469+
enum := &schema.Enum{
470+
Name: enumName,
471+
}
472+
p.schema.Enums[enum.Name] = enum
473+
474+
if err := p.eat(tLBrace); err != nil {
475+
return err
476+
}
477+
478+
if err := p.parseEnumFields(enum); err != nil {
479+
return err
480+
}
481+
482+
if err := p.eat(tRBrace); err != nil {
483+
return err
484+
}
485+
486+
return nil
487+
}
488+
489+
func (p *Parser) parseEnumFields(enum *schema.Enum) error {
490+
for {
491+
err, ok := p.parseEnumField(enum)
492+
if err != nil {
493+
return err
494+
}
495+
if !ok {
496+
break
497+
}
498+
}
499+
return nil
500+
}
501+
502+
func (p *Parser) parseEnumField(enum *schema.Enum) (error, bool) {
503+
if p.lexer.Token() != tIdent {
504+
return nil, false
505+
}
506+
507+
enum.Fields = append(enum.Fields, schema.EnumField{Name: p.lexer.Ident()})
508+
field := &enum.Fields[len(enum.Fields)-1]
509+
510+
p.lexer.Next() // skip field name
511+
512+
if err := p.eat(tAssign); err != nil {
513+
return err, false
514+
}
515+
516+
if p.lexer.Token() != tIntNumber {
517+
errMsg := "enum field value expected"
518+
if p.lexer.Token() == tError {
519+
errMsg += ": " + p.lexer.ErrMsg()
520+
}
521+
return p.error(errMsg), false
522+
}
523+
524+
field.Value = p.lexer.Uint64Number()
525+
p.lexer.Next()
526+
527+
return nil, true
528+
}

go/pkg/idl/parser_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ func TestParserErrors(t *testing.T) {
4444
input: "package abc\nstruct MyStruct {\nField []struct",
4545
err: "test.stef:3:10: type specifier expected after []",
4646
},
47+
{
48+
input: "package abc\nstruct MyStruct {\nField UnknownType }",
49+
err: "test.stef:3:20: unknown type: UnknownType",
50+
},
51+
{
52+
input: "package abc oneof A {} struct A {}",
53+
err: "test.stef:1:31: duplicate top-level identifier: A",
54+
},
55+
{
56+
input: "package abc enum {}",
57+
err: "test.stef:1:18: enum name expected",
58+
},
59+
{
60+
input: "package abc enum Enum { Value = }",
61+
err: "test.stef:1:33: enum field value expected",
62+
},
4763
}
4864

4965
for _, test := range tests {
@@ -79,7 +95,11 @@ func TestParserOtelSTEF(t *testing.T) {
7995
jsonBytes, err := os.ReadFile("testdata/oteltef.wire.json")
8096
require.NoError(t, err)
8197

82-
var schem schema.Schema
98+
schem := schema.Schema{
99+
Structs: map[string]*schema.Struct{},
100+
Multimaps: map[string]*schema.Multimap{},
101+
Enums: map[string]*schema.Enum{},
102+
}
83103
err = json.Unmarshal(jsonBytes, &schem)
84104
require.NoError(t, err)
85105

go/pkg/idl/testdata/example.stef

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ struct Book {
88
Title string // The title of the book.
99
PublishedOn Date // When was it published.
1010
Publisher string dict(Publisher) // Publishers name, encoded with a dict.
11+
Category Category
1112
Authors []Person // Zero or more authors of the book.
1213
}
1314

15+
enum Category {
16+
Fiction = 1
17+
NonFiction = 2
18+
HexMystery = 0x3
19+
OctalMystery = 0o4
20+
BinaryMystery = 0b101
21+
}
22+
1423
// BookEvent describes either a checkout or a checkin event.
1524
oneof BookEvent {
1625
Checkout CheckoutEvent

go/pkg/schema/schema.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type Schema struct {
99
PackageName string `json:"package,omitempty"`
1010
Structs map[string]*Struct `json:"structs"`
1111
Multimaps map[string]*Multimap `json:"multimaps"`
12+
Enums map[string]*Enum
1213
}
1314

1415
type Compatibility int
@@ -301,7 +302,8 @@ type FieldType struct {
301302
Array *FieldType `json:"array,omitempty"`
302303
Struct string `json:"struct,omitempty"`
303304
MultiMap string `json:"multimap,omitempty"`
304-
DictName string `json:"dict,omitempty"`
305+
Enum string
306+
DictName string `json:"dict,omitempty"`
305307
}
306308

307309
type MultimapField struct {
@@ -313,3 +315,13 @@ type Multimap struct {
313315
Key MultimapField `json:"key"`
314316
Value MultimapField `json:"value"`
315317
}
318+
319+
type Enum struct {
320+
Name string
321+
Fields []EnumField
322+
}
323+
324+
type EnumField struct {
325+
Name string
326+
Value uint64
327+
}

0 commit comments

Comments
 (0)