• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "google/protobuf/compiler/ruby/ruby_generator.h"
9 
10 #include <iomanip>
11 #include <memory>
12 #include <sstream>
13 
14 #include "google/protobuf/compiler/code_generator.h"
15 #include "absl/container/flat_hash_set.h"
16 #include "absl/log/absl_log.h"
17 #include "absl/strings/escaping.h"
18 #include "google/protobuf/compiler/plugin.h"
19 #include "google/protobuf/compiler/retention.h"
20 #include "google/protobuf/descriptor.h"
21 #include "google/protobuf/descriptor.pb.h"
22 #include "google/protobuf/io/printer.h"
23 #include "google/protobuf/io/zero_copy_stream.h"
24 
25 namespace google {
26 namespace protobuf {
27 namespace compiler {
28 namespace ruby {
29 
30 // Forward decls.
31 template <class numeric_type>
32 std::string NumberToString(numeric_type value);
33 std::string GetRequireName(absl::string_view proto_file);
34 std::string LabelForField(FieldDescriptor* field);
35 std::string TypeName(FieldDescriptor* field);
36 void GenerateMessageAssignment(absl::string_view prefix,
37                                const Descriptor* message, io::Printer* printer);
38 void GenerateEnumAssignment(absl::string_view prefix, const EnumDescriptor* en,
39                             io::Printer* printer);
40 std::string DefaultValueForField(const FieldDescriptor* field);
41 
42 template <class numeric_type>
NumberToString(numeric_type value)43 std::string NumberToString(numeric_type value) {
44   std::ostringstream os;
45   os << value;
46   return os.str();
47 }
48 
GetRequireName(absl::string_view proto_file)49 std::string GetRequireName(absl::string_view proto_file) {
50   size_t lastindex = proto_file.find_last_of('.');
51   return absl::StrCat(proto_file.substr(0, lastindex), "_pb");
52 }
53 
GetOutputFilename(absl::string_view proto_file)54 std::string GetOutputFilename(absl::string_view proto_file) {
55   return absl::StrCat(GetRequireName(proto_file), ".rb");
56 }
57 
58 // Locale-agnostic utility functions.
IsLower(char ch)59 bool IsLower(char ch) { return ch >= 'a' && ch <= 'z'; }
60 
IsUpper(char ch)61 bool IsUpper(char ch) { return ch >= 'A' && ch <= 'Z'; }
62 
IsAlpha(char ch)63 bool IsAlpha(char ch) { return IsLower(ch) || IsUpper(ch); }
64 
UpperChar(char ch)65 char UpperChar(char ch) { return IsLower(ch) ? (ch - 'a' + 'A') : ch; }
66 
67 // Package names in protobuf are snake_case by convention, but Ruby module
68 // names must be PascalCased.
69 //
70 //   foo_bar_baz -> FooBarBaz
PackageToModule(absl::string_view name)71 std::string PackageToModule(absl::string_view name) {
72   bool next_upper = true;
73   std::string result;
74   result.reserve(name.size());
75 
76   for (int i = 0; i < name.size(); i++) {
77     if (name[i] == '_') {
78       next_upper = true;
79     } else {
80       if (next_upper) {
81         result.push_back(UpperChar(name[i]));
82       } else {
83         result.push_back(name[i]);
84       }
85       next_upper = false;
86     }
87   }
88 
89   return result;
90 }
91 
92 // Class and enum names in protobuf should be PascalCased by convention, but
93 // since there is nothing enforcing this we need to ensure that they are valid
94 // Ruby constants.  That mainly means making sure that the first character is
95 // an upper-case letter.
RubifyConstant(absl::string_view name)96 std::string RubifyConstant(absl::string_view name) {
97   std::string ret(name);
98   if (!ret.empty()) {
99     if (IsLower(ret[0])) {
100       // If it starts with a lowercase letter, capitalize it.
101       ret[0] = UpperChar(ret[0]);
102     } else if (!IsAlpha(ret[0])) {
103       // Otherwise (e.g. if it begins with an underscore), we need to come up
104       // with some prefix that starts with a capital letter. We could be smarter
105       // here, e.g. try to strip leading underscores, but this may cause other
106       // problems if the user really intended the name. So let's just prepend a
107       // well-known suffix.
108       return absl::StrCat("PB_", ret);
109     }
110   }
111 
112   return ret;
113 }
114 
GenerateMessageAssignment(absl::string_view prefix,const Descriptor * message,io::Printer * printer)115 void GenerateMessageAssignment(absl::string_view prefix,
116                                const Descriptor* message,
117                                io::Printer* printer) {
118   // Don't generate MapEntry messages -- we use the Ruby extension's native
119   // support for map fields instead.
120   if (message->options().map_entry()) {
121     return;
122   }
123 
124   printer->Print("$prefix$$name$ = ", "prefix", prefix, "name",
125                  RubifyConstant(message->name()));
126   printer->Print(
127       "::Google::Protobuf::DescriptorPool.generated_pool."
128       "lookup(\"$full_name$\").msgclass\n",
129       "full_name", message->full_name());
130 
131   std::string nested_prefix =
132       absl::StrCat(prefix, RubifyConstant(message->name()), "::");
133   for (int i = 0; i < message->nested_type_count(); i++) {
134     GenerateMessageAssignment(nested_prefix, message->nested_type(i), printer);
135   }
136   for (int i = 0; i < message->enum_type_count(); i++) {
137     GenerateEnumAssignment(nested_prefix, message->enum_type(i), printer);
138   }
139 }
140 
GenerateEnumAssignment(absl::string_view prefix,const EnumDescriptor * en,io::Printer * printer)141 void GenerateEnumAssignment(absl::string_view prefix, const EnumDescriptor* en,
142                             io::Printer* printer) {
143   printer->Print("$prefix$$name$ = ", "prefix", prefix, "name",
144                  RubifyConstant(en->name()));
145   printer->Print(
146       "::Google::Protobuf::DescriptorPool.generated_pool."
147       "lookup(\"$full_name$\").enummodule\n",
148       "full_name", en->full_name());
149 }
150 
GeneratePackageModules(const FileDescriptor * file,io::Printer * printer)151 int GeneratePackageModules(const FileDescriptor* file, io::Printer* printer) {
152   int levels = 0;
153   bool need_change_to_module = true;
154   std::string package_name;
155 
156   // Determine the name to use in either format:
157   //   proto package:         one.two.three
158   //   option ruby_package:   One::Two::Three
159   if (file->options().has_ruby_package()) {
160     package_name = file->options().ruby_package();
161 
162     // If :: is in the package use the Ruby formatted name as-is
163     //    -> A::B::C
164     // otherwise, use the dot separator
165     //    -> A.B.C
166     if (package_name.find("::") != std::string::npos) {
167       need_change_to_module = false;
168     } else if (package_name.find('.') != std::string::npos) {
169       ABSL_LOG(WARNING) << "ruby_package option should be in the form of:"
170                         << " 'A::B::C' and not 'A.B.C'";
171     }
172   } else {
173     package_name = file->package();
174   }
175 
176   // Use the appropriate delimiter
177   std::string delimiter = need_change_to_module ? "." : "::";
178   int delimiter_size = need_change_to_module ? 1 : 2;
179 
180   // Extract each module name and indent
181   while (!package_name.empty()) {
182     size_t dot_index = package_name.find(delimiter);
183     std::string component;
184     if (dot_index == std::string::npos) {
185       component = package_name;
186       package_name = "";
187     } else {
188       component = package_name.substr(0, dot_index);
189       package_name = package_name.substr(dot_index + delimiter_size);
190     }
191     if (need_change_to_module) {
192       component = PackageToModule(component);
193     }
194     printer->Print("module $name$\n", "name", component);
195     printer->Indent();
196     levels++;
197   }
198   return levels;
199 }
200 
EndPackageModules(int levels,io::Printer * printer)201 void EndPackageModules(int levels, io::Printer* printer) {
202   while (levels > 0) {
203     levels--;
204     printer->Outdent();
205     printer->Print("end\n");
206   }
207 }
208 
SerializedDescriptor(const FileDescriptor * file)209 std::string SerializedDescriptor(const FileDescriptor* file) {
210   FileDescriptorProto file_proto = StripSourceRetentionOptions(*file);
211   std::string file_data;
212   file_proto.SerializeToString(&file_data);
213   return file_data;
214 }
215 
216 template <class F>
ForEachField(const Descriptor * d,F && func)217 void ForEachField(const Descriptor* d, F&& func) {
218   for (int i = 0; i < d->field_count(); i++) {
219     func(d->field(i));
220   }
221   for (int i = 0; i < d->nested_type_count(); i++) {
222     ForEachField(d->nested_type(i), func);
223   }
224 }
225 
226 template <class F>
ForEachField(const FileDescriptor * file,F && func)227 void ForEachField(const FileDescriptor* file, F&& func) {
228   for (int i = 0; i < file->message_type_count(); i++) {
229     ForEachField(file->message_type(i), func);
230   }
231   for (int i = 0; i < file->extension_count(); i++) {
232     func(file->extension(i));
233   }
234 }
235 
DumpImportList(const FileDescriptor * file)236 std::string DumpImportList(const FileDescriptor* file) {
237   // For each import, find a symbol that comes from that file.
238   absl::flat_hash_set<const FileDescriptor*> seen{file};
239   std::string ret;
240   ForEachField(file, [&](const FieldDescriptor* field) {
241     if (!field->message_type()) return;
242     const FileDescriptor* f = field->message_type()->file();
243     if (!seen.insert(f).second) return;
244     absl::StrAppend(&ret, "    [\"", field->message_type()->full_name(),
245                     "\", \"", f->name(), "\"],\n");
246   });
247   return ret;
248 }
249 
GenerateBinaryDescriptor(const FileDescriptor * file,io::Printer * printer,std::string * error)250 void GenerateBinaryDescriptor(const FileDescriptor* file, io::Printer* printer,
251                               std::string* error) {
252   printer->Print(R"(
253 descriptor_data = "$descriptor_data$"
254 
255 pool = Google::Protobuf::DescriptorPool.generated_pool
256 pool.add_serialized_file(descriptor_data)
257 
258 )",
259                  "descriptor_data",
260                  absl::CHexEscape(SerializedDescriptor(file)), "imports",
261                  DumpImportList(file));
262 }
263 
GenerateFile(const FileDescriptor * file,io::Printer * printer,std::string * error)264 bool GenerateFile(const FileDescriptor* file, io::Printer* printer,
265                   std::string* error) {
266   printer->Print(
267       "# frozen_string_literal: true\n"
268       "# Generated by the protocol buffer compiler.  DO NOT EDIT!\n"
269       "# source: $filename$\n"
270       "\n",
271       "filename", file->name());
272 
273   printer->Print("require 'google/protobuf'\n\n");
274 
275   if (file->dependency_count() != 0) {
276     for (int i = 0; i < file->dependency_count(); i++) {
277       printer->Print("require '$name$'\n", "name",
278                      GetRequireName(file->dependency(i)->name()));
279     }
280     printer->Print("\n");
281   }
282 
283   GenerateBinaryDescriptor(file, printer, error);
284 
285   int levels = GeneratePackageModules(file, printer);
286   for (int i = 0; i < file->message_type_count(); i++) {
287     GenerateMessageAssignment("", file->message_type(i), printer);
288   }
289   for (int i = 0; i < file->enum_type_count(); i++) {
290     GenerateEnumAssignment("", file->enum_type(i), printer);
291   }
292   EndPackageModules(levels, printer);
293 
294   return true;
295 }
296 
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * generator_context,std::string * error) const297 bool Generator::Generate(const FileDescriptor* file,
298                          const std::string& parameter,
299                          GeneratorContext* generator_context,
300                          std::string* error) const {
301   std::unique_ptr<io::ZeroCopyOutputStream> output(
302       generator_context->Open(GetOutputFilename(file->name())));
303   io::Printer printer(output.get(), '$');
304 
305   return GenerateFile(file, &printer, error);
306 }
307 
308 }  // namespace ruby
309 }  // namespace compiler
310 }  // namespace protobuf
311 }  // namespace google
312