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