func (p *parser) readService(srv *ast.Service) *parseError { if err := p.readToken("service"); err != nil { return err } srv.Position = p.cur.astPosition() tok := p.next() if tok.err != nil { return tok.err } srv.Name = tok.value // TODO: validate if err := p.readToken("{"); err != nil { return err } // Parse methods for !p.done { tok := p.next() if tok.err != nil { return tok.err } switch tok.value { case "}": // end of service return nil case "rpc": // handled below default: return p.errorf(`got %q, want "rpc" or "}"`, tok.value) } tok = p.next() if tok.err != nil { return tok.err } mth := new(ast.Method) srv.Methods = append(srv.Methods, mth) mth.Position = tok.astPosition() mth.Name = tok.value // TODO: validate mth.Up = srv if err := p.readToken("("); err != nil { return err } tok = p.next() if tok.err != nil { return tok.err } mth.InTypeName = tok.value // TODO: validate if tok.value == "stream" { // If the next token isn't ")", this is a stream. tok = p.next() if tok.err != nil { return tok.err } if tok.value != ")" { mth.InTypeName = tok.value mth.ClientStreaming = true } else { p.back() } } if err := p.readToken(")"); err != nil { return err } if err := p.readToken("returns"); err != nil { return err } if err := p.readToken("("); err != nil { return err } tok = p.next() if tok.err != nil { return tok.err } mth.OutTypeName = tok.value // TODO: validate if tok.value == "stream" { // If the next token isn't ")", this is a stream. tok = p.next() if tok.err != nil { return tok.err } if tok.value != ")" { mth.OutTypeName = tok.value mth.ServerStreaming = true } else { p.back() } } if err := p.readToken(")"); err != nil { return err } if err := p.readToken(";"); err != nil { return err } } return p.errorf("unexpected EOF while parsing service") }
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 }