cmd/protoc-gen-go-grpc: add gRPC code generator

This is a straight translation of the v1 API gRPC "plugin" to protogen.

Add a protoc-gen-go-grpc command. The preferred way to generate gRPC
services is to invoke both plugins separately:

  protoc --go_out=. --go-grpc_out=. foo.proto

When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.

Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
Reviewed-on: https://go-review.googlesource.com/137037
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/protogen/protogen.go b/protogen/protogen.go
index 03a2ff0..87e643c 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -349,6 +349,7 @@
 	Messages      []*Message    // top-level message declarations
 	Enums         []*Enum       // top-level enum declarations
 	Extensions    []*Extension  // top-level extension declarations
+	Services      []*Service    // top-level service declarations
 	Generate      bool          // true if we should generate code for this file
 
 	// GeneratedFilenamePrefix is used to construct filenames for generated
@@ -401,6 +402,9 @@
 	for i, extdescs := 0, desc.Extensions(); i < extdescs.Len(); i++ {
 		f.Extensions = append(f.Extensions, newField(gen, f, nil, extdescs.Get(i)))
 	}
+	for i, sdescs := 0, desc.Services(); i < sdescs.Len(); i++ {
+		f.Services = append(f.Services, newService(gen, f, sdescs.Get(i)))
+	}
 	for _, message := range f.Messages {
 		if err := message.init(gen); err != nil {
 			return nil, err
@@ -411,6 +415,13 @@
 			return nil, err
 		}
 	}
+	for _, service := range f.Services {
+		for _, method := range service.Methods {
+			if err := method.init(gen); err != nil {
+				return nil, err
+			}
+		}
+	}
 	return f, nil
 }
 
@@ -723,6 +734,68 @@
 	return g
 }
 
+// A Service describes a service.
+type Service struct {
+	Desc protoreflect.ServiceDescriptor
+
+	GoName  string
+	Path    []int32   // location path of this service
+	Methods []*Method // service method definitions
+}
+
+func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
+	service := &Service{
+		Desc:   desc,
+		GoName: camelCase(string(desc.Name())),
+		Path:   []int32{fileServiceField, int32(desc.Index())},
+	}
+	for i, mdescs := 0, desc.Methods(); i < mdescs.Len(); i++ {
+		service.Methods = append(service.Methods, newMethod(gen, f, service, mdescs.Get(i)))
+	}
+	return service
+}
+
+// A Method describes a method in a service.
+type Method struct {
+	Desc protoreflect.MethodDescriptor
+
+	GoName        string
+	ParentService *Service
+	Path          []int32 // location path of this method
+	InputType     *Message
+	OutputType    *Message
+}
+
+func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method {
+	method := &Method{
+		Desc:          desc,
+		GoName:        camelCase(string(desc.Name())),
+		ParentService: service,
+		Path:          pathAppend(service.Path, serviceMethodField, int32(desc.Index())),
+	}
+	return method
+}
+
+func (method *Method) init(gen *Plugin) error {
+	desc := method.Desc
+
+	inName := desc.InputType().FullName()
+	in, ok := gen.messagesByName[inName]
+	if !ok {
+		return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName)
+	}
+	method.InputType = in
+
+	outName := desc.OutputType().FullName()
+	out, ok := gen.messagesByName[outName]
+	if !ok {
+		return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName)
+	}
+	method.OutputType = out
+
+	return nil
+}
+
 // P prints a line to the generated output. It converts each parameter to a
 // string following the same rules as fmt.Print. It never inserts spaces
 // between parameters.
@@ -843,6 +916,7 @@
 	filePackageField   = 2 // package
 	fileMessageField   = 4 // message_type
 	fileEnumField      = 5 // enum_type
+	fileServiceField   = 6 // service
 	fileExtensionField = 7 // extension
 	// field numbers in DescriptorProto
 	messageFieldField     = 2 // field
@@ -852,6 +926,9 @@
 	messageOneofField     = 8 // oneof_decl
 	// field numbers in EnumDescriptorProto
 	enumValueField = 2 // value
+	// field numbers in ServiceDescriptorProto
+	serviceMethodField = 2 // method
+	serviceStreamField = 4 // stream
 )
 
 // pathAppend appends elements to a location path.