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