func (p *parser) readEnum(enum *ast.Enum) *parseError { if err := p.readToken("enum"); err != nil { return err } enum.Position = p.cur.astPosition() tok := p.next() if tok.err != nil { return tok.err } enum.Name = tok.value // TODO: validate if err := p.readToken("{"); err != nil { return err } // Parse enum values for !p.done { tok := p.next() if tok.err != nil { return tok.err } if tok.value == "}" { // end of enum // A semicolon after an enum is optional. if err := p.readToken(";"); err != nil { p.back() } return nil } // TODO: verify tok.value is a valid enum value name. ev := new(ast.EnumValue) enum.Values = append(enum.Values, ev) ev.Position = tok.astPosition() ev.Name = tok.value // TODO: validate ev.Up = enum if err := p.readToken("="); err != nil { return err } tok = p.next() if tok.err != nil { return tok.err } // TODO: check that tok.value is a valid enum value number. num, err := strconv.ParseInt(tok.value, 10, 32) if err != nil { return p.errorf("bad enum number %q: %v", tok.value, err) } ev.Number = int32(num) // TODO: validate if err := p.readToken(";"); err != nil { return err } } return p.errorf("unexpected EOF while parsing enum") }
func (p *parser) readMessageContents(msg *ast.Message) *parseError { // Parse message fields and other things inside a message. var oneof *ast.Oneof // set while inside a oneof for !p.done { tok := p.next() if tok.err != nil { return tok.err } switch tok.value { case "extend": // extension p.back() ext := new(ast.Extension) msg.Extensions = append(msg.Extensions, ext) if err := p.readExtension(ext); err != nil { return err } ext.Up = msg case "oneof": // oneof if oneof != nil { return p.errorf("nested oneof not permitted") } oneof = new(ast.Oneof) msg.Oneofs = append(msg.Oneofs, oneof) oneof.Position = p.cur.astPosition() tok := p.next() if tok.err != nil { return tok.err } oneof.Name = tok.value // TODO: validate oneof.Up = msg if err := p.readToken("{"); err != nil { return err } case "message": // nested message p.back() nmsg := new(ast.Message) msg.Messages = append(msg.Messages, nmsg) if err := p.readMessage(nmsg); err != nil { return err } nmsg.Up = msg case "enum": // nested enum p.back() ne := new(ast.Enum) msg.Enums = append(msg.Enums, ne) if err := p.readEnum(ne); err != nil { return err } ne.Up = msg case "extensions": // extension range p.back() r, err := p.readExtensionRange() if err != nil { return err } msg.ExtensionRanges = append(msg.ExtensionRanges, r...) default: // field; this token is required/optional/repeated, // a primitive type, or a named type. p.back() field := new(ast.Field) msg.Fields = append(msg.Fields, field) field.Oneof = oneof field.Up = msg // p.readField uses this if err := p.readField(field); err != nil { return err } case "}": if oneof != nil { // end of oneof oneof = nil continue } // end of message p.back() return nil } } return p.errorf("unexpected EOF while parsing message") }
func (p *parser) readFile(f *ast.File) *parseError { // Parse top-level things. for !p.done { tok := p.next() if tok.err == eof { break } else if tok.err != nil { return tok.err } // TODO: enforce ordering? package, imports, remainder switch tok.value { case "package": if f.Package != nil { return p.errorf("duplicate package statement") } var pkg string for { tok := p.next() if tok.err != nil { return tok.err } if tok.value == ";" { break } if tok.value == "." { // okay if we already have at least one package component, // and didn't just read a dot. if pkg == "" || strings.HasSuffix(pkg, ".") { return p.errorf(`got ".", want package name`) } } else { // okay if we don't have a package component, // or just read a dot. if pkg != "" && !strings.HasSuffix(pkg, ".") { return p.errorf(`got %q, want "." or ";"`, tok.value) } // TODO: validate more } pkg += tok.value } f.Package = strings.Split(pkg, ".") case "option": tok := p.next() if tok.err != nil { return tok.err } key := tok.value if err := p.readToken("="); err != nil { return err } tok = p.next() if tok.err != nil { return tok.err } value := tok.value if err := p.readToken(";"); err != nil { return err } f.Options = append(f.Options, [2]string{key, value}) case "syntax": if f.Syntax != "" { return p.errorf("duplicate syntax statement") } if err := p.readToken("="); err != nil { return err } tok, err := p.readString() if err != nil { return err } switch s := tok.unquoted; s { case "proto2", "proto3": f.Syntax = s default: return p.errorf("invalid syntax value %q", s) } if err := p.readToken(";"); err != nil { return err } case "import": if err := p.readToken("public"); err == nil { f.PublicImports = append(f.PublicImports, len(f.Imports)) } else { p.back() } tok, err := p.readString() if err != nil { return err } f.Imports = append(f.Imports, tok.unquoted) if err := p.readToken(";"); err != nil { return err } case "message": p.back() msg := new(ast.Message) f.Messages = append(f.Messages, msg) if err := p.readMessage(msg); err != nil { return err } msg.Up = f case "enum": p.back() enum := new(ast.Enum) f.Enums = append(f.Enums, enum) if err := p.readEnum(enum); err != nil { return err } enum.Up = f case "service": p.back() srv := new(ast.Service) f.Services = append(f.Services, srv) if err := p.readService(srv); err != nil { return err } srv.Up = f case "extend": p.back() ext := new(ast.Extension) f.Extensions = append(f.Extensions, ext) if err := p.readExtension(ext); err != nil { return err } ext.Up = f default: return p.errorf("unknown top-level thing %q", tok.value) } } // Handle comments. for len(p.comments) > 0 { n := 1 for ; n < len(p.comments); n++ { if p.comments[n].line != p.comments[n-1].line+1 { break } } c := &ast.Comment{ Start: ast.Position{ Line: p.comments[0].line, Offset: p.comments[0].offset, }, End: ast.Position{ Line: p.comments[n-1].line, Offset: p.comments[n-1].offset, }, } for _, comm := range p.comments[:n] { c.Text = append(c.Text, comm.text) } p.comments = p.comments[n:] // Strip common whitespace prefix and any whitespace suffix. // TODO: this is a bodgy implementation of Longest Common Prefix, // and also doesn't do tabs vs. spaces well. var prefix string for i, line := range c.Text { line = strings.TrimRightFunc(line, unicode.IsSpace) c.Text[i] = line trim := len(line) - len(strings.TrimLeftFunc(line, unicode.IsSpace)) if i == 0 { prefix = line[:trim] } else { // Check how much of prefix is in common. for !strings.HasPrefix(line, prefix) { prefix = prefix[:len(prefix)-1] } } if prefix == "" { break } } if prefix != "" { for i, line := range c.Text { c.Text[i] = strings.TrimPrefix(line, prefix) } } f.Comments = append(f.Comments, c) } // No need to sort comments; they are already in source order. return nil }