• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 #include <sstream>
32 
33 #include <google/protobuf/compiler/code_generator.h>
34 #include <google/protobuf/compiler/plugin.h>
35 #include <google/protobuf/descriptor.h>
36 #include <google/protobuf/descriptor.pb.h>
37 #include <google/protobuf/io/printer.h>
38 #include <google/protobuf/io/zero_copy_stream.h>
39 
40 #include <google/protobuf/compiler/ruby/ruby_generator.h>
41 
42 using google::protobuf::internal::scoped_ptr;
43 
44 namespace google {
45 namespace protobuf {
46 namespace compiler {
47 namespace ruby {
48 
49 // Forward decls.
50 std::string IntToString(int32 value);
51 std::string GetRequireName(const std::string& proto_file);
52 std::string LabelForField(google::protobuf::FieldDescriptor* field);
53 std::string TypeName(google::protobuf::FieldDescriptor* field);
54 void GenerateMessage(const google::protobuf::Descriptor* message,
55                      google::protobuf::io::Printer* printer);
56 void GenerateEnum(const google::protobuf::EnumDescriptor* en,
57                   google::protobuf::io::Printer* printer);
58 void GenerateMessageAssignment(
59     const std::string& prefix,
60     const google::protobuf::Descriptor* message,
61     google::protobuf::io::Printer* printer);
62 void GenerateEnumAssignment(
63     const std::string& prefix,
64     const google::protobuf::EnumDescriptor* en,
65     google::protobuf::io::Printer* printer);
66 
IntToString(int32 value)67 std::string IntToString(int32 value) {
68   std::ostringstream os;
69   os << value;
70   return os.str();
71 }
72 
GetRequireName(const std::string & proto_file)73 std::string GetRequireName(const std::string& proto_file) {
74   int lastindex = proto_file.find_last_of(".");
75   return proto_file.substr(0, lastindex) + "_pb";
76 }
77 
GetOutputFilename(const std::string & proto_file)78 std::string GetOutputFilename(const std::string& proto_file) {
79   return GetRequireName(proto_file) + ".rb";
80 }
81 
LabelForField(const google::protobuf::FieldDescriptor * field)82 std::string LabelForField(const google::protobuf::FieldDescriptor* field) {
83   switch (field->label()) {
84     case FieldDescriptor::LABEL_OPTIONAL: return "optional";
85     case FieldDescriptor::LABEL_REQUIRED: return "required";
86     case FieldDescriptor::LABEL_REPEATED: return "repeated";
87     default: assert(false); return "";
88   }
89 }
90 
TypeName(const google::protobuf::FieldDescriptor * field)91 std::string TypeName(const google::protobuf::FieldDescriptor* field) {
92   switch (field->type()) {
93     case FieldDescriptor::TYPE_INT32: return "int32";
94     case FieldDescriptor::TYPE_INT64: return "int64";
95     case FieldDescriptor::TYPE_UINT32: return "uint32";
96     case FieldDescriptor::TYPE_UINT64: return "uint64";
97     case FieldDescriptor::TYPE_SINT32: return "sint32";
98     case FieldDescriptor::TYPE_SINT64: return "sint64";
99     case FieldDescriptor::TYPE_FIXED32: return "fixed32";
100     case FieldDescriptor::TYPE_FIXED64: return "fixed64";
101     case FieldDescriptor::TYPE_SFIXED32: return "sfixed32";
102     case FieldDescriptor::TYPE_SFIXED64: return "sfixed64";
103     case FieldDescriptor::TYPE_DOUBLE: return "double";
104     case FieldDescriptor::TYPE_FLOAT: return "float";
105     case FieldDescriptor::TYPE_BOOL: return "bool";
106     case FieldDescriptor::TYPE_ENUM: return "enum";
107     case FieldDescriptor::TYPE_STRING: return "string";
108     case FieldDescriptor::TYPE_BYTES: return "bytes";
109     case FieldDescriptor::TYPE_MESSAGE: return "message";
110     case FieldDescriptor::TYPE_GROUP: return "group";
111     default: assert(false); return "";
112   }
113 }
114 
GenerateField(const google::protobuf::FieldDescriptor * field,google::protobuf::io::Printer * printer)115 void GenerateField(const google::protobuf::FieldDescriptor* field,
116                    google::protobuf::io::Printer* printer) {
117 
118   if (field->is_map()) {
119     const FieldDescriptor* key_field =
120         field->message_type()->FindFieldByNumber(1);
121     const FieldDescriptor* value_field =
122         field->message_type()->FindFieldByNumber(2);
123 
124     printer->Print(
125       "map :$name$, :$key_type$, :$value_type$, $number$",
126       "name", field->name(),
127       "key_type", TypeName(key_field),
128       "value_type", TypeName(value_field),
129       "number", IntToString(field->number()));
130 
131     if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
132       printer->Print(
133         ", \"$subtype$\"\n",
134         "subtype", value_field->message_type()->full_name());
135     } else if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
136       printer->Print(
137         ", \"$subtype$\"\n",
138         "subtype", value_field->enum_type()->full_name());
139     } else {
140       printer->Print("\n");
141     }
142   } else {
143 
144     printer->Print(
145       "$label$ :$name$, ",
146       "label", LabelForField(field),
147       "name", field->name());
148     printer->Print(
149       ":$type$, $number$",
150       "type", TypeName(field),
151       "number", IntToString(field->number()));
152 
153     if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
154       printer->Print(
155         ", \"$subtype$\"\n",
156        "subtype", field->message_type()->full_name());
157     } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
158       printer->Print(
159         ", \"$subtype$\"\n",
160         "subtype", field->enum_type()->full_name());
161     } else {
162       printer->Print("\n");
163     }
164   }
165 }
166 
GenerateOneof(const google::protobuf::OneofDescriptor * oneof,google::protobuf::io::Printer * printer)167 void GenerateOneof(const google::protobuf::OneofDescriptor* oneof,
168                    google::protobuf::io::Printer* printer) {
169   printer->Print(
170       "oneof :$name$ do\n",
171       "name", oneof->name());
172   printer->Indent();
173 
174   for (int i = 0; i < oneof->field_count(); i++) {
175     const FieldDescriptor* field = oneof->field(i);
176     GenerateField(field, printer);
177   }
178 
179   printer->Outdent();
180   printer->Print("end\n");
181 }
182 
GenerateMessage(const google::protobuf::Descriptor * message,google::protobuf::io::Printer * printer)183 void GenerateMessage(const google::protobuf::Descriptor* message,
184                      google::protobuf::io::Printer* printer) {
185 
186   // Don't generate MapEntry messages -- we use the Ruby extension's native
187   // support for map fields instead.
188   if (message->options().map_entry()) {
189     return;
190   }
191 
192   printer->Print(
193     "add_message \"$name$\" do\n",
194     "name", message->full_name());
195   printer->Indent();
196 
197   for (int i = 0; i < message->field_count(); i++) {
198     const FieldDescriptor* field = message->field(i);
199     if (!field->containing_oneof()) {
200       GenerateField(field, printer);
201     }
202   }
203 
204   for (int i = 0; i < message->oneof_decl_count(); i++) {
205     const OneofDescriptor* oneof = message->oneof_decl(i);
206     GenerateOneof(oneof, printer);
207   }
208 
209   printer->Outdent();
210   printer->Print("end\n");
211 
212   for (int i = 0; i < message->nested_type_count(); i++) {
213     GenerateMessage(message->nested_type(i), printer);
214   }
215   for (int i = 0; i < message->enum_type_count(); i++) {
216     GenerateEnum(message->enum_type(i), printer);
217   }
218 }
219 
GenerateEnum(const google::protobuf::EnumDescriptor * en,google::protobuf::io::Printer * printer)220 void GenerateEnum(const google::protobuf::EnumDescriptor* en,
221                   google::protobuf::io::Printer* printer) {
222   printer->Print(
223     "add_enum \"$name$\" do\n",
224     "name", en->full_name());
225   printer->Indent();
226 
227   for (int i = 0; i < en->value_count(); i++) {
228     const EnumValueDescriptor* value = en->value(i);
229     printer->Print(
230       "value :$name$, $number$\n",
231       "name", value->name(),
232       "number", IntToString(value->number()));
233   }
234 
235   printer->Outdent();
236   printer->Print(
237     "end\n");
238 }
239 
240 // Locale-agnostic utility functions.
IsLower(char ch)241 bool IsLower(char ch) { return ch >= 'a' && ch <= 'z'; }
242 
IsUpper(char ch)243 bool IsUpper(char ch) { return ch >= 'A' && ch <= 'Z'; }
244 
IsAlpha(char ch)245 bool IsAlpha(char ch) { return IsLower(ch) || IsUpper(ch); }
246 
ToUpper(char ch)247 char ToUpper(char ch) { return IsLower(ch) ? (ch - 'a' + 'A') : ch; }
248 
249 
250 // Package names in protobuf are snake_case by convention, but Ruby module
251 // names must be PascalCased.
252 //
253 //   foo_bar_baz -> FooBarBaz
PackageToModule(const std::string & name)254 std::string PackageToModule(const std::string& name) {
255   bool next_upper = true;
256   std::string result;
257   result.reserve(name.size());
258 
259   for (int i = 0; i < name.size(); i++) {
260     if (name[i] == '_') {
261       next_upper = true;
262     } else {
263       if (next_upper) {
264         result.push_back(ToUpper(name[i]));
265       } else {
266         result.push_back(name[i]);
267       }
268       next_upper = false;
269     }
270   }
271 
272   return result;
273 }
274 
275 // Class and enum names in protobuf should be PascalCased by convention, but
276 // since there is nothing enforcing this we need to ensure that they are valid
277 // Ruby constants.  That mainly means making sure that the first character is
278 // an upper-case letter.
RubifyConstant(const std::string & name)279 std::string RubifyConstant(const std::string& name) {
280   std::string ret = name;
281   if (!ret.empty()) {
282     if (IsLower(ret[0])) {
283       // If it starts with a lowercase letter, capitalize it.
284       ret[0] = ToUpper(ret[0]);
285     } else if (!IsAlpha(ret[0])) {
286       // Otherwise (e.g. if it begins with an underscore), we need to come up
287       // with some prefix that starts with a capital letter. We could be smarter
288       // here, e.g. try to strip leading underscores, but this may cause other
289       // problems if the user really intended the name. So let's just prepend a
290       // well-known suffix.
291       ret = "PB_" + ret;
292     }
293   }
294 
295   return ret;
296 }
297 
GenerateMessageAssignment(const std::string & prefix,const google::protobuf::Descriptor * message,google::protobuf::io::Printer * printer)298 void GenerateMessageAssignment(
299     const std::string& prefix,
300     const google::protobuf::Descriptor* message,
301     google::protobuf::io::Printer* printer) {
302 
303   // Don't generate MapEntry messages -- we use the Ruby extension's native
304   // support for map fields instead.
305   if (message->options().map_entry()) {
306     return;
307   }
308 
309   printer->Print(
310     "$prefix$$name$ = ",
311     "prefix", prefix,
312     "name", RubifyConstant(message->name()));
313   printer->Print(
314     "Google::Protobuf::DescriptorPool.generated_pool."
315     "lookup(\"$full_name$\").msgclass\n",
316     "full_name", message->full_name());
317 
318   std::string nested_prefix = prefix + message->name() + "::";
319   for (int i = 0; i < message->nested_type_count(); i++) {
320     GenerateMessageAssignment(nested_prefix, message->nested_type(i), printer);
321   }
322   for (int i = 0; i < message->enum_type_count(); i++) {
323     GenerateEnumAssignment(nested_prefix, message->enum_type(i), printer);
324   }
325 }
326 
GenerateEnumAssignment(const std::string & prefix,const google::protobuf::EnumDescriptor * en,google::protobuf::io::Printer * printer)327 void GenerateEnumAssignment(
328     const std::string& prefix,
329     const google::protobuf::EnumDescriptor* en,
330     google::protobuf::io::Printer* printer) {
331   printer->Print(
332     "$prefix$$name$ = ",
333     "prefix", prefix,
334     "name", RubifyConstant(en->name()));
335   printer->Print(
336     "Google::Protobuf::DescriptorPool.generated_pool."
337     "lookup(\"$full_name$\").enummodule\n",
338     "full_name", en->full_name());
339 }
340 
GeneratePackageModules(std::string package_name,google::protobuf::io::Printer * printer)341 int GeneratePackageModules(
342     std::string package_name,
343     google::protobuf::io::Printer* printer) {
344   int levels = 0;
345   while (!package_name.empty()) {
346     size_t dot_index = package_name.find(".");
347     string component;
348     if (dot_index == string::npos) {
349       component = package_name;
350       package_name = "";
351     } else {
352       component = package_name.substr(0, dot_index);
353       package_name = package_name.substr(dot_index + 1);
354     }
355     component = PackageToModule(component);
356     printer->Print(
357       "module $name$\n",
358       "name", component);
359     printer->Indent();
360     levels++;
361   }
362   return levels;
363 }
364 
EndPackageModules(int levels,google::protobuf::io::Printer * printer)365 void EndPackageModules(
366     int levels,
367     google::protobuf::io::Printer* printer) {
368   while (levels > 0) {
369     levels--;
370     printer->Outdent();
371     printer->Print(
372       "end\n");
373   }
374 }
375 
UsesTypeFromFile(const Descriptor * message,const FileDescriptor * file,string * error)376 bool UsesTypeFromFile(const Descriptor* message, const FileDescriptor* file,
377                       string* error) {
378   for (int i = 0; i < message->field_count(); i++) {
379     const FieldDescriptor* field = message->field(i);
380     if ((field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
381          field->message_type()->file() == file) ||
382         (field->type() == FieldDescriptor::TYPE_ENUM &&
383          field->enum_type()->file() == file)) {
384       *error = "proto3 message field " + field->full_name() + " in file " +
385                file->name() + " has a dependency on a type from proto2 file " +
386                file->name() +
387                ".  Ruby doesn't support proto2 yet, so we must fail.";
388       return true;
389     }
390   }
391 
392   for (int i = 0; i < message->nested_type_count(); i++) {
393     if (UsesTypeFromFile(message->nested_type(i), file, error)) {
394       return true;
395     }
396   }
397 
398   return false;
399 }
400 
401 // Ruby doesn't currently support proto2.  This causes a failure even for proto3
402 // files that import proto2.  But in some cases, the proto2 file is only being
403 // imported to extend another proto2 message.  The prime example is declaring
404 // custom options by extending FileOptions/FieldOptions/etc.
405 //
406 // If the proto3 messages don't have any proto2 submessages, it is safe to omit
407 // the dependency completely.  Users won't be able to use any proto2 extensions,
408 // but they already couldn't because proto2 messages aren't supported.
409 //
410 // If/when we add proto2 support, we should remove this.
MaybeEmitDependency(const FileDescriptor * import,const FileDescriptor * from,io::Printer * printer,string * error)411 bool MaybeEmitDependency(const FileDescriptor* import,
412                          const FileDescriptor* from,
413                          io::Printer* printer,
414                          string* error) {
415   if (import->syntax() == FileDescriptor::SYNTAX_PROTO2) {
416     for (int i = 0; i < from->message_type_count(); i++) {
417       if (UsesTypeFromFile(from->message_type(i), import, error)) {
418         // Error text was already set by UsesTypeFromFile().
419         return false;
420       }
421     }
422 
423     // Ok to omit this proto2 dependency -- so we won't print anything.
424     GOOGLE_LOG(WARNING) << "Omitting proto2 dependency '" << import->name()
425                         << "' from proto3 output file '"
426                         << GetOutputFilename(from->name())
427                         << "' because we don't support proto2 and no proto2 "
428                            "types from that file are being used.";
429     return true;
430   } else {
431     printer->Print(
432       "require '$name$'\n", "name", GetRequireName(import->name()));
433     return true;
434   }
435 }
436 
GenerateFile(const FileDescriptor * file,io::Printer * printer,string * error)437 bool GenerateFile(const FileDescriptor* file, io::Printer* printer,
438                   string* error) {
439   printer->Print(
440     "# Generated by the protocol buffer compiler.  DO NOT EDIT!\n"
441     "# source: $filename$\n"
442     "\n",
443     "filename", file->name());
444 
445   printer->Print(
446     "require 'google/protobuf'\n\n");
447 
448   for (int i = 0; i < file->dependency_count(); i++) {
449     if (!MaybeEmitDependency(file->dependency(i), file, printer, error)) {
450       return false;
451     }
452   }
453 
454   printer->Print(
455     "Google::Protobuf::DescriptorPool.generated_pool.build do\n");
456   printer->Indent();
457   for (int i = 0; i < file->message_type_count(); i++) {
458     GenerateMessage(file->message_type(i), printer);
459   }
460   for (int i = 0; i < file->enum_type_count(); i++) {
461     GenerateEnum(file->enum_type(i), printer);
462   }
463   printer->Outdent();
464   printer->Print(
465     "end\n\n");
466 
467   int levels = GeneratePackageModules(file->package(), printer);
468   for (int i = 0; i < file->message_type_count(); i++) {
469     GenerateMessageAssignment("", file->message_type(i), printer);
470   }
471   for (int i = 0; i < file->enum_type_count(); i++) {
472     GenerateEnumAssignment("", file->enum_type(i), printer);
473   }
474   EndPackageModules(levels, printer);
475   return true;
476 }
477 
Generate(const FileDescriptor * file,const string & parameter,GeneratorContext * generator_context,string * error) const478 bool Generator::Generate(
479     const FileDescriptor* file,
480     const string& parameter,
481     GeneratorContext* generator_context,
482     string* error) const {
483 
484   if (file->syntax() != FileDescriptor::SYNTAX_PROTO3) {
485     *error =
486         "Can only generate Ruby code for proto3 .proto files.\n"
487         "Please add 'syntax = \"proto3\";' to the top of your .proto file.\n";
488     return false;
489   }
490 
491   scoped_ptr<io::ZeroCopyOutputStream> output(
492       generator_context->Open(GetOutputFilename(file->name())));
493   io::Printer printer(output.get(), '$');
494 
495   return GenerateFile(file, &printer, error);
496 }
497 
498 }  // namespace ruby
499 }  // namespace compiler
500 }  // namespace protobuf
501 }  // namespace google
502