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 <iomanip>
32 #include <sstream>
33
34 #include <google/protobuf/compiler/code_generator.h>
35 #include <google/protobuf/compiler/plugin.h>
36 #include <google/protobuf/descriptor.h>
37 #include <google/protobuf/descriptor.pb.h>
38 #include <google/protobuf/io/printer.h>
39 #include <google/protobuf/io/zero_copy_stream.h>
40
41 #include <google/protobuf/compiler/ruby/ruby_generator.h>
42
43 namespace google {
44 namespace protobuf {
45 namespace compiler {
46 namespace ruby {
47
48 // Forward decls.
49 template <class numeric_type>
50 std::string NumberToString(numeric_type value);
51 std::string GetRequireName(const std::string& proto_file);
52 std::string LabelForField(FieldDescriptor* field);
53 std::string TypeName(FieldDescriptor* field);
54 bool GenerateMessage(const Descriptor* message, io::Printer* printer,
55 std::string* error);
56 void GenerateEnum(const EnumDescriptor* en, io::Printer* printer);
57 void GenerateMessageAssignment(const std::string& prefix,
58 const Descriptor* message, io::Printer* printer);
59 void GenerateEnumAssignment(const std::string& prefix, const EnumDescriptor* en,
60 io::Printer* printer);
61 std::string DefaultValueForField(const FieldDescriptor* field);
62
63 template<class numeric_type>
NumberToString(numeric_type value)64 std::string NumberToString(numeric_type value) {
65 std::ostringstream os;
66 os << value;
67 return os.str();
68 }
69
GetRequireName(const std::string & proto_file)70 std::string GetRequireName(const std::string& proto_file) {
71 int lastindex = proto_file.find_last_of(".");
72 return proto_file.substr(0, lastindex) + "_pb";
73 }
74
GetOutputFilename(const std::string & proto_file)75 std::string GetOutputFilename(const std::string& proto_file) {
76 return GetRequireName(proto_file) + ".rb";
77 }
78
LabelForField(const FieldDescriptor * field)79 std::string LabelForField(const FieldDescriptor* field) {
80 switch (field->label()) {
81 case FieldDescriptor::LABEL_OPTIONAL: return "optional";
82 case FieldDescriptor::LABEL_REQUIRED: return "required";
83 case FieldDescriptor::LABEL_REPEATED: return "repeated";
84 default: assert(false); return "";
85 }
86 }
87
TypeName(const FieldDescriptor * field)88 std::string TypeName(const FieldDescriptor* field) {
89 switch (field->type()) {
90 case FieldDescriptor::TYPE_INT32: return "int32";
91 case FieldDescriptor::TYPE_INT64: return "int64";
92 case FieldDescriptor::TYPE_UINT32: return "uint32";
93 case FieldDescriptor::TYPE_UINT64: return "uint64";
94 case FieldDescriptor::TYPE_SINT32: return "sint32";
95 case FieldDescriptor::TYPE_SINT64: return "sint64";
96 case FieldDescriptor::TYPE_FIXED32: return "fixed32";
97 case FieldDescriptor::TYPE_FIXED64: return "fixed64";
98 case FieldDescriptor::TYPE_SFIXED32: return "sfixed32";
99 case FieldDescriptor::TYPE_SFIXED64: return "sfixed64";
100 case FieldDescriptor::TYPE_DOUBLE: return "double";
101 case FieldDescriptor::TYPE_FLOAT: return "float";
102 case FieldDescriptor::TYPE_BOOL: return "bool";
103 case FieldDescriptor::TYPE_ENUM: return "enum";
104 case FieldDescriptor::TYPE_STRING: return "string";
105 case FieldDescriptor::TYPE_BYTES: return "bytes";
106 case FieldDescriptor::TYPE_MESSAGE: return "message";
107 case FieldDescriptor::TYPE_GROUP: return "group";
108 default: assert(false); return "";
109 }
110 }
111
StringifySyntax(FileDescriptor::Syntax syntax)112 string StringifySyntax(FileDescriptor::Syntax syntax) {
113 switch (syntax) {
114 case FileDescriptor::SYNTAX_PROTO2:
115 return "proto2";
116 case FileDescriptor::SYNTAX_PROTO3:
117 return "proto3";
118 case FileDescriptor::SYNTAX_UNKNOWN:
119 default:
120 GOOGLE_LOG(FATAL) << "Unsupported syntax; this generator only supports "
121 "proto2 and proto3 syntax.";
122 return "";
123 }
124 }
125
DefaultValueForField(const FieldDescriptor * field)126 std::string DefaultValueForField(const FieldDescriptor* field) {
127 switch(field->cpp_type()) {
128 case FieldDescriptor::CPPTYPE_INT32:
129 return NumberToString(field->default_value_int32());
130 case FieldDescriptor::CPPTYPE_INT64:
131 return NumberToString(field->default_value_int64());
132 case FieldDescriptor::CPPTYPE_UINT32:
133 return NumberToString(field->default_value_uint32());
134 case FieldDescriptor::CPPTYPE_UINT64:
135 return NumberToString(field->default_value_uint64());
136 case FieldDescriptor::CPPTYPE_FLOAT:
137 return NumberToString(field->default_value_float());
138 case FieldDescriptor::CPPTYPE_DOUBLE:
139 return NumberToString(field->default_value_double());
140 case FieldDescriptor::CPPTYPE_BOOL:
141 return field->default_value_bool() ? "true" : "false";
142 case FieldDescriptor::CPPTYPE_ENUM:
143 return NumberToString(field->default_value_enum()->number());
144 case FieldDescriptor::CPPTYPE_STRING: {
145 std::ostringstream os;
146 string default_str = field->default_value_string();
147
148 if (field->type() == FieldDescriptor::TYPE_STRING) {
149 os << "\"" << default_str << "\"";
150 } else if (field->type() == FieldDescriptor::TYPE_BYTES) {
151 os << "\"";
152
153 os.fill('0');
154 for (int i = 0; i < default_str.length(); ++i) {
155 // Write the hex form of each byte.
156 os << "\\x" << std::hex << std::setw(2)
157 << ((uint16)((unsigned char)default_str.at(i)));
158 }
159 os << "\".force_encoding(\"ASCII-8BIT\")";
160 }
161
162 return os.str();
163 }
164 default: assert(false); return "";
165 }
166 }
167
GenerateField(const FieldDescriptor * field,io::Printer * printer)168 void GenerateField(const FieldDescriptor* field, io::Printer* printer) {
169 if (field->is_map()) {
170 const FieldDescriptor* key_field =
171 field->message_type()->FindFieldByNumber(1);
172 const FieldDescriptor* value_field =
173 field->message_type()->FindFieldByNumber(2);
174
175 printer->Print(
176 "map :$name$, :$key_type$, :$value_type$, $number$",
177 "name", field->name(),
178 "key_type", TypeName(key_field),
179 "value_type", TypeName(value_field),
180 "number", NumberToString(field->number()));
181
182 if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
183 printer->Print(
184 ", \"$subtype$\"\n",
185 "subtype", value_field->message_type()->full_name());
186 } else if (value_field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
187 printer->Print(
188 ", \"$subtype$\"\n",
189 "subtype", value_field->enum_type()->full_name());
190 } else {
191 printer->Print("\n");
192 }
193 } else {
194
195 printer->Print(
196 "$label$ :$name$, ",
197 "label", LabelForField(field),
198 "name", field->name());
199 printer->Print(
200 ":$type$, $number$",
201 "type", TypeName(field),
202 "number", NumberToString(field->number()));
203
204 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
205 printer->Print(
206 ", \"$subtype$\"",
207 "subtype", field->message_type()->full_name());
208 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
209 printer->Print(
210 ", \"$subtype$\"",
211 "subtype", field->enum_type()->full_name());
212 }
213
214 if (field->has_default_value()) {
215 printer->Print(", default: $default$", "default",
216 DefaultValueForField(field));
217 }
218
219 printer->Print("\n");
220 }
221 }
222
GenerateOneof(const OneofDescriptor * oneof,io::Printer * printer)223 void GenerateOneof(const OneofDescriptor* oneof, io::Printer* printer) {
224 printer->Print(
225 "oneof :$name$ do\n",
226 "name", oneof->name());
227 printer->Indent();
228
229 for (int i = 0; i < oneof->field_count(); i++) {
230 const FieldDescriptor* field = oneof->field(i);
231 GenerateField(field, printer);
232 }
233
234 printer->Outdent();
235 printer->Print("end\n");
236 }
237
GenerateMessage(const Descriptor * message,io::Printer * printer,std::string * error)238 bool GenerateMessage(const Descriptor* message, io::Printer* printer,
239 std::string* error) {
240 if (message->extension_range_count() > 0 || message->extension_count() > 0) {
241 *error = "Extensions are not yet supported for proto2 .proto files.";
242 return false;
243 }
244
245 // Don't generate MapEntry messages -- we use the Ruby extension's native
246 // support for map fields instead.
247 if (message->options().map_entry()) {
248 return true;
249 }
250
251 printer->Print(
252 "add_message \"$name$\" do\n",
253 "name", message->full_name());
254 printer->Indent();
255
256 for (int i = 0; i < message->field_count(); i++) {
257 const FieldDescriptor* field = message->field(i);
258 if (!field->containing_oneof()) {
259 GenerateField(field, printer);
260 }
261 }
262
263 for (int i = 0; i < message->oneof_decl_count(); i++) {
264 const OneofDescriptor* oneof = message->oneof_decl(i);
265 GenerateOneof(oneof, printer);
266 }
267
268 printer->Outdent();
269 printer->Print("end\n");
270
271 for (int i = 0; i < message->nested_type_count(); i++) {
272 if (!GenerateMessage(message->nested_type(i), printer, error)) {
273 return false;
274 }
275 }
276 for (int i = 0; i < message->enum_type_count(); i++) {
277 GenerateEnum(message->enum_type(i), printer);
278 }
279
280 return true;
281 }
282
GenerateEnum(const EnumDescriptor * en,io::Printer * printer)283 void GenerateEnum(const EnumDescriptor* en, io::Printer* printer) {
284 printer->Print(
285 "add_enum \"$name$\" do\n",
286 "name", en->full_name());
287 printer->Indent();
288
289 for (int i = 0; i < en->value_count(); i++) {
290 const EnumValueDescriptor* value = en->value(i);
291 printer->Print(
292 "value :$name$, $number$\n",
293 "name", value->name(),
294 "number", NumberToString(value->number()));
295 }
296
297 printer->Outdent();
298 printer->Print(
299 "end\n");
300 }
301
302 // Locale-agnostic utility functions.
IsLower(char ch)303 bool IsLower(char ch) { return ch >= 'a' && ch <= 'z'; }
304
IsUpper(char ch)305 bool IsUpper(char ch) { return ch >= 'A' && ch <= 'Z'; }
306
IsAlpha(char ch)307 bool IsAlpha(char ch) { return IsLower(ch) || IsUpper(ch); }
308
UpperChar(char ch)309 char UpperChar(char ch) { return IsLower(ch) ? (ch - 'a' + 'A') : ch; }
310
311
312 // Package names in protobuf are snake_case by convention, but Ruby module
313 // names must be PascalCased.
314 //
315 // foo_bar_baz -> FooBarBaz
PackageToModule(const std::string & name)316 std::string PackageToModule(const std::string& name) {
317 bool next_upper = true;
318 std::string result;
319 result.reserve(name.size());
320
321 for (int i = 0; i < name.size(); i++) {
322 if (name[i] == '_') {
323 next_upper = true;
324 } else {
325 if (next_upper) {
326 result.push_back(UpperChar(name[i]));
327 } else {
328 result.push_back(name[i]);
329 }
330 next_upper = false;
331 }
332 }
333
334 return result;
335 }
336
337 // Class and enum names in protobuf should be PascalCased by convention, but
338 // since there is nothing enforcing this we need to ensure that they are valid
339 // Ruby constants. That mainly means making sure that the first character is
340 // an upper-case letter.
RubifyConstant(const std::string & name)341 std::string RubifyConstant(const std::string& name) {
342 std::string ret = name;
343 if (!ret.empty()) {
344 if (IsLower(ret[0])) {
345 // If it starts with a lowercase letter, capitalize it.
346 ret[0] = UpperChar(ret[0]);
347 } else if (!IsAlpha(ret[0])) {
348 // Otherwise (e.g. if it begins with an underscore), we need to come up
349 // with some prefix that starts with a capital letter. We could be smarter
350 // here, e.g. try to strip leading underscores, but this may cause other
351 // problems if the user really intended the name. So let's just prepend a
352 // well-known suffix.
353 ret = "PB_" + ret;
354 }
355 }
356
357 return ret;
358 }
359
GenerateMessageAssignment(const std::string & prefix,const Descriptor * message,io::Printer * printer)360 void GenerateMessageAssignment(const std::string& prefix,
361 const Descriptor* message,
362 io::Printer* printer) {
363 // Don't generate MapEntry messages -- we use the Ruby extension's native
364 // support for map fields instead.
365 if (message->options().map_entry()) {
366 return;
367 }
368
369 printer->Print(
370 "$prefix$$name$ = ",
371 "prefix", prefix,
372 "name", RubifyConstant(message->name()));
373 printer->Print(
374 "Google::Protobuf::DescriptorPool.generated_pool."
375 "lookup(\"$full_name$\").msgclass\n",
376 "full_name", message->full_name());
377
378 std::string nested_prefix = prefix + RubifyConstant(message->name()) + "::";
379 for (int i = 0; i < message->nested_type_count(); i++) {
380 GenerateMessageAssignment(nested_prefix, message->nested_type(i), printer);
381 }
382 for (int i = 0; i < message->enum_type_count(); i++) {
383 GenerateEnumAssignment(nested_prefix, message->enum_type(i), printer);
384 }
385 }
386
GenerateEnumAssignment(const std::string & prefix,const EnumDescriptor * en,io::Printer * printer)387 void GenerateEnumAssignment(const std::string& prefix, const EnumDescriptor* en,
388 io::Printer* printer) {
389 printer->Print(
390 "$prefix$$name$ = ",
391 "prefix", prefix,
392 "name", RubifyConstant(en->name()));
393 printer->Print(
394 "Google::Protobuf::DescriptorPool.generated_pool."
395 "lookup(\"$full_name$\").enummodule\n",
396 "full_name", en->full_name());
397 }
398
GeneratePackageModules(const FileDescriptor * file,io::Printer * printer)399 int GeneratePackageModules(const FileDescriptor* file, io::Printer* printer) {
400 int levels = 0;
401 bool need_change_to_module = true;
402 std::string package_name;
403
404 // Determine the name to use in either format:
405 // proto package: one.two.three
406 // option ruby_package: One::Two::Three
407 if (file->options().has_ruby_package()) {
408 package_name = file->options().ruby_package();
409
410 // If :: is in the package use the Ruby formated name as-is
411 // -> A::B::C
412 // otherwise, use the dot seperator
413 // -> A.B.C
414 if (package_name.find("::") != std::string::npos) {
415 need_change_to_module = false;
416 } else {
417 GOOGLE_LOG(WARNING) << "ruby_package option should be in the form of:"
418 << " 'A::B::C' and not 'A.B.C'";
419 }
420 } else {
421 package_name = file->package();
422 }
423
424 // Use the appropriate delimter
425 string delimiter = need_change_to_module ? "." : "::";
426 int delimiter_size = need_change_to_module ? 1 : 2;
427
428 // Extract each module name and indent
429 while (!package_name.empty()) {
430 size_t dot_index = package_name.find(delimiter);
431 string component;
432 if (dot_index == string::npos) {
433 component = package_name;
434 package_name = "";
435 } else {
436 component = package_name.substr(0, dot_index);
437 package_name = package_name.substr(dot_index + delimiter_size);
438 }
439 if (need_change_to_module) {
440 component = PackageToModule(component);
441 }
442 printer->Print(
443 "module $name$\n",
444 "name", component);
445 printer->Indent();
446 levels++;
447 }
448 return levels;
449 }
450
EndPackageModules(int levels,io::Printer * printer)451 void EndPackageModules(int levels, io::Printer* printer) {
452 while (levels > 0) {
453 levels--;
454 printer->Outdent();
455 printer->Print(
456 "end\n");
457 }
458 }
459
UsesTypeFromFile(const Descriptor * message,const FileDescriptor * file,string * error)460 bool UsesTypeFromFile(const Descriptor* message, const FileDescriptor* file,
461 string* error) {
462 for (int i = 0; i < message->field_count(); i++) {
463 const FieldDescriptor* field = message->field(i);
464 if ((field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE &&
465 field->message_type()->file() == file) ||
466 (field->type() == FieldDescriptor::TYPE_ENUM &&
467 field->enum_type()->file() == file)) {
468 *error = "proto3 message field " + field->full_name() + " in file " +
469 file->name() + " has a dependency on a type from proto2 file " +
470 file->name() +
471 ". Ruby doesn't support proto2 yet, so we must fail.";
472 return true;
473 }
474 }
475
476 for (int i = 0; i < message->nested_type_count(); i++) {
477 if (UsesTypeFromFile(message->nested_type(i), file, error)) {
478 return true;
479 }
480 }
481
482 return false;
483 }
484
485 // Ruby doesn't currently support proto2. This causes a failure even for proto3
486 // files that import proto2. But in some cases, the proto2 file is only being
487 // imported to extend another proto2 message. The prime example is declaring
488 // custom options by extending FileOptions/FieldOptions/etc.
489 //
490 // If the proto3 messages don't have any proto2 submessages, it is safe to omit
491 // the dependency completely. Users won't be able to use any proto2 extensions,
492 // but they already couldn't because proto2 messages aren't supported.
493 //
494 // If/when we add proto2 support, we should remove this.
MaybeEmitDependency(const FileDescriptor * import,const FileDescriptor * from,io::Printer * printer,string * error)495 bool MaybeEmitDependency(const FileDescriptor* import,
496 const FileDescriptor* from,
497 io::Printer* printer,
498 string* error) {
499 if (from->syntax() == FileDescriptor::SYNTAX_PROTO3 &&
500 import->syntax() == FileDescriptor::SYNTAX_PROTO2) {
501 for (int i = 0; i < from->message_type_count(); i++) {
502 if (UsesTypeFromFile(from->message_type(i), import, error)) {
503 // Error text was already set by UsesTypeFromFile().
504 return false;
505 }
506 }
507
508 // Ok to omit this proto2 dependency -- so we won't print anything.
509 GOOGLE_LOG(WARNING) << "Omitting proto2 dependency '" << import->name()
510 << "' from proto3 output file '"
511 << GetOutputFilename(from->name())
512 << "' because we don't support proto2 and no proto2 "
513 "types from that file are being used.";
514 return true;
515 } else {
516 printer->Print(
517 "require '$name$'\n", "name", GetRequireName(import->name()));
518 return true;
519 }
520 }
521
GenerateFile(const FileDescriptor * file,io::Printer * printer,string * error)522 bool GenerateFile(const FileDescriptor* file, io::Printer* printer,
523 string* error) {
524 printer->Print(
525 "# Generated by the protocol buffer compiler. DO NOT EDIT!\n"
526 "# source: $filename$\n"
527 "\n",
528 "filename", file->name());
529
530 printer->Print(
531 "require 'google/protobuf'\n\n");
532
533 for (int i = 0; i < file->dependency_count(); i++) {
534 if (!MaybeEmitDependency(file->dependency(i), file, printer, error)) {
535 return false;
536 }
537 }
538
539 // TODO: Remove this when ruby supports extensions for proto2 syntax.
540 if (file->extension_count() > 0) {
541 *error = "Extensions are not yet supported for proto2 .proto files.";
542 return false;
543 }
544
545 printer->Print("Google::Protobuf::DescriptorPool.generated_pool.build do\n");
546 printer->Indent();
547 printer->Print("add_file(\"$filename$\", :syntax => :$syntax$) do\n",
548 "filename", file->name(), "syntax",
549 StringifySyntax(file->syntax()));
550 printer->Indent();
551 for (int i = 0; i < file->message_type_count(); i++) {
552 if (!GenerateMessage(file->message_type(i), printer, error)) {
553 return false;
554 }
555 }
556 for (int i = 0; i < file->enum_type_count(); i++) {
557 GenerateEnum(file->enum_type(i), printer);
558 }
559 printer->Outdent();
560 printer->Print("end\n");
561 printer->Outdent();
562 printer->Print(
563 "end\n\n");
564
565 int levels = GeneratePackageModules(file, printer);
566 for (int i = 0; i < file->message_type_count(); i++) {
567 GenerateMessageAssignment("", file->message_type(i), printer);
568 }
569 for (int i = 0; i < file->enum_type_count(); i++) {
570 GenerateEnumAssignment("", file->enum_type(i), printer);
571 }
572 EndPackageModules(levels, printer);
573 return true;
574 }
575
Generate(const FileDescriptor * file,const string & parameter,GeneratorContext * generator_context,string * error) const576 bool Generator::Generate(
577 const FileDescriptor* file,
578 const string& parameter,
579 GeneratorContext* generator_context,
580 string* error) const {
581
582 if (file->syntax() != FileDescriptor::SYNTAX_PROTO3 &&
583 file->syntax() != FileDescriptor::SYNTAX_PROTO2) {
584 *error = "Invalid or unsupported proto syntax";
585 return false;
586 }
587
588 std::unique_ptr<io::ZeroCopyOutputStream> output(
589 generator_context->Open(GetOutputFilename(file->name())));
590 io::Printer printer(output.get(), '$');
591
592 return GenerateFile(file, &printer, error);
593 }
594
595 } // namespace ruby
596 } // namespace compiler
597 } // namespace protobuf
598 } // namespace google
599