OLD | NEW |
| (Empty) |
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 <google/protobuf/compiler/php/php_generator.h> | |
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 #include <google/protobuf/stubs/strutil.h> | |
40 | |
41 #include <sstream> | |
42 | |
43 using google::protobuf::internal::scoped_ptr; | |
44 | |
45 const std::string kDescriptorFile = "google/protobuf/descriptor.proto"; | |
46 const std::string kEmptyFile = "google/protobuf/empty.proto"; | |
47 const std::string kEmptyMetadataFile = "GPBMetadata/Google/Protobuf/GPBEmpty.php
"; | |
48 const std::string kDescriptorMetadataFile = | |
49 "GPBMetadata/Google/Protobuf/Internal/Descriptor.php"; | |
50 const std::string kDescriptorDirName = "Google/Protobuf/Internal"; | |
51 const std::string kDescriptorPackageName = "Google\\Protobuf\\Internal"; | |
52 | |
53 namespace google { | |
54 namespace protobuf { | |
55 namespace compiler { | |
56 namespace php { | |
57 | |
58 // Forward decls. | |
59 std::string PhpName(const std::string& full_name, bool is_descriptor); | |
60 std::string DefaultForField(FieldDescriptor* field); | |
61 std::string IntToString(int32 value); | |
62 std::string FilenameToClassname(const string& filename); | |
63 std::string GeneratedMetadataFileName(const std::string& proto_file, | |
64 bool is_descriptor); | |
65 std::string LabelForField(FieldDescriptor* field); | |
66 std::string TypeName(FieldDescriptor* field); | |
67 std::string UnderscoresToCamelCase(const string& name, bool cap_first_letter); | |
68 std::string EscapeDollor(const string& to_escape); | |
69 std::string BinaryToHex(const string& binary); | |
70 void Indent(io::Printer* printer); | |
71 void Outdent(io::Printer* printer); | |
72 void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message); | |
73 void GenerateFieldDocComment(io::Printer* printer, | |
74 const FieldDescriptor* field); | |
75 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_); | |
76 void GenerateEnumValueDocComment(io::Printer* printer, | |
77 const EnumValueDescriptor* value); | |
78 | |
79 std::string RenameEmpty(const std::string& name) { | |
80 if (name == "Empty") { | |
81 return "GPBEmpty"; | |
82 } else { | |
83 return name; | |
84 } | |
85 } | |
86 | |
87 std::string MessagePrefix(const Descriptor* message) { | |
88 // Empty cannot be php class name. | |
89 if (message->name() == "Empty" && | |
90 message->file()->package() == "google.protobuf") { | |
91 return "GPB"; | |
92 } else { | |
93 return ""; | |
94 } | |
95 } | |
96 | |
97 std::string MessageName(const Descriptor* message, bool is_descriptor) { | |
98 string message_name = message->name(); | |
99 const Descriptor* descriptor = message->containing_type(); | |
100 while (descriptor != NULL) { | |
101 message_name = descriptor->name() + '_' + message_name; | |
102 descriptor = descriptor->containing_type(); | |
103 } | |
104 message_name = MessagePrefix(message) + message_name; | |
105 | |
106 return PhpName(message->file()->package(), is_descriptor) + '\\' + | |
107 message_name; | |
108 } | |
109 | |
110 std::string MessageFullName(const Descriptor* message, bool is_descriptor) { | |
111 if (is_descriptor) { | |
112 return StringReplace(message->full_name(), | |
113 "google.protobuf", | |
114 "google.protobuf.internal", false); | |
115 } else { | |
116 return message->full_name(); | |
117 } | |
118 } | |
119 | |
120 std::string EnumFullName(const EnumDescriptor* envm, bool is_descriptor) { | |
121 if (is_descriptor) { | |
122 return StringReplace(envm->full_name(), | |
123 "google.protobuf", | |
124 "google.protobuf.internal", false); | |
125 } else { | |
126 return envm->full_name(); | |
127 } | |
128 } | |
129 | |
130 std::string EnumClassName(const EnumDescriptor* envm) { | |
131 string enum_class_name = envm->name(); | |
132 const Descriptor* descriptor = envm->containing_type(); | |
133 while (descriptor != NULL) { | |
134 enum_class_name = descriptor->name() + '_' + enum_class_name; | |
135 descriptor = descriptor->containing_type(); | |
136 } | |
137 return enum_class_name; | |
138 } | |
139 | |
140 std::string EnumName(const EnumDescriptor* envm, bool is_descriptor) { | |
141 string enum_name = EnumClassName(envm); | |
142 return PhpName(envm->file()->package(), is_descriptor) + '\\' + enum_name; | |
143 } | |
144 | |
145 std::string PhpName(const std::string& full_name, bool is_descriptor) { | |
146 if (is_descriptor) { | |
147 return kDescriptorPackageName; | |
148 } | |
149 | |
150 std::string result; | |
151 bool cap_next_letter = true; | |
152 for (int i = 0; i < full_name.size(); i++) { | |
153 if ('a' <= full_name[i] && full_name[i] <= 'z' && cap_next_letter) { | |
154 result += full_name[i] + ('A' - 'a'); | |
155 cap_next_letter = false; | |
156 } else if (full_name[i] == '.') { | |
157 result += '\\'; | |
158 cap_next_letter = true; | |
159 } else { | |
160 result += full_name[i]; | |
161 cap_next_letter = false; | |
162 } | |
163 } | |
164 return result; | |
165 } | |
166 | |
167 std::string DefaultForField(const FieldDescriptor* field) { | |
168 switch (field->type()) { | |
169 case FieldDescriptor::TYPE_INT32: | |
170 case FieldDescriptor::TYPE_INT64: | |
171 case FieldDescriptor::TYPE_UINT32: | |
172 case FieldDescriptor::TYPE_UINT64: | |
173 case FieldDescriptor::TYPE_SINT32: | |
174 case FieldDescriptor::TYPE_SINT64: | |
175 case FieldDescriptor::TYPE_FIXED32: | |
176 case FieldDescriptor::TYPE_FIXED64: | |
177 case FieldDescriptor::TYPE_SFIXED32: | |
178 case FieldDescriptor::TYPE_SFIXED64: | |
179 case FieldDescriptor::TYPE_ENUM: return "0"; | |
180 case FieldDescriptor::TYPE_DOUBLE: | |
181 case FieldDescriptor::TYPE_FLOAT: return "0.0"; | |
182 case FieldDescriptor::TYPE_BOOL: return "false"; | |
183 case FieldDescriptor::TYPE_STRING: | |
184 case FieldDescriptor::TYPE_BYTES: return "''"; | |
185 case FieldDescriptor::TYPE_MESSAGE: | |
186 case FieldDescriptor::TYPE_GROUP: return "null"; | |
187 default: assert(false); return ""; | |
188 } | |
189 } | |
190 | |
191 std::string GeneratedMetadataFileName(const std::string& proto_file, | |
192 bool is_descriptor) { | |
193 int start_index = 0; | |
194 int first_index = proto_file.find_first_of("/", start_index); | |
195 std::string result = "GPBMetadata/"; | |
196 | |
197 if (proto_file == kEmptyFile) { | |
198 return kEmptyMetadataFile; | |
199 } | |
200 if (is_descriptor) { | |
201 return kDescriptorMetadataFile; | |
202 } | |
203 | |
204 // Append directory name. | |
205 std::string file_no_suffix; | |
206 int lastindex = proto_file.find_last_of("."); | |
207 if (proto_file == kEmptyFile) { | |
208 return kEmptyMetadataFile; | |
209 } else { | |
210 file_no_suffix = proto_file.substr(0, lastindex); | |
211 } | |
212 | |
213 while (first_index != string::npos) { | |
214 result += UnderscoresToCamelCase( | |
215 file_no_suffix.substr(start_index, first_index - start_index), true); | |
216 result += "/"; | |
217 start_index = first_index + 1; | |
218 first_index = file_no_suffix.find_first_of("/", start_index); | |
219 } | |
220 | |
221 // Append file name. | |
222 result += RenameEmpty(UnderscoresToCamelCase( | |
223 file_no_suffix.substr(start_index, first_index - start_index), true)); | |
224 | |
225 return result += ".php"; | |
226 } | |
227 | |
228 std::string GeneratedMessageFileName(const Descriptor* message, | |
229 bool is_descriptor) { | |
230 std::string result = MessageName(message, is_descriptor); | |
231 for (int i = 0; i < result.size(); i++) { | |
232 if (result[i] == '\\') { | |
233 result[i] = '/'; | |
234 } | |
235 } | |
236 return result + ".php"; | |
237 } | |
238 | |
239 std::string GeneratedEnumFileName(const EnumDescriptor* en, | |
240 bool is_descriptor) { | |
241 std::string result = EnumName(en, is_descriptor); | |
242 for (int i = 0; i < result.size(); i++) { | |
243 if (result[i] == '\\') { | |
244 result[i] = '/'; | |
245 } | |
246 } | |
247 return result + ".php"; | |
248 } | |
249 | |
250 std::string IntToString(int32 value) { | |
251 std::ostringstream os; | |
252 os << value; | |
253 return os.str(); | |
254 } | |
255 | |
256 std::string LabelForField(const FieldDescriptor* field) { | |
257 switch (field->label()) { | |
258 case FieldDescriptor::LABEL_OPTIONAL: return "optional"; | |
259 case FieldDescriptor::LABEL_REQUIRED: return "required"; | |
260 case FieldDescriptor::LABEL_REPEATED: return "repeated"; | |
261 default: assert(false); return ""; | |
262 } | |
263 } | |
264 | |
265 std::string TypeName(const FieldDescriptor* field) { | |
266 switch (field->type()) { | |
267 case FieldDescriptor::TYPE_INT32: return "int32"; | |
268 case FieldDescriptor::TYPE_INT64: return "int64"; | |
269 case FieldDescriptor::TYPE_UINT32: return "uint32"; | |
270 case FieldDescriptor::TYPE_UINT64: return "uint64"; | |
271 case FieldDescriptor::TYPE_SINT32: return "sint32"; | |
272 case FieldDescriptor::TYPE_SINT64: return "sint64"; | |
273 case FieldDescriptor::TYPE_FIXED32: return "fixed32"; | |
274 case FieldDescriptor::TYPE_FIXED64: return "fixed64"; | |
275 case FieldDescriptor::TYPE_SFIXED32: return "sfixed32"; | |
276 case FieldDescriptor::TYPE_SFIXED64: return "sfixed64"; | |
277 case FieldDescriptor::TYPE_DOUBLE: return "double"; | |
278 case FieldDescriptor::TYPE_FLOAT: return "float"; | |
279 case FieldDescriptor::TYPE_BOOL: return "bool"; | |
280 case FieldDescriptor::TYPE_ENUM: return "enum"; | |
281 case FieldDescriptor::TYPE_STRING: return "string"; | |
282 case FieldDescriptor::TYPE_BYTES: return "bytes"; | |
283 case FieldDescriptor::TYPE_MESSAGE: return "message"; | |
284 case FieldDescriptor::TYPE_GROUP: return "group"; | |
285 default: assert(false); return ""; | |
286 } | |
287 } | |
288 | |
289 std::string EnumOrMessageSuffix( | |
290 const FieldDescriptor* field, bool is_descriptor) { | |
291 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { | |
292 return ", '" + MessageFullName(field->message_type(), is_descriptor) + "'"; | |
293 } | |
294 if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { | |
295 return ", '" + EnumFullName(field->enum_type(), is_descriptor) + "'"; | |
296 } | |
297 return ""; | |
298 } | |
299 | |
300 // Converts a name to camel-case. If cap_first_letter is true, capitalize the | |
301 // first letter. | |
302 std::string UnderscoresToCamelCase(const string& input, bool cap_first_letter) { | |
303 std::string result; | |
304 for (int i = 0; i < input.size(); i++) { | |
305 if ('a' <= input[i] && input[i] <= 'z') { | |
306 if (cap_first_letter) { | |
307 result += input[i] + ('A' - 'a'); | |
308 } else { | |
309 result += input[i]; | |
310 } | |
311 cap_first_letter = false; | |
312 } else if ('A' <= input[i] && input[i] <= 'Z') { | |
313 if (i == 0 && !cap_first_letter) { | |
314 // Force first letter to lower-case unless explicitly told to | |
315 // capitalize it. | |
316 result += input[i] + ('a' - 'A'); | |
317 } else { | |
318 // Capital letters after the first are left as-is. | |
319 result += input[i]; | |
320 } | |
321 cap_first_letter = false; | |
322 } else if ('0' <= input[i] && input[i] <= '9') { | |
323 result += input[i]; | |
324 cap_first_letter = true; | |
325 } else { | |
326 cap_first_letter = true; | |
327 } | |
328 } | |
329 // Add a trailing "_" if the name should be altered. | |
330 if (input[input.size() - 1] == '#') { | |
331 result += '_'; | |
332 } | |
333 return result; | |
334 } | |
335 | |
336 std::string EscapeDollor(const string& to_escape) { | |
337 return StringReplace(to_escape, "$", "\\$", true); | |
338 } | |
339 | |
340 std::string BinaryToHex(const string& src) { | |
341 string dest; | |
342 size_t i; | |
343 unsigned char symbol[16] = { | |
344 '0', '1', '2', '3', | |
345 '4', '5', '6', '7', | |
346 '8', '9', 'a', 'b', | |
347 'c', 'd', 'e', 'f', | |
348 }; | |
349 | |
350 dest.resize(src.size() * 2); | |
351 char* append_ptr = &dest[0]; | |
352 | |
353 for (i = 0; i < src.size(); i++) { | |
354 *append_ptr++ = symbol[(src[i] & 0xf0) >> 4]; | |
355 *append_ptr++ = symbol[src[i] & 0x0f]; | |
356 } | |
357 | |
358 return dest; | |
359 } | |
360 | |
361 void Indent(io::Printer* printer) { | |
362 printer->Indent(); | |
363 printer->Indent(); | |
364 } | |
365 void Outdent(io::Printer* printer) { | |
366 printer->Outdent(); | |
367 printer->Outdent(); | |
368 } | |
369 | |
370 void GenerateField(const FieldDescriptor* field, io::Printer* printer, | |
371 bool is_descriptor) { | |
372 if (field->is_repeated()) { | |
373 GenerateFieldDocComment(printer, field); | |
374 printer->Print( | |
375 "private $^name^;\n", | |
376 "name", field->name()); | |
377 } else if (field->containing_oneof()) { | |
378 // Oneof fields are handled by GenerateOneofField. | |
379 return; | |
380 } else { | |
381 GenerateFieldDocComment(printer, field); | |
382 printer->Print( | |
383 "private $^name^ = ^default^;\n", | |
384 "name", field->name(), | |
385 "default", DefaultForField(field)); | |
386 } | |
387 | |
388 if (is_descriptor) { | |
389 printer->Print( | |
390 "private $has_^name^ = false;\n", | |
391 "name", field->name()); | |
392 } | |
393 } | |
394 | |
395 void GenerateOneofField(const OneofDescriptor* oneof, io::Printer* printer) { | |
396 // Oneof property needs to be protected in order to be accessed by parent | |
397 // class in implementation. | |
398 printer->Print( | |
399 "protected $^name^;\n", | |
400 "name", oneof->name()); | |
401 } | |
402 | |
403 void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor, | |
404 io::Printer* printer) { | |
405 const OneofDescriptor* oneof = field->containing_oneof(); | |
406 | |
407 // Generate getter. | |
408 if (oneof != NULL) { | |
409 GenerateFieldDocComment(printer, field); | |
410 printer->Print( | |
411 "public function get^camel_name^()\n" | |
412 "{\n" | |
413 " return $this->readOneof(^number^);\n" | |
414 "}\n\n", | |
415 "camel_name", UnderscoresToCamelCase(field->name(), true), | |
416 "number", IntToString(field->number())); | |
417 } else { | |
418 GenerateFieldDocComment(printer, field); | |
419 printer->Print( | |
420 "public function get^camel_name^()\n" | |
421 "{\n" | |
422 " return $this->^name^;\n" | |
423 "}\n\n", | |
424 "camel_name", UnderscoresToCamelCase(field->name(), true), "name", | |
425 field->name()); | |
426 } | |
427 | |
428 // Generate setter. | |
429 GenerateFieldDocComment(printer, field); | |
430 printer->Print( | |
431 "public function set^camel_name^(^var^)\n" | |
432 "{\n", | |
433 "camel_name", UnderscoresToCamelCase(field->name(), true), | |
434 "var", (field->is_repeated() || | |
435 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? | |
436 "&$var": "$var"); | |
437 | |
438 Indent(printer); | |
439 | |
440 // Type check. | |
441 if (field->is_map()) { | |
442 } else if (field->is_repeated()) { | |
443 printer->Print( | |
444 "GPBUtil::checkRepeatedField($var, " | |
445 "\\Google\\Protobuf\\Internal\\GPBType::^type^", | |
446 "type", ToUpper(field->type_name())); | |
447 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { | |
448 printer->Print( | |
449 ", \\^class_name^);\n", | |
450 "class_name", | |
451 MessageName(field->message_type(), is_descriptor) + "::class"); | |
452 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { | |
453 printer->Print( | |
454 ", ^class_name^);\n", | |
455 "class_name", | |
456 EnumName(field->enum_type(), is_descriptor) + "::class"); | |
457 } else { | |
458 printer->Print(");\n"); | |
459 } | |
460 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { | |
461 printer->Print( | |
462 "GPBUtil::checkMessage($var, \\^class_name^::class);\n", | |
463 "class_name", MessageName(field->message_type(), is_descriptor)); | |
464 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { | |
465 printer->Print( | |
466 "GPBUtil::checkEnum($var, \\^class_name^::class);\n", | |
467 "class_name", EnumName(field->enum_type(), is_descriptor)); | |
468 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) { | |
469 printer->Print( | |
470 "GPBUtil::checkString($var, ^utf8^);\n", | |
471 "utf8", | |
472 field->type() == FieldDescriptor::TYPE_STRING ? "True": "False"); | |
473 } else { | |
474 printer->Print( | |
475 "GPBUtil::check^type^($var);\n", | |
476 "type", UnderscoresToCamelCase(field->cpp_type_name(), true)); | |
477 } | |
478 | |
479 if (oneof != NULL) { | |
480 printer->Print( | |
481 "$this->writeOneof(^number^, $var);\n", | |
482 "number", IntToString(field->number())); | |
483 } else { | |
484 printer->Print( | |
485 "$this->^name^ = $var;\n", | |
486 "name", field->name()); | |
487 } | |
488 | |
489 // Set has bit for proto2 only. | |
490 if (is_descriptor) { | |
491 printer->Print( | |
492 "$this->has_^field_name^ = true;\n", | |
493 "field_name", field->name()); | |
494 } | |
495 | |
496 Outdent(printer); | |
497 | |
498 printer->Print( | |
499 "}\n\n"); | |
500 | |
501 // Generate has method for proto2 only. | |
502 if (is_descriptor) { | |
503 printer->Print( | |
504 "public function has^camel_name^()\n" | |
505 "{\n" | |
506 " return $this->has_^field_name^;\n" | |
507 "}\n\n", | |
508 "camel_name", UnderscoresToCamelCase(field->name(), true), | |
509 "field_name", field->name()); | |
510 } | |
511 } | |
512 | |
513 void GenerateEnumToPool(const EnumDescriptor* en, io::Printer* printer) { | |
514 printer->Print( | |
515 "$pool->addEnum('^name^', " | |
516 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", | |
517 "name", EnumFullName(en, true), | |
518 "class_name", en->name()); | |
519 Indent(printer); | |
520 | |
521 for (int i = 0; i < en->value_count(); i++) { | |
522 const EnumValueDescriptor* value = en->value(i); | |
523 printer->Print( | |
524 "->value(\"^name^\", ^number^)\n", | |
525 "name", value->name(), | |
526 "number", IntToString(value->number())); | |
527 } | |
528 printer->Print("->finalizeToPool();\n\n"); | |
529 Outdent(printer); | |
530 } | |
531 | |
532 void GenerateMessageToPool(const string& name_prefix, const Descriptor* message, | |
533 io::Printer* printer) { | |
534 // Don't generate MapEntry messages -- we use the PHP extension's native | |
535 // support for map fields instead. | |
536 if (message->options().map_entry()) { | |
537 return; | |
538 } | |
539 string class_name = name_prefix.empty()? | |
540 message->name() : name_prefix + "_" + message->name(); | |
541 | |
542 printer->Print( | |
543 "$pool->addMessage('^message^', " | |
544 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", | |
545 "message", MessageFullName(message, true), | |
546 "class_name", class_name); | |
547 | |
548 Indent(printer); | |
549 | |
550 for (int i = 0; i < message->field_count(); i++) { | |
551 const FieldDescriptor* field = message->field(i); | |
552 if (field->is_map()) { | |
553 const FieldDescriptor* key = | |
554 field->message_type()->FindFieldByName("key"); | |
555 const FieldDescriptor* val = | |
556 field->message_type()->FindFieldByName("value"); | |
557 printer->Print( | |
558 "->map('^field^', \\Google\\Protobuf\\Internal\\GPBType::^key^, " | |
559 "\\Google\\Protobuf\\Internal\\GPBType::^value^, ^number^^other^)\n", | |
560 "field", field->name(), | |
561 "key", ToUpper(key->type_name()), | |
562 "value", ToUpper(val->type_name()), | |
563 "number", SimpleItoa(field->number()), | |
564 "other", EnumOrMessageSuffix(val, true)); | |
565 } else if (!field->containing_oneof()) { | |
566 printer->Print( | |
567 "->^label^('^field^', " | |
568 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", | |
569 "field", field->name(), | |
570 "label", LabelForField(field), | |
571 "type", ToUpper(field->type_name()), | |
572 "number", SimpleItoa(field->number()), | |
573 "other", EnumOrMessageSuffix(field, true)); | |
574 } | |
575 } | |
576 | |
577 // oneofs. | |
578 for (int i = 0; i < message->oneof_decl_count(); i++) { | |
579 const OneofDescriptor* oneof = message->oneof_decl(i); | |
580 printer->Print("->oneof(^name^)\n", | |
581 "name", oneof->name()); | |
582 Indent(printer); | |
583 for (int index = 0; index < oneof->field_count(); index++) { | |
584 const FieldDescriptor* field = oneof->field(index); | |
585 printer->Print( | |
586 "->value('^field^', " | |
587 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", | |
588 "field", field->name(), | |
589 "type", ToUpper(field->type_name()), | |
590 "number", SimpleItoa(field->number()), | |
591 "other", EnumOrMessageSuffix(field, true)); | |
592 } | |
593 printer->Print("->finish()\n"); | |
594 Outdent(printer); | |
595 } | |
596 | |
597 printer->Print( | |
598 "->finalizeToPool();\n"); | |
599 | |
600 Outdent(printer); | |
601 | |
602 printer->Print( | |
603 "\n"); | |
604 | |
605 for (int i = 0; i < message->nested_type_count(); i++) { | |
606 GenerateMessageToPool(class_name, message->nested_type(i), printer); | |
607 } | |
608 for (int i = 0; i < message->enum_type_count(); i++) { | |
609 GenerateEnumToPool(message->enum_type(i), printer); | |
610 } | |
611 } | |
612 | |
613 void GenerateAddFileToPool(const FileDescriptor* file, bool is_descriptor, | |
614 io::Printer* printer) { | |
615 printer->Print( | |
616 "public static $is_initialized = false;\n\n" | |
617 "public static function initOnce() {\n"); | |
618 Indent(printer); | |
619 | |
620 printer->Print( | |
621 "$pool = \\Google\\Protobuf\\Internal\\" | |
622 "DescriptorPool::getGeneratedPool();\n\n" | |
623 "if (static::$is_initialized == true) {\n" | |
624 " return;\n" | |
625 "}\n"); | |
626 | |
627 if (is_descriptor) { | |
628 for (int i = 0; i < file->message_type_count(); i++) { | |
629 GenerateMessageToPool("", file->message_type(i), printer); | |
630 } | |
631 for (int i = 0; i < file->enum_type_count(); i++) { | |
632 GenerateEnumToPool(file->enum_type(i), printer); | |
633 } | |
634 | |
635 printer->Print( | |
636 "$pool->finish();\n"); | |
637 } else { | |
638 for (int i = 0; i < file->dependency_count(); i++) { | |
639 const std::string& name = file->dependency(i)->name(); | |
640 std::string dependency_filename = | |
641 GeneratedMetadataFileName(name, is_descriptor); | |
642 printer->Print( | |
643 "\\^name^::initOnce();\n", | |
644 "name", FilenameToClassname(dependency_filename)); | |
645 } | |
646 | |
647 // Add messages and enums to descriptor pool. | |
648 FileDescriptorSet files; | |
649 FileDescriptorProto* file_proto = files.add_file(); | |
650 file->CopyTo(file_proto); | |
651 string files_data; | |
652 files.SerializeToString(&files_data); | |
653 | |
654 printer->Print("$pool->internalAddGeneratedFile(hex2bin(\n"); | |
655 Indent(printer); | |
656 | |
657 // Only write 30 bytes per line. | |
658 static const int kBytesPerLine = 30; | |
659 for (int i = 0; i < files_data.size(); i += kBytesPerLine) { | |
660 printer->Print( | |
661 "\"^data^\"^dot^\n", | |
662 "data", BinaryToHex(files_data.substr(i, kBytesPerLine)), | |
663 "dot", i + kBytesPerLine < files_data.size() ? " ." : ""); | |
664 } | |
665 | |
666 Outdent(printer); | |
667 printer->Print( | |
668 "));\n\n"); | |
669 } | |
670 printer->Print( | |
671 "static::$is_initialized = true;\n"); | |
672 Outdent(printer); | |
673 printer->Print("}\n"); | |
674 } | |
675 | |
676 void GenerateUseDeclaration(bool is_descriptor, io::Printer* printer) { | |
677 if (!is_descriptor) { | |
678 printer->Print( | |
679 "use Google\\Protobuf\\Internal\\GPBType;\n" | |
680 "use Google\\Protobuf\\Internal\\RepeatedField;\n" | |
681 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); | |
682 } else { | |
683 printer->Print( | |
684 "use Google\\Protobuf\\Internal\\GPBType;\n" | |
685 "use Google\\Protobuf\\Internal\\GPBWire;\n" | |
686 "use Google\\Protobuf\\Internal\\RepeatedField;\n" | |
687 "use Google\\Protobuf\\Internal\\InputStream;\n\n" | |
688 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); | |
689 } | |
690 } | |
691 | |
692 void GenerateHead(const FileDescriptor* file, io::Printer* printer) { | |
693 printer->Print( | |
694 "<?php\n" | |
695 "# Generated by the protocol buffer compiler. DO NOT EDIT!\n" | |
696 "# source: ^filename^\n" | |
697 "\n", | |
698 "filename", file->name()); | |
699 } | |
700 | |
701 std::string FilenameToClassname(const string& filename) { | |
702 int lastindex = filename.find_last_of("."); | |
703 std::string result = filename.substr(0, lastindex); | |
704 for (int i = 0; i < result.size(); i++) { | |
705 if (result[i] == '/') { | |
706 result[i] = '\\'; | |
707 } | |
708 } | |
709 return result; | |
710 } | |
711 | |
712 void GenerateMetadataFile(const FileDescriptor* file, | |
713 bool is_descriptor, | |
714 GeneratorContext* generator_context) { | |
715 std::string filename = GeneratedMetadataFileName(file->name(), is_descriptor); | |
716 scoped_ptr<io::ZeroCopyOutputStream> output( | |
717 generator_context->Open(filename)); | |
718 io::Printer printer(output.get(), '^'); | |
719 | |
720 GenerateHead(file, &printer); | |
721 | |
722 std::string fullname = FilenameToClassname(filename); | |
723 int lastindex = fullname.find_last_of("\\"); | |
724 | |
725 printer.Print( | |
726 "namespace ^name^;\n\n", | |
727 "name", fullname.substr(0, lastindex)); | |
728 | |
729 if (lastindex != string::npos) { | |
730 printer.Print( | |
731 "class ^name^\n" | |
732 "{\n", | |
733 "name", fullname.substr(lastindex + 1)); | |
734 } else { | |
735 printer.Print( | |
736 "class ^name^\n" | |
737 "{\n", | |
738 "name", fullname); | |
739 } | |
740 Indent(&printer); | |
741 | |
742 GenerateAddFileToPool(file, is_descriptor, &printer); | |
743 | |
744 Outdent(&printer); | |
745 printer.Print("}\n\n"); | |
746 } | |
747 | |
748 void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en, | |
749 bool is_descriptor, GeneratorContext* generator_context) { | |
750 std::string filename = GeneratedEnumFileName(en, is_descriptor); | |
751 scoped_ptr<io::ZeroCopyOutputStream> output( | |
752 generator_context->Open(filename)); | |
753 io::Printer printer(output.get(), '^'); | |
754 | |
755 GenerateHead(file, &printer); | |
756 | |
757 std::string fullname = FilenameToClassname(filename); | |
758 int lastindex = fullname.find_last_of("\\"); | |
759 | |
760 GenerateEnumDocComment(&printer, en); | |
761 if (lastindex != string::npos) { | |
762 printer.Print( | |
763 "namespace ^name^;\n\n", | |
764 "name", fullname.substr(0, lastindex)); | |
765 | |
766 printer.Print( | |
767 "class ^name^\n" | |
768 "{\n", | |
769 "name", fullname.substr(lastindex + 1)); | |
770 } else { | |
771 printer.Print( | |
772 "class ^name^\n" | |
773 "{\n", | |
774 "name", fullname); | |
775 } | |
776 Indent(&printer); | |
777 | |
778 for (int i = 0; i < en->value_count(); i++) { | |
779 const EnumValueDescriptor* value = en->value(i); | |
780 GenerateEnumValueDocComment(&printer, value); | |
781 printer.Print("const ^name^ = ^number^;\n", | |
782 "name", value->name(), | |
783 "number", IntToString(value->number())); | |
784 } | |
785 | |
786 Outdent(&printer); | |
787 printer.Print("}\n\n"); | |
788 } | |
789 | |
790 void GenerateMessageFile(const FileDescriptor* file, const Descriptor* message, | |
791 bool is_descriptor, | |
792 GeneratorContext* generator_context) { | |
793 // Don't generate MapEntry messages -- we use the PHP extension's native | |
794 // support for map fields instead. | |
795 if (message->options().map_entry()) { | |
796 return; | |
797 } | |
798 | |
799 std::string filename = GeneratedMessageFileName(message, is_descriptor); | |
800 scoped_ptr<io::ZeroCopyOutputStream> output( | |
801 generator_context->Open(filename)); | |
802 io::Printer printer(output.get(), '^'); | |
803 | |
804 GenerateHead(file, &printer); | |
805 | |
806 std::string fullname = FilenameToClassname(filename); | |
807 int lastindex = fullname.find_last_of("\\"); | |
808 | |
809 if (!file->package().empty()) { | |
810 printer.Print( | |
811 "namespace ^name^;\n\n", | |
812 "name", fullname.substr(0, lastindex)); | |
813 } | |
814 | |
815 GenerateUseDeclaration(is_descriptor, &printer); | |
816 | |
817 GenerateMessageDocComment(&printer, message); | |
818 if (lastindex != string::npos) { | |
819 printer.Print( | |
820 "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" | |
821 "{\n", | |
822 "name", fullname.substr(lastindex + 1)); | |
823 } else { | |
824 printer.Print( | |
825 "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" | |
826 "{\n", | |
827 "name", fullname); | |
828 } | |
829 Indent(&printer); | |
830 | |
831 // Field and oneof definitions. | |
832 for (int i = 0; i < message->field_count(); i++) { | |
833 const FieldDescriptor* field = message->field(i); | |
834 GenerateField(field, &printer, is_descriptor); | |
835 } | |
836 for (int i = 0; i < message->oneof_decl_count(); i++) { | |
837 const OneofDescriptor* oneof = message->oneof_decl(i); | |
838 GenerateOneofField(oneof, &printer); | |
839 } | |
840 printer.Print("\n"); | |
841 | |
842 printer.Print( | |
843 "public function __construct() {\n"); | |
844 Indent(&printer); | |
845 | |
846 std::string metadata_filename = | |
847 GeneratedMetadataFileName(file->name(), is_descriptor); | |
848 std::string metadata_fullname = FilenameToClassname(metadata_filename); | |
849 printer.Print( | |
850 "\\^fullname^::initOnce();\n" | |
851 "parent::__construct();\n", | |
852 "fullname", metadata_fullname); | |
853 | |
854 Outdent(&printer); | |
855 printer.Print("}\n\n"); | |
856 | |
857 // Field and oneof accessors. | |
858 for (int i = 0; i < message->field_count(); i++) { | |
859 const FieldDescriptor* field = message->field(i); | |
860 GenerateFieldAccessor(field, is_descriptor, &printer); | |
861 } | |
862 for (int i = 0; i < message->oneof_decl_count(); i++) { | |
863 const OneofDescriptor* oneof = message->oneof_decl(i); | |
864 printer.Print( | |
865 "public function get^camel_name^()\n" | |
866 "{\n" | |
867 " return $this->^name^;\n" | |
868 "}\n\n", | |
869 "camel_name", UnderscoresToCamelCase(oneof->name(), true), "name", | |
870 oneof->name()); | |
871 } | |
872 | |
873 Outdent(&printer); | |
874 printer.Print("}\n\n"); | |
875 | |
876 // Nested messages and enums. | |
877 for (int i = 0; i < message->nested_type_count(); i++) { | |
878 GenerateMessageFile(file, message->nested_type(i), is_descriptor, | |
879 generator_context); | |
880 } | |
881 for (int i = 0; i < message->enum_type_count(); i++) { | |
882 GenerateEnumFile(file, message->enum_type(i), is_descriptor, | |
883 generator_context); | |
884 } | |
885 } | |
886 | |
887 void GenerateFile(const FileDescriptor* file, bool is_descriptor, | |
888 GeneratorContext* generator_context) { | |
889 GenerateMetadataFile(file, is_descriptor, generator_context); | |
890 for (int i = 0; i < file->message_type_count(); i++) { | |
891 GenerateMessageFile(file, file->message_type(i), is_descriptor, | |
892 generator_context); | |
893 } | |
894 for (int i = 0; i < file->enum_type_count(); i++) { | |
895 GenerateEnumFile(file, file->enum_type(i), is_descriptor, | |
896 generator_context); | |
897 } | |
898 } | |
899 | |
900 static string EscapePhpdoc(const string& input) { | |
901 string result; | |
902 result.reserve(input.size() * 2); | |
903 | |
904 char prev = '*'; | |
905 | |
906 for (string::size_type i = 0; i < input.size(); i++) { | |
907 char c = input[i]; | |
908 switch (c) { | |
909 case '*': | |
910 // Avoid "/*". | |
911 if (prev == '/') { | |
912 result.append("*"); | |
913 } else { | |
914 result.push_back(c); | |
915 } | |
916 break; | |
917 case '/': | |
918 // Avoid "*/". | |
919 if (prev == '*') { | |
920 result.append("/"); | |
921 } else { | |
922 result.push_back(c); | |
923 } | |
924 break; | |
925 case '@': | |
926 // '@' starts phpdoc tags including the @deprecated tag, which will | |
927 // cause a compile-time error if inserted before a declaration that | |
928 // does not have a corresponding @Deprecated annotation. | |
929 result.append("@"); | |
930 break; | |
931 case '<': | |
932 // Avoid interpretation as HTML. | |
933 result.append("<"); | |
934 break; | |
935 case '>': | |
936 // Avoid interpretation as HTML. | |
937 result.append(">"); | |
938 break; | |
939 case '&': | |
940 // Avoid interpretation as HTML. | |
941 result.append("&"); | |
942 break; | |
943 case '\\': | |
944 // Java interprets Unicode escape sequences anywhere! | |
945 result.append("\"); | |
946 break; | |
947 default: | |
948 result.push_back(c); | |
949 break; | |
950 } | |
951 | |
952 prev = c; | |
953 } | |
954 | |
955 return result; | |
956 } | |
957 | |
958 static void GenerateDocCommentBodyForLocation( | |
959 io::Printer* printer, const SourceLocation& location) { | |
960 string comments = location.leading_comments.empty() ? | |
961 location.trailing_comments : location.leading_comments; | |
962 if (!comments.empty()) { | |
963 // TODO(teboring): Ideally we should parse the comment text as Markdown and | |
964 // write it back as HTML, but this requires a Markdown parser. For now | |
965 // we just use <pre> to get fixed-width text formatting. | |
966 | |
967 // If the comment itself contains block comment start or end markers, | |
968 // HTML-escape them so that they don't accidentally close the doc comment. | |
969 comments = EscapePhpdoc(comments); | |
970 | |
971 vector<string> lines = Split(comments, "\n"); | |
972 while (!lines.empty() && lines.back().empty()) { | |
973 lines.pop_back(); | |
974 } | |
975 | |
976 printer->Print(" * <pre>\n"); | |
977 for (int i = 0; i < lines.size(); i++) { | |
978 // Most lines should start with a space. Watch out for lines that start | |
979 // with a /, since putting that right after the leading asterisk will | |
980 // close the comment. | |
981 if (!lines[i].empty() && lines[i][0] == '/') { | |
982 printer->Print(" * ^line^\n", "line", lines[i]); | |
983 } else { | |
984 printer->Print(" *^line^\n", "line", lines[i]); | |
985 } | |
986 } | |
987 printer->Print( | |
988 " * </pre>\n" | |
989 " *\n"); | |
990 } | |
991 } | |
992 | |
993 template <typename DescriptorType> | |
994 static void GenerateDocCommentBody( | |
995 io::Printer* printer, const DescriptorType* descriptor) { | |
996 SourceLocation location; | |
997 if (descriptor->GetSourceLocation(&location)) { | |
998 GenerateDocCommentBodyForLocation(printer, location); | |
999 } | |
1000 } | |
1001 | |
1002 static string FirstLineOf(const string& value) { | |
1003 string result = value; | |
1004 | |
1005 string::size_type pos = result.find_first_of('\n'); | |
1006 if (pos != string::npos) { | |
1007 result.erase(pos); | |
1008 } | |
1009 | |
1010 return result; | |
1011 } | |
1012 | |
1013 void GenerateMessageDocComment(io::Printer* printer, | |
1014 const Descriptor* message) { | |
1015 printer->Print("/**\n"); | |
1016 GenerateDocCommentBody(printer, message); | |
1017 printer->Print( | |
1018 " * Protobuf type <code>^fullname^</code>\n" | |
1019 " */\n", | |
1020 "fullname", EscapePhpdoc(message->full_name())); | |
1021 } | |
1022 | |
1023 void GenerateFieldDocComment(io::Printer* printer, | |
1024 const FieldDescriptor* field) { | |
1025 // In theory we should have slightly different comments for setters, getters, | |
1026 // etc., but in practice everyone already knows the difference between these | |
1027 // so it's redundant information. | |
1028 | |
1029 // We start the comment with the main body based on the comments from the | |
1030 // .proto file (if present). We then end with the field declaration, e.g.: | |
1031 // optional string foo = 5; | |
1032 // If the field is a group, the debug string might end with {. | |
1033 printer->Print("/**\n"); | |
1034 GenerateDocCommentBody(printer, field); | |
1035 printer->Print( | |
1036 " * <code>^def^</code>\n", | |
1037 "def", EscapePhpdoc(FirstLineOf(field->DebugString()))); | |
1038 printer->Print(" */\n"); | |
1039 } | |
1040 | |
1041 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_) { | |
1042 printer->Print("/**\n"); | |
1043 GenerateDocCommentBody(printer, enum_); | |
1044 printer->Print( | |
1045 " * Protobuf enum <code>^fullname^</code>\n" | |
1046 " */\n", | |
1047 "fullname", EscapePhpdoc(enum_->full_name())); | |
1048 } | |
1049 | |
1050 void GenerateEnumValueDocComment(io::Printer* printer, | |
1051 const EnumValueDescriptor* value) { | |
1052 printer->Print("/**\n"); | |
1053 GenerateDocCommentBody(printer, value); | |
1054 printer->Print( | |
1055 " * <code>^def^</code>\n" | |
1056 " */\n", | |
1057 "def", EscapePhpdoc(FirstLineOf(value->DebugString()))); | |
1058 } | |
1059 | |
1060 bool Generator::Generate(const FileDescriptor* file, const string& parameter, | |
1061 GeneratorContext* generator_context, | |
1062 string* error) const { | |
1063 bool is_descriptor = parameter == "internal"; | |
1064 | |
1065 if (is_descriptor && file->name() != kDescriptorFile) { | |
1066 *error = | |
1067 "Can only generate PHP code for google/protobuf/descriptor.proto.\n"; | |
1068 return false; | |
1069 } | |
1070 | |
1071 if (!is_descriptor && file->syntax() != FileDescriptor::SYNTAX_PROTO3) { | |
1072 *error = | |
1073 "Can only generate PHP code for proto3 .proto files.\n" | |
1074 "Please add 'syntax = \"proto3\";' to the top of your .proto file.\n"; | |
1075 return false; | |
1076 } | |
1077 | |
1078 GenerateFile(file, is_descriptor, generator_context); | |
1079 | |
1080 return true; | |
1081 } | |
1082 | |
1083 } // namespace php | |
1084 } // namespace compiler | |
1085 } // namespace protobuf | |
1086 } // namespace google | |
OLD | NEW |